pyglove 0.4.5.dev20240319__py3-none-any.whl → 0.4.5.dev202501132210__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyglove/core/__init__.py +54 -20
- pyglove/core/coding/__init__.py +42 -0
- pyglove/core/coding/errors.py +111 -0
- pyglove/core/coding/errors_test.py +98 -0
- pyglove/core/coding/execution.py +309 -0
- pyglove/core/coding/execution_test.py +333 -0
- pyglove/core/{object_utils/codegen.py → coding/function_generation.py} +10 -4
- pyglove/core/{object_utils/codegen_test.py → coding/function_generation_test.py} +5 -7
- pyglove/core/coding/parsing.py +153 -0
- pyglove/core/coding/parsing_test.py +150 -0
- pyglove/core/coding/permissions.py +100 -0
- pyglove/core/coding/permissions_test.py +93 -0
- pyglove/core/geno/base.py +54 -41
- pyglove/core/geno/base_test.py +2 -4
- pyglove/core/geno/categorical.py +37 -28
- pyglove/core/geno/custom.py +19 -16
- pyglove/core/geno/numerical.py +20 -17
- pyglove/core/geno/space.py +4 -5
- pyglove/core/hyper/base.py +6 -6
- pyglove/core/hyper/categorical.py +94 -55
- pyglove/core/hyper/custom.py +7 -7
- pyglove/core/hyper/custom_test.py +9 -10
- pyglove/core/hyper/derived.py +30 -22
- pyglove/core/hyper/derived_test.py +2 -4
- pyglove/core/hyper/dynamic_evaluation.py +5 -6
- pyglove/core/hyper/evolvable.py +57 -46
- pyglove/core/hyper/numerical.py +48 -24
- pyglove/core/hyper/numerical_test.py +9 -9
- pyglove/core/hyper/object_template.py +58 -46
- pyglove/core/io/__init__.py +1 -0
- pyglove/core/io/file_system.py +17 -7
- pyglove/core/io/file_system_test.py +2 -0
- pyglove/core/io/sequence.py +299 -0
- pyglove/core/io/sequence_test.py +124 -0
- pyglove/core/logging_test.py +0 -2
- pyglove/core/patching/object_factory.py +4 -4
- pyglove/core/patching/pattern_based.py +4 -4
- pyglove/core/patching/rule_based.py +17 -5
- pyglove/core/patching/rule_based_test.py +27 -4
- pyglove/core/symbolic/__init__.py +2 -7
- pyglove/core/symbolic/base.py +320 -183
- pyglove/core/symbolic/base_test.py +123 -19
- pyglove/core/symbolic/boilerplate.py +7 -13
- pyglove/core/symbolic/boilerplate_test.py +25 -23
- pyglove/core/symbolic/class_wrapper.py +48 -45
- pyglove/core/symbolic/class_wrapper_test.py +2 -2
- pyglove/core/symbolic/compounding.py +9 -15
- pyglove/core/symbolic/compounding_test.py +2 -4
- pyglove/core/symbolic/dict.py +154 -110
- pyglove/core/symbolic/dict_test.py +238 -130
- pyglove/core/symbolic/diff.py +199 -10
- pyglove/core/symbolic/diff_test.py +226 -0
- pyglove/core/symbolic/flags.py +1 -1
- pyglove/core/symbolic/functor.py +29 -26
- pyglove/core/symbolic/functor_test.py +102 -50
- pyglove/core/symbolic/inferred.py +2 -2
- pyglove/core/symbolic/list.py +81 -50
- pyglove/core/symbolic/list_test.py +119 -97
- pyglove/core/symbolic/object.py +225 -113
- pyglove/core/symbolic/object_test.py +320 -108
- pyglove/core/symbolic/origin.py +17 -14
- pyglove/core/symbolic/origin_test.py +4 -2
- pyglove/core/symbolic/pure_symbolic.py +4 -3
- pyglove/core/symbolic/ref.py +108 -21
- pyglove/core/symbolic/ref_test.py +93 -0
- pyglove/core/symbolic/symbolize_test.py +10 -2
- pyglove/core/tuning/local_backend.py +2 -2
- pyglove/core/tuning/protocols.py +3 -3
- pyglove/core/tuning/sample_test.py +3 -3
- pyglove/core/typing/__init__.py +14 -5
- pyglove/core/typing/annotation_conversion.py +43 -27
- pyglove/core/typing/annotation_conversion_test.py +23 -0
- pyglove/core/typing/callable_ext.py +241 -3
- pyglove/core/typing/callable_ext_test.py +255 -0
- pyglove/core/typing/callable_signature.py +510 -66
- pyglove/core/typing/callable_signature_test.py +619 -99
- pyglove/core/typing/class_schema.py +229 -154
- pyglove/core/typing/class_schema_test.py +149 -95
- pyglove/core/typing/custom_typing.py +5 -4
- pyglove/core/typing/inspect.py +63 -0
- pyglove/core/typing/inspect_test.py +39 -0
- pyglove/core/typing/key_specs.py +10 -11
- pyglove/core/typing/key_specs_test.py +7 -4
- pyglove/core/typing/type_conversion.py +4 -5
- pyglove/core/typing/type_conversion_test.py +12 -12
- pyglove/core/typing/typed_missing.py +6 -7
- pyglove/core/typing/typed_missing_test.py +7 -8
- pyglove/core/typing/value_specs.py +604 -362
- pyglove/core/typing/value_specs_test.py +328 -90
- pyglove/core/utils/__init__.py +164 -0
- pyglove/core/{object_utils → utils}/common_traits.py +3 -67
- pyglove/core/utils/common_traits_test.py +36 -0
- pyglove/core/{object_utils → utils}/docstr_utils.py +23 -0
- pyglove/core/{object_utils → utils}/docstr_utils_test.py +36 -4
- pyglove/core/{object_utils → utils}/error_utils.py +78 -9
- pyglove/core/{object_utils → utils}/error_utils_test.py +61 -5
- pyglove/core/utils/formatting.py +464 -0
- pyglove/core/utils/formatting_test.py +453 -0
- pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
- pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
- pyglove/core/{object_utils → utils}/json_conversion.py +177 -52
- pyglove/core/{object_utils → utils}/json_conversion_test.py +97 -16
- pyglove/core/{object_utils → utils}/missing.py +3 -3
- pyglove/core/{object_utils → utils}/missing_test.py +2 -4
- pyglove/core/utils/text_color.py +128 -0
- pyglove/core/utils/text_color_test.py +94 -0
- pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
- pyglove/core/utils/timing.py +236 -0
- pyglove/core/utils/timing_test.py +154 -0
- pyglove/core/{object_utils → utils}/value_location.py +275 -6
- pyglove/core/utils/value_location_test.py +707 -0
- pyglove/core/views/__init__.py +32 -0
- pyglove/core/views/base.py +804 -0
- pyglove/core/views/base_test.py +580 -0
- pyglove/core/views/html/__init__.py +27 -0
- pyglove/core/views/html/base.py +547 -0
- pyglove/core/views/html/base_test.py +830 -0
- pyglove/core/views/html/controls/__init__.py +35 -0
- pyglove/core/views/html/controls/base.py +275 -0
- pyglove/core/views/html/controls/label.py +207 -0
- pyglove/core/views/html/controls/label_test.py +157 -0
- pyglove/core/views/html/controls/progress_bar.py +183 -0
- pyglove/core/views/html/controls/progress_bar_test.py +97 -0
- pyglove/core/views/html/controls/tab.py +320 -0
- pyglove/core/views/html/controls/tab_test.py +87 -0
- pyglove/core/views/html/controls/tooltip.py +99 -0
- pyglove/core/views/html/controls/tooltip_test.py +99 -0
- pyglove/core/views/html/tree_view.py +1517 -0
- pyglove/core/views/html/tree_view_test.py +1461 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/METADATA +18 -4
- pyglove-0.4.5.dev202501132210.dist-info/RECORD +214 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/WHEEL +1 -1
- pyglove/core/object_utils/__init__.py +0 -154
- pyglove/core/object_utils/common_traits_test.py +0 -82
- pyglove/core/object_utils/formatting.py +0 -234
- pyglove/core/object_utils/formatting_test.py +0 -223
- pyglove/core/object_utils/value_location_test.py +0 -385
- pyglove/core/symbolic/schema_utils.py +0 -327
- pyglove/core/symbolic/schema_utils_test.py +0 -57
- pyglove/core/typing/class_schema_utils.py +0 -202
- pyglove/core/typing/class_schema_utils_test.py +0 -194
- pyglove-0.4.5.dev20240319.dist-info/RECORD +0 -185
- /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/LICENSE +0 -0
- {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/top_level.txt +0 -0
@@ -1,234 +0,0 @@
|
|
1
|
-
# Copyright 2022 The PyGlove Authors
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
"""Utilities for formatting objects."""
|
15
|
-
|
16
|
-
import enum
|
17
|
-
import sys
|
18
|
-
from typing import Any, List, Optional, Sequence, Set, Tuple
|
19
|
-
from pyglove.core.object_utils import common_traits
|
20
|
-
from pyglove.core.object_utils.value_location import KeyPath
|
21
|
-
|
22
|
-
|
23
|
-
def kvlist_str(
|
24
|
-
kvlist: List[Tuple[str, Any, Any]],
|
25
|
-
compact: bool = True,
|
26
|
-
verbose: bool = False,
|
27
|
-
root_indent: int = 0) -> str:
|
28
|
-
"""Formats a list key/value pairs into a comma delimited string.
|
29
|
-
|
30
|
-
Args:
|
31
|
-
kvlist: List of tuples in format of
|
32
|
-
(key, value, default_value or a tuple of default values)
|
33
|
-
compact: If True, format value in kvlist in compact form.
|
34
|
-
verbose: If True, format value in kvlist in verbose.
|
35
|
-
root_indent: The indent should be applied for values in kvlist if they are
|
36
|
-
multi-line.
|
37
|
-
|
38
|
-
Returns:
|
39
|
-
A formatted string from a list of key/value pairs delimited by comma.
|
40
|
-
"""
|
41
|
-
s = []
|
42
|
-
is_first = True
|
43
|
-
for k, v, d in kvlist:
|
44
|
-
if isinstance(d, tuple):
|
45
|
-
include_pair = True
|
46
|
-
for sd in d:
|
47
|
-
if sd == v:
|
48
|
-
include_pair = False
|
49
|
-
break
|
50
|
-
else:
|
51
|
-
include_pair = v != d
|
52
|
-
if include_pair:
|
53
|
-
if not is_first:
|
54
|
-
s.append(', ')
|
55
|
-
if not isinstance(v, str):
|
56
|
-
v = format(v, compact=compact, verbose=verbose, root_indent=root_indent)
|
57
|
-
if k:
|
58
|
-
s.append(f'{k}={v}')
|
59
|
-
else:
|
60
|
-
s.append(str(v))
|
61
|
-
is_first = False
|
62
|
-
return ''.join(s)
|
63
|
-
|
64
|
-
|
65
|
-
def quote_if_str(value: Any) -> Any:
|
66
|
-
"""Quotes the value if it is a str."""
|
67
|
-
if isinstance(value, str):
|
68
|
-
return repr(value)
|
69
|
-
return value
|
70
|
-
|
71
|
-
|
72
|
-
def comma_delimited_str(value_list: Sequence[Any]) -> str:
|
73
|
-
"""Gets comma delimited string."""
|
74
|
-
return ', '.join(str(quote_if_str(v)) for v in value_list)
|
75
|
-
|
76
|
-
|
77
|
-
def auto_plural(
|
78
|
-
number: int, singular: str, plural: Optional[str] = None) -> str:
|
79
|
-
"""Use singular form if number is 1, otherwise use plural form."""
|
80
|
-
if plural is None:
|
81
|
-
plural = singular + 's'
|
82
|
-
return singular if number == 1 else plural
|
83
|
-
|
84
|
-
|
85
|
-
def message_on_path(
|
86
|
-
message: str, path: KeyPath) -> str:
|
87
|
-
"""Formats a message that is associated with a `KeyPath`."""
|
88
|
-
if path is None:
|
89
|
-
return message
|
90
|
-
return f'{message} (path={path})'
|
91
|
-
|
92
|
-
|
93
|
-
class BracketType(enum.IntEnum):
|
94
|
-
"""Bracket types used for complex type formatting."""
|
95
|
-
# Round bracket.
|
96
|
-
ROUND = 0
|
97
|
-
|
98
|
-
# Square bracket.
|
99
|
-
SQUARE = 1
|
100
|
-
|
101
|
-
# Curly bracket.
|
102
|
-
CURLY = 2
|
103
|
-
|
104
|
-
|
105
|
-
_BRACKET_CHARS = [
|
106
|
-
('(', ')'),
|
107
|
-
('[', ']'),
|
108
|
-
('{', '}'),
|
109
|
-
]
|
110
|
-
|
111
|
-
|
112
|
-
def bracket_chars(bracket_type: BracketType) -> Tuple[str, str]:
|
113
|
-
"""Gets bracket character."""
|
114
|
-
return _BRACKET_CHARS[int(bracket_type)]
|
115
|
-
|
116
|
-
|
117
|
-
def format( # pylint: disable=redefined-builtin
|
118
|
-
value: Any,
|
119
|
-
compact: bool = False,
|
120
|
-
verbose: bool = True,
|
121
|
-
root_indent: int = 0,
|
122
|
-
list_wrap_threshold: int = 80,
|
123
|
-
strip_object_id: bool = False,
|
124
|
-
include_keys: Optional[Set[str]] = None,
|
125
|
-
exclude_keys: Optional[Set[str]] = None,
|
126
|
-
markdown: bool = False,
|
127
|
-
**kwargs,
|
128
|
-
) -> str:
|
129
|
-
"""Formats a (maybe) hierarchical value with flags.
|
130
|
-
|
131
|
-
Args:
|
132
|
-
value: The value to format.
|
133
|
-
compact: If True, this object will be formatted into a single line.
|
134
|
-
verbose: If True, this object will be formatted with verbosity. Subclasses
|
135
|
-
should define `verbosity` on their own.
|
136
|
-
root_indent: The start indent level for this object if the output is a
|
137
|
-
multi-line string.
|
138
|
-
list_wrap_threshold: A threshold in number of characters for wrapping a list
|
139
|
-
value in a single line.
|
140
|
-
strip_object_id: If True, format object as '<class-name>(...)' other than
|
141
|
-
'object at <address>'.
|
142
|
-
include_keys: A set of keys to include from the top-level dict or object.
|
143
|
-
exclude_keys: A set of keys to exclude from the top-level dict or object.
|
144
|
-
Applicable only when `include_keys` is set to None.
|
145
|
-
markdown: If True, use markdown notion to quote the formatted object.
|
146
|
-
**kwargs: Keyword arguments that will be passed through unto child
|
147
|
-
``Formattable`` objects.
|
148
|
-
|
149
|
-
Returns:
|
150
|
-
A string representation for `value`.
|
151
|
-
"""
|
152
|
-
|
153
|
-
exclude_keys = exclude_keys or set()
|
154
|
-
|
155
|
-
def _indent(text, indent: int) -> str:
|
156
|
-
return ' ' * 2 * indent + text
|
157
|
-
|
158
|
-
def _should_include_key(key: str) -> bool:
|
159
|
-
if include_keys:
|
160
|
-
return key in include_keys
|
161
|
-
return key not in exclude_keys
|
162
|
-
|
163
|
-
def _format_child(v):
|
164
|
-
return format(v, compact=compact, verbose=verbose,
|
165
|
-
root_indent=root_indent + 1,
|
166
|
-
list_wrap_threshold=list_wrap_threshold,
|
167
|
-
strip_object_id=strip_object_id,
|
168
|
-
**kwargs)
|
169
|
-
|
170
|
-
if isinstance(value, common_traits.Formattable):
|
171
|
-
return value.format(compact=compact,
|
172
|
-
verbose=verbose,
|
173
|
-
root_indent=root_indent,
|
174
|
-
list_wrap_threshold=list_wrap_threshold,
|
175
|
-
strip_object_id=strip_object_id,
|
176
|
-
include_keys=include_keys,
|
177
|
-
exclude_keys=exclude_keys,
|
178
|
-
**kwargs)
|
179
|
-
elif isinstance(value, (list, tuple)):
|
180
|
-
# Always try compact representation if length is not too long.
|
181
|
-
open_bracket, close_bracket = bracket_chars(
|
182
|
-
BracketType.SQUARE if isinstance(value, list) else BracketType.ROUND)
|
183
|
-
s = [open_bracket]
|
184
|
-
s.append(', '.join([_format_child(elem) for elem in value]))
|
185
|
-
s.append(close_bracket)
|
186
|
-
s = [''.join(s)]
|
187
|
-
if not compact and len(s[-1]) > list_wrap_threshold:
|
188
|
-
s = [f'{open_bracket}\n']
|
189
|
-
s.append(',\n'.join([
|
190
|
-
_indent(_format_child(elem), root_indent + 1)
|
191
|
-
for elem in value
|
192
|
-
]))
|
193
|
-
s.append('\n')
|
194
|
-
s.append(_indent(close_bracket, root_indent))
|
195
|
-
elif isinstance(value, dict):
|
196
|
-
if compact or not value:
|
197
|
-
s = ['{']
|
198
|
-
s.append(', '.join([
|
199
|
-
f'{k!r}: {_format_child(v)}'
|
200
|
-
for k, v in value.items() if _should_include_key(k)
|
201
|
-
]))
|
202
|
-
s.append('}')
|
203
|
-
else:
|
204
|
-
s = ['{\n']
|
205
|
-
s.append(',\n'.join([
|
206
|
-
_indent(f'{k!r}: {_format_child(v)}', root_indent + 1)
|
207
|
-
for k, v in value.items() if _should_include_key(k)
|
208
|
-
]))
|
209
|
-
s.append('\n')
|
210
|
-
s.append(_indent('}', root_indent))
|
211
|
-
else:
|
212
|
-
if isinstance(value, str):
|
213
|
-
s = [repr(value)]
|
214
|
-
else:
|
215
|
-
s = [repr(value) if compact else str(value)]
|
216
|
-
if strip_object_id and 'object at 0x' in s[-1]:
|
217
|
-
s = [f'{value.__class__.__name__}(...)']
|
218
|
-
return maybe_markdown_quote(''.join(s), markdown)
|
219
|
-
|
220
|
-
|
221
|
-
def maybe_markdown_quote(s: str, markdown: bool = True) -> str:
|
222
|
-
"""Maybe quote the formatted string with markdown."""
|
223
|
-
if not markdown:
|
224
|
-
return s
|
225
|
-
if '\n' not in s:
|
226
|
-
return f'`{s}`'
|
227
|
-
else:
|
228
|
-
return f'```\n{s}\n```'
|
229
|
-
|
230
|
-
|
231
|
-
def printv(v: Any, **kwargs):
|
232
|
-
"""Prints formatted value."""
|
233
|
-
fs = kwargs.pop('file', sys.stdout)
|
234
|
-
print(format(v, **kwargs), file=fs)
|
@@ -1,223 +0,0 @@
|
|
1
|
-
# Copyright 2022 The PyGlove Authors
|
2
|
-
#
|
3
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
-
# you may not use this file except in compliance with the License.
|
5
|
-
# You may obtain a copy of the License at
|
6
|
-
#
|
7
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
-
#
|
9
|
-
# Unless required by applicable law or agreed to in writing, software
|
10
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
-
# See the License for the specific language governing permissions and
|
13
|
-
# limitations under the License.
|
14
|
-
"""Tests for pyglove.object_utils.formatting."""
|
15
|
-
import inspect
|
16
|
-
import unittest
|
17
|
-
|
18
|
-
from pyglove.core.object_utils import common_traits
|
19
|
-
from pyglove.core.object_utils import formatting
|
20
|
-
|
21
|
-
|
22
|
-
class StringHelperTest(unittest.TestCase):
|
23
|
-
"""Tests for string helper methods in formatting."""
|
24
|
-
|
25
|
-
def test_kvlist_str(self):
|
26
|
-
self.assertEqual(
|
27
|
-
formatting.kvlist_str([
|
28
|
-
('', 'foo', None),
|
29
|
-
('a', 1, None),
|
30
|
-
('b', 'str', (None, 'str')),
|
31
|
-
('c', True, False),
|
32
|
-
]), 'foo, a=1, c=True')
|
33
|
-
|
34
|
-
def test_quote_if_str(self):
|
35
|
-
self.assertEqual(formatting.quote_if_str(1), 1)
|
36
|
-
self.assertEqual(formatting.quote_if_str('foo'), '\'foo\'')
|
37
|
-
self.assertEqual(formatting.quote_if_str('foo\'s\na'), '"foo\'s\\na"')
|
38
|
-
|
39
|
-
def test_message_on_path(self):
|
40
|
-
self.assertEqual(formatting.message_on_path('hi.', None), 'hi.')
|
41
|
-
self.assertEqual(
|
42
|
-
formatting.message_on_path('hi.', formatting.KeyPath()),
|
43
|
-
'hi. (path=)')
|
44
|
-
|
45
|
-
def test_comma_delimited_str(self):
|
46
|
-
self.assertEqual(
|
47
|
-
formatting.comma_delimited_str([1, 2, 'abc']), '1, 2, \'abc\'')
|
48
|
-
|
49
|
-
def test_auto_plural(self):
|
50
|
-
self.assertEqual(formatting.auto_plural(2, 'number'), 'numbers')
|
51
|
-
self.assertEqual(formatting.auto_plural(2, 'was', 'were'), 'were')
|
52
|
-
|
53
|
-
|
54
|
-
class FormatTest(unittest.TestCase):
|
55
|
-
"""Tests for formatting.format."""
|
56
|
-
|
57
|
-
def test_formattable(self):
|
58
|
-
|
59
|
-
class A(common_traits.Formattable):
|
60
|
-
|
61
|
-
def format(self, compact=True, **kwargs):
|
62
|
-
if compact:
|
63
|
-
return 'A()'
|
64
|
-
else:
|
65
|
-
return 'A(...)'
|
66
|
-
|
67
|
-
self.assertEqual(str(A()), 'A(...)')
|
68
|
-
self.assertEqual(repr(A()), 'A()')
|
69
|
-
|
70
|
-
def test_simple_types(self):
|
71
|
-
self.assertEqual(formatting.format(True, compact=True), 'True')
|
72
|
-
self.assertEqual(formatting.format(1, compact=True), '1')
|
73
|
-
self.assertEqual(formatting.format(1.0, compact=True), '1.0')
|
74
|
-
self.assertEqual(formatting.format('foo', compact=True), '\'foo\'')
|
75
|
-
self.assertEqual(
|
76
|
-
formatting.format('foo\'s\na', compact=True), '"foo\'s\\na"')
|
77
|
-
|
78
|
-
# Compact=False has no impact on simple types.
|
79
|
-
self.assertEqual(formatting.format(True, compact=False), 'True')
|
80
|
-
self.assertEqual(formatting.format(1, compact=False), '1')
|
81
|
-
self.assertEqual(formatting.format(1.0, compact=False), '1.0')
|
82
|
-
self.assertEqual(formatting.format('foo', compact=False), '\'foo\'')
|
83
|
-
self.assertEqual(
|
84
|
-
formatting.format('foo\'s\na', compact=False), '"foo\'s\\na"')
|
85
|
-
|
86
|
-
# Verbose has no impact on simple types.
|
87
|
-
self.assertEqual(formatting.format(True, verbose=True), 'True')
|
88
|
-
self.assertEqual(formatting.format(1, verbose=True), '1')
|
89
|
-
self.assertEqual(formatting.format(1.0, verbose=True), '1.0')
|
90
|
-
self.assertEqual(formatting.format('foo', verbose=True), '\'foo\'')
|
91
|
-
self.assertEqual(
|
92
|
-
formatting.format('foo\'s\na', verbose=True), '"foo\'s\\na"')
|
93
|
-
|
94
|
-
# Root indent has no impact on simple types.
|
95
|
-
self.assertEqual(formatting.format(True, root_indent=4), 'True')
|
96
|
-
self.assertEqual(formatting.format(1, root_indent=4), '1')
|
97
|
-
self.assertEqual(formatting.format(1.0, root_indent=4), '1.0')
|
98
|
-
self.assertEqual(formatting.format('foo', root_indent=4), '\'foo\'')
|
99
|
-
self.assertEqual(
|
100
|
-
formatting.format('foo\'s\na', root_indent=4), '"foo\'s\\na"')
|
101
|
-
|
102
|
-
def test_complex_types(self):
|
103
|
-
|
104
|
-
class CustomFormattable(common_traits.Formattable):
|
105
|
-
"""Custom formattable."""
|
106
|
-
|
107
|
-
def format(self, custom_param=None, **kwargs):
|
108
|
-
return f'CustomFormattable({custom_param})'
|
109
|
-
|
110
|
-
class A:
|
111
|
-
pass
|
112
|
-
|
113
|
-
self.assertEqual(
|
114
|
-
formatting.format(
|
115
|
-
{
|
116
|
-
'a': CustomFormattable(),
|
117
|
-
'b': {
|
118
|
-
'c': [1, 2, 3],
|
119
|
-
'd': ['foo', 'bar\na', 3, 4, 5]
|
120
|
-
}
|
121
|
-
},
|
122
|
-
compact=True,
|
123
|
-
custom_param='foo'),
|
124
|
-
"{'a': CustomFormattable(foo), 'b': {'c': [1, 2, 3], "
|
125
|
-
"'d': ['foo', 'bar\\na', 3, 4, 5]}}")
|
126
|
-
|
127
|
-
self.assertEqual(
|
128
|
-
formatting.format(
|
129
|
-
{
|
130
|
-
'a': A(),
|
131
|
-
'b': {
|
132
|
-
'c': [1, 2, 3],
|
133
|
-
'd': ['foo', 'bar\na', 3, 4, 5]
|
134
|
-
}
|
135
|
-
},
|
136
|
-
compact=False,
|
137
|
-
list_wrap_threshold=15,
|
138
|
-
strip_object_id=True),
|
139
|
-
inspect.cleandoc("""{
|
140
|
-
'a': A(...),
|
141
|
-
'b': {
|
142
|
-
'c': [1, 2, 3],
|
143
|
-
'd': [
|
144
|
-
'foo',
|
145
|
-
'bar\\na',
|
146
|
-
3,
|
147
|
-
4,
|
148
|
-
5
|
149
|
-
]
|
150
|
-
}
|
151
|
-
}"""))
|
152
|
-
|
153
|
-
def test_include_exclude_keys(self):
|
154
|
-
"""Test format with excluded keys."""
|
155
|
-
|
156
|
-
class A:
|
157
|
-
pass
|
158
|
-
|
159
|
-
class B(common_traits.Formattable):
|
160
|
-
"""Custom formattable."""
|
161
|
-
|
162
|
-
def format(
|
163
|
-
self, custom_param=None,
|
164
|
-
include_keys=None, exclude_keys=None, **kwargs):
|
165
|
-
exclude_keys = exclude_keys or set()
|
166
|
-
kv = dict(a=1, b=2, c=3)
|
167
|
-
def _should_include(k):
|
168
|
-
if include_keys:
|
169
|
-
return k in include_keys
|
170
|
-
return k not in exclude_keys
|
171
|
-
kv_pairs = [(k, v, None) for k, v in kv.items() if _should_include(k)]
|
172
|
-
return f'B({formatting.kvlist_str(kv_pairs, compact=True)})'
|
173
|
-
|
174
|
-
self.assertEqual(
|
175
|
-
formatting.format(B(), compact=False, include_keys=set(['a', 'c'])),
|
176
|
-
'B(a=1, c=3)')
|
177
|
-
self.assertEqual(
|
178
|
-
formatting.format(B(), compact=False, exclude_keys=set(['a', 'c'])),
|
179
|
-
'B(b=2)')
|
180
|
-
self.assertEqual(
|
181
|
-
formatting.format(
|
182
|
-
{
|
183
|
-
'a': A(),
|
184
|
-
'b': B(),
|
185
|
-
'c': {
|
186
|
-
'd': [1, 2, 3],
|
187
|
-
}
|
188
|
-
},
|
189
|
-
compact=False,
|
190
|
-
list_wrap_threshold=15,
|
191
|
-
strip_object_id=True,
|
192
|
-
# 'a' should be removed, but 'b.a', 'c.d' should be kept as they are
|
193
|
-
# not at the top level.
|
194
|
-
exclude_keys=set(['a', 'd'])),
|
195
|
-
inspect.cleandoc("""{
|
196
|
-
'b': B(a=1, b=2, c=3),
|
197
|
-
'c': {
|
198
|
-
'd': [1, 2, 3]
|
199
|
-
}
|
200
|
-
}"""))
|
201
|
-
|
202
|
-
def test_markdown(self):
|
203
|
-
self.assertEqual(
|
204
|
-
formatting.format([1], compact=True, markdown=True), '`[1]`'
|
205
|
-
)
|
206
|
-
self.assertEqual(
|
207
|
-
formatting.format(
|
208
|
-
[1, 2, 3], list_wrap_threshold=5, compact=False, markdown=True
|
209
|
-
),
|
210
|
-
inspect.cleandoc("""
|
211
|
-
```
|
212
|
-
[
|
213
|
-
1,
|
214
|
-
2,
|
215
|
-
3
|
216
|
-
]
|
217
|
-
```
|
218
|
-
"""),
|
219
|
-
)
|
220
|
-
|
221
|
-
|
222
|
-
if __name__ == '__main__':
|
223
|
-
unittest.main()
|