From c97818d7809eba914be533e96cec26a29cb5d021 Mon Sep 17 00:00:00 2001 From: Daiyi Peng Date: Thu, 4 Jan 2024 15:32:55 -0800 Subject: [PATCH] Reimplement markdown quotes for object formatting. This gives `pg.Formattable` subclasses control whether to apply the markdown quotes. Also it allows better control for quotes to be applied only on the top level objects. PiperOrigin-RevId: 595819480 --- pyglove/core/geno/base.py | 60 +++++--- pyglove/core/object_utils/__init__.py | 1 + pyglove/core/object_utils/common_traits.py | 22 +-- .../core/object_utils/common_traits_test.py | 7 - pyglove/core/object_utils/formatting.py | 42 +++-- pyglove/core/object_utils/formatting_test.py | 19 +++ pyglove/core/symbolic/base.py | 25 +-- pyglove/core/symbolic/dict.py | 10 +- pyglove/core/symbolic/dict_test.py | 62 ++++---- pyglove/core/symbolic/diff.py | 8 +- pyglove/core/symbolic/list.py | 6 +- pyglove/core/symbolic/list_test.py | 68 ++++---- pyglove/core/symbolic/object_test.py | 46 +++--- pyglove/core/symbolic/origin.py | 18 ++- pyglove/core/symbolic/ref.py | 19 ++- pyglove/core/typing/callable_signature.py | 6 +- pyglove/core/typing/class_schema.py | 29 ++-- pyglove/core/typing/value_specs.py | 145 ++++++++++++------ 18 files changed, 359 insertions(+), 234 deletions(-) diff --git a/pyglove/core/geno/base.py b/pyglove/core/geno/base.py index 806e4c9..63c9025 100644 --- a/pyglove/core/geno/base.py +++ b/pyglove/core/geno/base.py @@ -1593,13 +1593,17 @@ def iter_dna(self): self._ensure_dna_spec() return self.spec.iter_dna(self) - def format(self, - compact: bool = False, - verbose: bool = True, - root_indent: int = 0, - list_wrap_threshold: int = 80, - as_dict: bool = False, - **kwargs): + def format( + self, + compact: bool = False, + verbose: bool = True, + root_indent: int = 0, + *, + markdown: bool = False, + list_wrap_threshold: int = 80, + as_dict: bool = False, + **kwargs, + ): """Customize format method for DNA for more compact representation.""" if as_dict and self.spec: details = object_utils.format( @@ -1608,25 +1612,31 @@ def format(self, verbose, root_indent, **kwargs) - return f'DNA({details})' - - if 'list_wrap_threshold' not in kwargs: - kwargs['list_wrap_threshold'] = list_wrap_threshold - - if not verbose: - return super().format(False, verbose, root_indent, **kwargs) - - if self.is_leaf: - return f'DNA({self.value!r})' + s = f'DNA({details})' + compact = False + else: + if 'list_wrap_threshold' not in kwargs: + kwargs['list_wrap_threshold'] = list_wrap_threshold - rep = object_utils.format( - self.to_json(compact=True, type_info=False), - compact, verbose, root_indent, **kwargs) - if rep and rep[0] == '(': - # NOTE(daiyip): for conditional choice from the root, - # we don't want to keep duplicate round bracket. - return f'DNA{rep}' - return f'DNA({rep})' + if not verbose: + s = super().format(False, verbose, root_indent, **kwargs) + elif self.is_leaf: + s = f'DNA({self.value!r})' + else: + rep = object_utils.format( + self.to_json(compact=True, type_info=False), + compact, + verbose, + root_indent, + **kwargs, + ) + if rep and rep[0] == '(': + # NOTE(daiyip): for conditional choice from the root, + # we don't want to keep duplicate round bracket. + s = f'DNA{rep}' + else: + s = f'DNA({rep})' + return object_utils.maybe_markdown_quote(s, markdown) def parameters( self, use_literal_values: bool = False) -> Dict[str, str]: diff --git a/pyglove/core/object_utils/__init__.py b/pyglove/core/object_utils/__init__.py index ef195d0..92bfc51 100644 --- a/pyglove/core/object_utils/__init__.py +++ b/pyglove/core/object_utils/__init__.py @@ -111,6 +111,7 @@ from pyglove.core.object_utils.formatting import printv as print # pylint: disable=redefined-builtin from pyglove.core.object_utils.formatting import kvlist_str from pyglove.core.object_utils.formatting import quote_if_str +from pyglove.core.object_utils.formatting import maybe_markdown_quote from pyglove.core.object_utils.formatting import comma_delimited_str from pyglove.core.object_utils.formatting import auto_plural from pyglove.core.object_utils.formatting import message_on_path diff --git a/pyglove/core/object_utils/common_traits.py b/pyglove/core/object_utils/common_traits.py index 2aee2bd..ed423ab 100644 --- a/pyglove/core/object_utils/common_traits.py +++ b/pyglove/core/object_utils/common_traits.py @@ -76,31 +76,13 @@ def __str__(self) -> str: """Returns the full (maybe multi-line) representation of this object.""" kwargs = dict(self.__str_format_kwargs__) kwargs.update(thread_local.thread_local_kwargs(_TLS_STR_FORMAT_KWARGS)) - return self._maybe_quote(self.format(**kwargs), **kwargs) + return self.format(**kwargs) def __repr__(self) -> str: """Returns a single-line representation of this object.""" kwargs = dict(self.__repr_format_kwargs__) kwargs.update(thread_local.thread_local_kwargs(_TLS_REPR_FORMAT_KWARGS)) - return self._maybe_quote(self.format(**kwargs), **kwargs) - - def _maybe_quote( - self, - s: str, - *, - compact: bool = False, - root_indent: int = 0, - markdown: bool = False, - **kwargs - ) -> str: - """Maybe quote the formatted string with markdown.""" - del kwargs - if not markdown or root_indent > 0: - return s - if compact: - return f'`{s}`' - else: - return f'\n```\n{s}\n```\n' + return self.format(**kwargs) class MaybePartial(metaclass=abc.ABCMeta): diff --git a/pyglove/core/object_utils/common_traits_test.py b/pyglove/core/object_utils/common_traits_test.py index 0595d84..fa938d2 100644 --- a/pyglove/core/object_utils/common_traits_test.py +++ b/pyglove/core/object_utils/common_traits_test.py @@ -60,13 +60,6 @@ def test_formattable_with_context_managers(self): self.assertEqual(repr(foo), 'Foo(compact=False, verbose=True)') self.assertEqual(str(foo), 'Foo(compact=False, verbose=False)') - bar = Bar(foo) - with common_traits.repr_format(markdown=True): - self.assertEqual(repr(bar), '`Bar(foo=Foo(compact=True, verbose=True))`') - with common_traits.str_format(markdown=True): - self.assertEqual( - str(bar), '\n```\nBar(foo=Foo(compact=False, verbose=True))\n```\n') - class ExplicitlyOverrideTest(unittest.TestCase): diff --git a/pyglove/core/object_utils/formatting.py b/pyglove/core/object_utils/formatting.py index 274b0cf..d1e9aa3 100644 --- a/pyglove/core/object_utils/formatting.py +++ b/pyglove/core/object_utils/formatting.py @@ -114,31 +114,35 @@ def bracket_chars(bracket_type: BracketType) -> Tuple[str, str]: return _BRACKET_CHARS[int(bracket_type)] -def format(value: Any, # pylint: disable=redefined-builtin - compact: bool = False, - verbose: bool = True, - root_indent: int = 0, - list_wrap_threshold: int = 80, - strip_object_id: bool = False, - include_keys: Optional[Set[str]] = None, - exclude_keys: Optional[Set[str]] = None, - **kwargs) -> str: +def format( # pylint: disable=redefined-builtin + value: Any, + compact: bool = False, + verbose: bool = True, + root_indent: int = 0, + list_wrap_threshold: int = 80, + strip_object_id: bool = False, + include_keys: Optional[Set[str]] = None, + exclude_keys: Optional[Set[str]] = None, + markdown: bool = False, + **kwargs, +) -> str: """Formats a (maybe) hierarchical value with flags. Args: value: The value to format. compact: If True, this object will be formatted into a single line. - verbose: If True, this object will be formatted with verbosity. - Subclasses should define `verbosity` on their own. + verbose: If True, this object will be formatted with verbosity. Subclasses + should define `verbosity` on their own. root_indent: The start indent level for this object if the output is a multi-line string. - list_wrap_threshold: A threshold in number of characters for wrapping a - list value in a single line. + list_wrap_threshold: A threshold in number of characters for wrapping a list + value in a single line. strip_object_id: If True, format object as '(...)' other than 'object at
'. include_keys: A set of keys to include from the top-level dict or object. exclude_keys: A set of keys to exclude from the top-level dict or object. Applicable only when `include_keys` is set to None. + markdown: If True, use markdown notion to quote the formatted object. **kwargs: Keyword arguments that will be passed through unto child ``Formattable`` objects. @@ -211,7 +215,17 @@ def _format_child(v): s = [repr(value) if compact else str(value)] if strip_object_id and 'object at 0x' in s[-1]: s = [f'{value.__class__.__name__}(...)'] - return ''.join(s) + return maybe_markdown_quote(''.join(s), markdown) + + +def maybe_markdown_quote(s: str, markdown: bool = True) -> str: + """Maybe quote the formatted string with markdown.""" + if not markdown: + return s + if '\n' not in s: + return f'`{s}`' + else: + return f'```\n{s}\n```' def printv(v: Any, **kwargs): diff --git a/pyglove/core/object_utils/formatting_test.py b/pyglove/core/object_utils/formatting_test.py index 3c0a707..d6f599f 100644 --- a/pyglove/core/object_utils/formatting_test.py +++ b/pyglove/core/object_utils/formatting_test.py @@ -199,6 +199,25 @@ def _should_include(k): } }""")) + def test_markdown(self): + self.assertEqual( + formatting.format([1], compact=True, markdown=True), '`[1]`' + ) + self.assertEqual( + formatting.format( + [1, 2, 3], list_wrap_threshold=5, compact=False, markdown=True + ), + inspect.cleandoc(""" + ``` + [ + 1, + 2, + 3 + ] + ``` + """), + ) + if __name__ == '__main__': unittest.main() diff --git a/pyglove/core/symbolic/base.py b/pyglove/core/symbolic/base.py index ad3d9a5..d95c0f9 100644 --- a/pyglove/core/symbolic/base.py +++ b/pyglove/core/symbolic/base.py @@ -61,15 +61,18 @@ def __init__(self, self.old_value = old_value self.new_value = new_value - def format(self, - compact: bool = False, - verbose: bool = True, - root_indent: int = 0, - *, - python_format: bool = False, - hide_default_values: bool = False, - hide_missing_values: bool = False, - **kwargs) -> str: + def format( + self, + compact: bool = False, + verbose: bool = True, + root_indent: int = 0, + *, + python_format: bool = False, + markdown: bool = False, + hide_default_values: bool = False, + hide_missing_values: bool = False, + **kwargs, + ) -> str: """Formats this object.""" kwargs.update({ 'python_format': python_format, @@ -87,7 +90,9 @@ def format(self, self.new_value, compact, verbose, root_indent + 1, **kwargs), object_utils.MISSING_VALUE), ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) def __eq__(self, other: Any) -> bool: """Operator ==.""" diff --git a/pyglove/core/symbolic/dict.py b/pyglove/core/symbolic/dict.py index 8525d9a..1bcfdff 100644 --- a/pyglove/core/symbolic/dict.py +++ b/pyglove/core/symbolic/dict.py @@ -879,6 +879,7 @@ def format( root_indent: int = 0, *, python_format: bool = False, + markdown: bool = False, hide_default_values: bool = False, hide_missing_values: bool = False, include_keys: Optional[Set[str]] = None, @@ -888,7 +889,8 @@ def format( bracket_type: object_utils.BracketType = object_utils.BracketType.CURLY, key_as_attribute: bool = False, extra_blankline_for_field_docstr: bool = False, - **kwargs) -> str: + **kwargs, + ) -> str: """Formats this Dict.""" cls_name = cls_name or '' exclude_keys = exclude_keys or set() @@ -926,7 +928,9 @@ def _should_include_key(key): open_bracket, close_bracket = object_utils.bracket_chars(bracket_type) if not field_list: - return f'{cls_name}{open_bracket}{close_bracket}' + return object_utils.maybe_markdown_quote( + f'{cls_name}{open_bracket}{close_bracket}', markdown + ) if compact: s = [f'{cls_name}{open_bracket}'] @@ -984,7 +988,7 @@ def _should_include_key(key): s.append(_indent(f'\'{k}\': {v_str}', root_indent + 1)) s.append('\n') s.append(_indent(close_bracket, root_indent)) - return ''.join(s) + return object_utils.maybe_markdown_quote(''.join(s), markdown) def __repr__(self) -> str: """Operator repr().""" diff --git a/pyglove/core/symbolic/dict_test.py b/pyglove/core/symbolic/dict_test.py index 485de45..35910a0 100644 --- a/pyglove/core/symbolic/dict_test.py +++ b/pyglove/core/symbolic/dict_test.py @@ -2046,39 +2046,47 @@ def test_compact_exclude_keys(self): def test_compact_python_format(self): self.assertEqual( - self._dict.format(compact=True, python_format=True), - '{\'a1\': 1, \'a2\': {\'b1\': {\'c1\': [{\'d1\': MISSING_VALUE, ' - '\'d2\': True, \'d3\': A(x=2, y=MISSING_VALUE, z={\'p\': [None, True], ' - '\'q\': \'foo\', \'t\': \'foo\'})}]}}}') + self._dict.format(compact=True, python_format=True, markdown=True), + "`{'a1': 1, 'a2': {'b1': {'c1': [{'d1': MISSING_VALUE, " + "'d2': True, 'd3': A(x=2, y=MISSING_VALUE, z={'p': [None, True], " + "'q': 'foo', 't': 'foo'})}]}}}`", + ) def test_noncompact_python_format(self): self.assertEqual( - self._dict.format(compact=False, verbose=False, python_format=True), - inspect.cleandoc("""{ - 'a1': 1, - 'a2': { - 'b1': { - 'c1': [ - { - 'd1': MISSING_VALUE(Str()), - 'd2': True, - 'd3': A( - x=2, - y=MISSING_VALUE(Str()), - z={ - 'p': [ - None, - True - ], - 'q': 'foo', - 't': 'foo' + self._dict.format( + compact=False, verbose=False, python_format=True, markdown=True + ), + inspect.cleandoc(""" + ``` + { + 'a1': 1, + 'a2': { + 'b1': { + 'c1': [ + { + 'd1': MISSING_VALUE(Str()), + 'd2': True, + 'd3': A( + x=2, + y=MISSING_VALUE(Str()), + z={ + 'p': [ + None, + True + ], + 'q': 'foo', + 't': 'foo' + } + ) } - ) + ] } - ] + } } - } - }""")) + ``` + """), + ) def test_noncompact_nonverbose(self): self.assertEqual( diff --git a/pyglove/core/symbolic/diff.py b/pyglove/core/symbolic/diff.py index 09e2dc3..ccba372 100644 --- a/pyglove/core/symbolic/diff.py +++ b/pyglove/core/symbolic/diff.py @@ -128,7 +128,9 @@ def format( verbose=verbose, root_indent=root_indent, cls_name='', - bracket_type=object_utils.BracketType.SQUARE) + bracket_type=object_utils.BracketType.SQUARE, + **kwargs, + ) if self.left is self.right: cls_name = self.left.__name__ else: @@ -138,7 +140,9 @@ def format( verbose=verbose, root_indent=root_indent, cls_name=cls_name, - bracket_type=object_utils.BracketType.ROUND) + bracket_type=object_utils.BracketType.ROUND, + **kwargs, + ) # NOTE(daiyip): we add the symbolic attribute to Diff after its declaration diff --git a/pyglove/core/symbolic/list.py b/pyglove/core/symbolic/list.py index c98f7b2..9481ad5 100644 --- a/pyglove/core/symbolic/list.py +++ b/pyglove/core/symbolic/list.py @@ -767,10 +767,12 @@ def format( root_indent: int = 0, *, python_format: bool = False, + markdown: bool = False, use_inferred: bool = False, cls_name: Optional[str] = None, bracket_type: object_utils.BracketType = object_utils.BracketType.SQUARE, - **kwargs) -> str: + **kwargs, + ) -> str: """Formats this List.""" def _indent(text, indent): @@ -813,7 +815,7 @@ def _indent(text, indent): s.append(_indent(close_bracket, root_indent)) else: s.append(close_bracket) - return ''.join(s) + return object_utils.maybe_markdown_quote(''.join(s), markdown) def __copy__(self) -> 'List': """List.copy.""" diff --git a/pyglove/core/symbolic/list_test.py b/pyglove/core/symbolic/list_test.py index 28392f4..f43c69c 100644 --- a/pyglove/core/symbolic/list_test.py +++ b/pyglove/core/symbolic/list_test.py @@ -1716,41 +1716,49 @@ def test_compact(self): def test_compact_python_format(self): self.assertEqual( - self._list.format(compact=True, python_format=True), - '[{\'a1\': 1, \'a2\': {\'b1\': {\'c1\': [{\'d1\': MISSING_VALUE, ' - '\'d2\': True, \'d3\': A(x=2, y=MISSING_VALUE, z={\'p\': [None, True], ' - '\'q\': \'foo\', \'t\': \'foo\'})}]}}}]') + self._list.format(compact=True, python_format=True, markdown=True), + "`[{'a1': 1, 'a2': {'b1': {'c1': [{'d1': MISSING_VALUE, " + "'d2': True, 'd3': A(x=2, y=MISSING_VALUE, z={'p': [None, True], " + "'q': 'foo', 't': 'foo'})}]}}}]`", + ) def test_noncompact_python_format(self): self.assertEqual( - self._list.format(compact=False, verbose=False, python_format=True), - inspect.cleandoc("""[ - { - 'a1': 1, - 'a2': { - 'b1': { - 'c1': [ - { - 'd1': MISSING_VALUE(Str()), - 'd2': True, - 'd3': A( - x=2, - y=MISSING_VALUE(Str()), - z={ - 'p': [ - None, - True - ], - 'q': 'foo', - 't': 'foo' - } - ) - } - ] + self._list.format( + compact=False, verbose=False, python_format=True, markdown=True + ), + inspect.cleandoc(""" + ``` + [ + { + 'a1': 1, + 'a2': { + 'b1': { + 'c1': [ + { + 'd1': MISSING_VALUE(Str()), + 'd2': True, + 'd3': A( + x=2, + y=MISSING_VALUE(Str()), + z={ + 'p': [ + None, + True + ], + 'q': 'foo', + 't': 'foo' + } + ) + } + ] + } } } - } - ]""")) + ] + ``` + """), + ) def test_compact_inferred(self): self.assertEqual( diff --git a/pyglove/core/symbolic/object_test.py b/pyglove/core/symbolic/object_test.py index 6213ab4..f6a419f 100644 --- a/pyglove/core/symbolic/object_test.py +++ b/pyglove/core/symbolic/object_test.py @@ -3090,9 +3090,10 @@ def test_compact(self): def test_compact_python_format(self): self.assertEqual( - self._a.format(compact=True, python_format=True), - 'A(x=[A(x=1, y=None), A(x=\'foo\', y={\'a\': A(x=True, y=1.0)})], ' - 'y=MISSING_VALUE)') + self._a.format(compact=True, python_format=True, markdown=True), + "`A(x=[A(x=1, y=None), A(x='foo', y={'a': A(x=True, y=1.0)})], " + 'y=MISSING_VALUE)`', + ) def test_noncompact_with_inferred_value(self): @@ -3134,25 +3135,32 @@ class A(Object): def test_noncompact_python_format(self): self.assertEqual( - self._a.format(compact=False, verbose=False, python_format=True), - inspect.cleandoc("""A( - x=[ - A( - x=1, - y=None - ), + self._a.format( + compact=False, verbose=False, python_format=True, markdown=True + ), + inspect.cleandoc(""" + ``` A( - x='foo', - y={ - 'a': A( - x=True, - y=1.0 + x=[ + A( + x=1, + y=None + ), + A( + x='foo', + y={ + 'a': A( + x=True, + y=1.0 + ) + } ) - } + ], + y=MISSING_VALUE(Any()) ) - ], - y=MISSING_VALUE(Any()) - )""")) + ``` + """), + ) def test_noncompact_nonverbose(self): self.assertEqual( diff --git a/pyglove/core/symbolic/origin.py b/pyglove/core/symbolic/origin.py index 3266552..dabc936 100644 --- a/pyglove/core/symbolic/origin.py +++ b/pyglove/core/symbolic/origin.py @@ -147,11 +147,15 @@ def chain(self, tag: Optional[str] = None) -> List['Origin']: o = getattr(o.source, 'sym_origin', None) return origins - def format(self, - compact: bool = False, - verbose: bool = True, - root_indent: int = 0, - **kwargs) -> str: + def format( + self, + compact: bool = False, + verbose: bool = True, + root_indent: int = 0, + *, + markdown: bool = False, + **kwargs, + ) -> str: """Formats this object.""" if isinstance(self._source, (str, type(None))): source_str = object_utils.quote_if_str(self._source) @@ -163,7 +167,9 @@ def format(self, ('tag', object_utils.quote_if_str(self._tag), None), ('source', source_str, None), ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) def __eq__(self, other: Any) -> bool: """Operator ==.""" diff --git a/pyglove/core/symbolic/ref.py b/pyglove/core/symbolic/ref.py index b4cc78c..b923969 100644 --- a/pyglove/core/symbolic/ref.py +++ b/pyglove/core/symbolic/ref.py @@ -146,17 +146,24 @@ def format( compact: bool = False, verbose: bool = False, root_indent: int = 0, - **kwargs: Any) -> str: + *, + markdown: bool = False, + **kwargs: Any, + ) -> str: value_str = object_utils.format( self._value, compact=compact, verbose=verbose, root_indent=root_indent + 1) if compact: - return f'{self.__class__.__name__}({value_str})' + s = f'{self.__class__.__name__}({value_str})' else: - return (f'{self.__class__.__name__}(\n' - + ' ' * (root_indent + 1) - + f'value = {value_str}\n' - + ' ' * root_indent + ')') + s = ( + f'{self.__class__.__name__}(\n' + + ' ' * (root_indent + 1) + + f'value = {value_str}\n' + + ' ' * root_indent + + ')' + ) + return object_utils.maybe_markdown_quote(s, markdown) def maybe_ref(value: Any) -> Optional[Ref]: diff --git a/pyglove/core/typing/callable_signature.py b/pyglove/core/typing/callable_signature.py index 0f230a7..d905b43 100644 --- a/pyglove/core/typing/callable_signature.py +++ b/pyglove/core/typing/callable_signature.py @@ -155,7 +155,7 @@ def __eq__(self, other: Any) -> bool: self.varargs == other.varargs and self.varkw == other.varkw and self.return_value == other.return_value) - def format(self, *args, **kwargs) -> str: + def format(self, *args, markdown: bool = False, **kwargs) -> str: """Format current object.""" details = object_utils.kvlist_str([ ('', repr(self.id), ''), @@ -165,7 +165,9 @@ def format(self, *args, **kwargs) -> str: ('varargs', object_utils.format(self.varargs, **kwargs), 'None'), ('varkw', object_utils.format(self.varkw, **kwargs), 'None'), ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) @classmethod def from_schema( diff --git a/pyglove/core/typing/class_schema.py b/pyglove/core/typing/class_schema.py index bf533bd..894c21f 100644 --- a/pyglove/core/typing/class_schema.py +++ b/pyglove/core/typing/class_schema.py @@ -140,13 +140,15 @@ def cls(self) -> Type[Any]: ) return reference - def format(self, *args, **kwargs) -> str: + def format(self, *args, markdown: bool = False, **kwargs) -> str: """Format this object.""" details = object_utils.kvlist_str([ ('module', self.module.__name__, None), ('name', self.name, None), ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) def __eq__(self, other: Any) -> bool: """Operator==.""" @@ -705,11 +707,15 @@ def frozen(self) -> bool: """Returns True if current field's value is frozen.""" return self._value.frozen - def format(self, - compact: bool = False, - verbose: bool = True, - root_indent: int = 0, - **kwargs) -> str: + def format( + self, + compact: bool = False, + verbose: bool = True, + root_indent: int = 0, + *, + markdown: bool = False, + **kwargs, + ) -> str: """Format this field into a string.""" description = self._description if not verbose and self._description and len(self._description) > 20: @@ -733,7 +739,7 @@ def format(self, ('description', object_utils.quote_if_str(description), None), ('metadata', metadata, '{}') ]) - return f'Field({attr_str})' + return object_utils.maybe_markdown_quote(f'Field({attr_str})', markdown) def to_json(self, **kwargs: Any) -> Dict[str, Any]: return self.to_json_dict( @@ -1204,9 +1210,12 @@ def format( compact: bool = False, verbose: bool = True, root_indent: int = 0, + *, + markdown: bool = False, cls_name: Optional[str] = None, bracket_type: object_utils.BracketType = object_utils.BracketType.ROUND, - **kwargs) -> str: + **kwargs, + ) -> str: """Format current Schema into nicely printed string.""" if cls_name is None: cls_name = 'Schema' @@ -1245,7 +1254,7 @@ def _format_child(child): _indent(f'{f.key} = {_format_child(f.value)}', root_indent + 1)) s.append('\n') s.append(_indent(close_bracket, root_indent)) - return ''.join(s) + return object_utils.maybe_markdown_quote(''.join(s), markdown) def to_json(self, **kwargs) -> Dict[str, Any]: return self.to_json_dict( diff --git a/pyglove/core/typing/value_specs.py b/pyglove/core/typing/value_specs.py index 869ae28..c14c813 100644 --- a/pyglove/core/typing/value_specs.py +++ b/pyglove/core/typing/value_specs.py @@ -369,14 +369,16 @@ def __or__(self, other: typing.Any) -> bool: def __ror__(self, other: typing.Any) -> bool: return Union[other, self] - def format(self, **kwargs) -> str: + def format(self, *, markdown: bool = False, **kwargs) -> str: """Format this object.""" details = object_utils.kvlist_str([ ('default', object_utils.quote_if_str(self._default), MISSING_VALUE), ('noneable', self._is_noneable, False), ('frozen', self._frozen, False) ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) class PrimitiveType(ValueSpecBase): @@ -544,7 +546,7 @@ def _annotate(self) -> typing.Any: """Annotate with PyType annotation.""" return str - def format(self, **kwargs) -> str: + def format(self, *, markdown: bool = False, **kwargs) -> str: """Format this object.""" regex_pattern = self._regex.pattern if self._regex else None details = object_utils.kvlist_str([ @@ -553,7 +555,9 @@ def format(self, **kwargs) -> str: ('noneable', self._is_noneable, False), ('frozen', self._frozen, False) ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) def _eq(self, other: 'Str') -> bool: return self.regex == other.regex @@ -660,7 +664,7 @@ def _eq(self, other: 'Number') -> bool: return (self.min_value == other.min_value and self.max_value == other.max_value) - def format(self, **kwargs) -> str: + def format(self, *, markdown: bool = False, **kwargs) -> str: """Format this object.""" details = object_utils.kvlist_str([ ('default', self._default, MISSING_VALUE), @@ -669,7 +673,9 @@ def format(self, **kwargs) -> str: ('noneable', self._is_noneable, False), ('frozen', self._frozen, False) ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: return self.to_json_dict( @@ -900,14 +906,16 @@ def _annotate(self) -> typing.Any: def _eq(self, other: 'Enum') -> bool: return self.values == other.values - def format(self, **kwargs) -> str: + def format(self, *, markdown: bool = False, **kwargs) -> str: """Format this object.""" details = object_utils.kvlist_str([ ('default', object_utils.quote_if_str(self._default), MISSING_VALUE), ('values', self._values, None), ('frozen', self._frozen, False), ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: return self.to_json_dict( @@ -1090,13 +1098,17 @@ def _annotate(self) -> typing.Any: def _eq(self, other: 'List') -> bool: return self.element == other.element - def format(self, - compact: bool = False, - verbose: bool = True, - root_indent: int = 0, - hide_default_values: bool = True, - hide_missing_values: bool = True, - **kwargs) -> str: + def format( + self, + compact: bool = False, + verbose: bool = True, + root_indent: int = 0, + *, + markdown: bool = False, + hide_default_values: bool = True, + hide_missing_values: bool = True, + **kwargs, + ) -> str: """Format this object.""" details = object_utils.kvlist_str([ ('', self._element.value.format( @@ -1117,7 +1129,9 @@ def format(self, ('noneable', self._is_noneable, False), ('frozen', self._frozen, False), ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: return self.to_json_dict( @@ -1407,13 +1421,17 @@ def _eq(self, other: typing.Any) -> bool: and self.min_size == other.min_size and self.max_size == other.max_size) - def format(self, - compact: bool = False, - verbose: bool = True, - root_indent: int = 0, - hide_default_values: bool = True, - hide_missing_values: bool = True, - **kwargs) -> str: + def format( + self, + compact: bool = False, + verbose: bool = True, + root_indent: int = 0, + *, + markdown: bool = False, + hide_default_values: bool = True, + hide_missing_values: bool = True, + **kwargs, + ) -> str: """Format this object.""" if self.fixed_length: element_values = [f.value for f in self._elements] @@ -1435,7 +1453,7 @@ def format(self, ('noneable', self._is_noneable, False), ('frozen', self._frozen, False), ]) - return f'{self.__class__.__name__}({details})' + s = f'{self.__class__.__name__}({details})' else: details = object_utils.kvlist_str([ ('', object_utils.format( @@ -1456,7 +1474,8 @@ def format(self, ('max_size', self._max_size, None), ('noneable', self._is_noneable, False), ]) - return f'{self.__class__.__name__}({details})' + s = f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote(s, markdown) def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: if self.fixed_length: @@ -1642,11 +1661,15 @@ def _annotate(self) -> typing.Any: def _eq(self, other: 'Dict') -> bool: return self.schema == other.schema - def format(self, - compact: bool = False, - verbose: bool = True, - root_indent: int = 0, - **kwargs) -> str: + def format( + self, + compact: bool = False, + verbose: bool = True, + root_indent: int = 0, + *, + markdown: bool = False, + **kwargs, + ) -> str: """Format this object.""" schema_details = '' if self._schema: @@ -1663,7 +1686,9 @@ def format(self, ('noneable', self._is_noneable, False), ('frozen', self._frozen, False), ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: fields = dict( @@ -1828,13 +1853,17 @@ def _eq(self, other: 'Object') -> bool: return self.value_type == other.value_type return self.forward_refs == other.forward_refs - def format(self, - compact: bool = False, - verbose: bool = True, - root_indent: int = 0, - hide_default_values: bool = True, - hide_missing_values: bool = True, - **kwargs) -> str: + def format( + self, + compact: bool = False, + verbose: bool = True, + root_indent: int = 0, + *, + markdown: bool = False, + hide_default_values: bool = True, + hide_missing_values: bool = True, + **kwargs, + ) -> str: """Format this object.""" if self._forward_ref is not None: name = self._forward_ref.name @@ -1854,7 +1883,9 @@ def format(self, ('noneable', self._is_noneable, False), ('frozen', self._frozen, False), ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: return self.to_json_dict( @@ -2132,7 +2163,7 @@ def _eq(self, other: 'Callable') -> bool: and self._kw == other.kw and self._return_value == other.return_value) - def format(self, **kwargs) -> str: + def format(self, *, markdown: bool = False, **kwargs) -> str: """Format this spec.""" details = object_utils.kvlist_str([ ('args', object_utils.format(self._args, **kwargs), '[]'), @@ -2143,7 +2174,9 @@ def format(self, **kwargs) -> str: ('noneable', self._is_noneable, False), ('frozen', self._frozen, False) ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: return self.to_json_dict( @@ -2329,7 +2362,7 @@ def _eq(self, other: 'Type') -> bool: return self.type == other.type return self.forward_refs == other.forward_refs - def format(self, **kwargs): + def format(self, *, markdown: bool = False, **kwargs): """Format this object.""" details = object_utils.kvlist_str([ ('', self._expected_type, None), @@ -2337,7 +2370,9 @@ def format(self, **kwargs): ('noneable', self._is_noneable, False), ('frozen', self._frozen, False), ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: return self.to_json_dict( @@ -2605,11 +2640,15 @@ def _annotate(self) -> typing.Any: ]) return typing.Union[candidates] - def format(self, - compact: bool = False, - verbose: bool = True, - root_indent: int = 0, - **kwargs) -> str: + def format( + self, + compact: bool = False, + verbose: bool = True, + root_indent: int = 0, + *, + markdown: bool = False, + **kwargs, + ) -> str: """Format this object.""" list_wrap_threshold = kwargs.pop('list_wrap_threshold', 20) details = object_utils.kvlist_str([ @@ -2624,7 +2663,9 @@ def format(self, ('noneable', self._is_noneable, False), ('frozen', self._frozen, False), ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) def to_json(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: return self.to_json_dict( @@ -2759,7 +2800,7 @@ def is_compatible(self, other: ValueSpec) -> bool: """Any is compatible with any ValueSpec.""" return True - def format(self, **kwargs) -> str: + def format(self, *, markdown: bool = False, **kwargs) -> str: """Format this object.""" details = object_utils.kvlist_str([ ('default', object_utils.format(self._default, **kwargs), @@ -2767,7 +2808,9 @@ def format(self, **kwargs) -> str: ('frozen', self._frozen, False), ('annotation', self._annotation, MISSING_VALUE) ]) - return f'{self.__class__.__name__}({details})' + return object_utils.maybe_markdown_quote( + f'{self.__class__.__name__}({details})', markdown + ) def annotate(self, annotation: typing.Any) -> 'Any': """Set external type annotation."""