pyglove 0.4.5.dev20240319__py3-none-any.whl → 0.4.5.dev202501140808__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.
Files changed (145) hide show
  1. pyglove/core/__init__.py +54 -20
  2. pyglove/core/coding/__init__.py +42 -0
  3. pyglove/core/coding/errors.py +111 -0
  4. pyglove/core/coding/errors_test.py +98 -0
  5. pyglove/core/coding/execution.py +309 -0
  6. pyglove/core/coding/execution_test.py +333 -0
  7. pyglove/core/{object_utils/codegen.py → coding/function_generation.py} +10 -4
  8. pyglove/core/{object_utils/codegen_test.py → coding/function_generation_test.py} +5 -7
  9. pyglove/core/coding/parsing.py +153 -0
  10. pyglove/core/coding/parsing_test.py +150 -0
  11. pyglove/core/coding/permissions.py +100 -0
  12. pyglove/core/coding/permissions_test.py +93 -0
  13. pyglove/core/geno/base.py +54 -41
  14. pyglove/core/geno/base_test.py +2 -4
  15. pyglove/core/geno/categorical.py +37 -28
  16. pyglove/core/geno/custom.py +19 -16
  17. pyglove/core/geno/numerical.py +20 -17
  18. pyglove/core/geno/space.py +4 -5
  19. pyglove/core/hyper/base.py +6 -6
  20. pyglove/core/hyper/categorical.py +94 -55
  21. pyglove/core/hyper/custom.py +7 -7
  22. pyglove/core/hyper/custom_test.py +9 -10
  23. pyglove/core/hyper/derived.py +30 -22
  24. pyglove/core/hyper/derived_test.py +2 -4
  25. pyglove/core/hyper/dynamic_evaluation.py +5 -6
  26. pyglove/core/hyper/evolvable.py +57 -46
  27. pyglove/core/hyper/numerical.py +48 -24
  28. pyglove/core/hyper/numerical_test.py +9 -9
  29. pyglove/core/hyper/object_template.py +58 -46
  30. pyglove/core/io/__init__.py +1 -0
  31. pyglove/core/io/file_system.py +17 -7
  32. pyglove/core/io/file_system_test.py +2 -0
  33. pyglove/core/io/sequence.py +299 -0
  34. pyglove/core/io/sequence_test.py +124 -0
  35. pyglove/core/logging_test.py +0 -2
  36. pyglove/core/patching/object_factory.py +4 -4
  37. pyglove/core/patching/pattern_based.py +4 -4
  38. pyglove/core/patching/rule_based.py +17 -5
  39. pyglove/core/patching/rule_based_test.py +27 -4
  40. pyglove/core/symbolic/__init__.py +2 -7
  41. pyglove/core/symbolic/base.py +320 -183
  42. pyglove/core/symbolic/base_test.py +123 -19
  43. pyglove/core/symbolic/boilerplate.py +7 -13
  44. pyglove/core/symbolic/boilerplate_test.py +25 -23
  45. pyglove/core/symbolic/class_wrapper.py +48 -45
  46. pyglove/core/symbolic/class_wrapper_test.py +2 -2
  47. pyglove/core/symbolic/compounding.py +9 -15
  48. pyglove/core/symbolic/compounding_test.py +2 -4
  49. pyglove/core/symbolic/dict.py +154 -110
  50. pyglove/core/symbolic/dict_test.py +238 -130
  51. pyglove/core/symbolic/diff.py +199 -10
  52. pyglove/core/symbolic/diff_test.py +226 -0
  53. pyglove/core/symbolic/flags.py +1 -1
  54. pyglove/core/symbolic/functor.py +29 -26
  55. pyglove/core/symbolic/functor_test.py +102 -50
  56. pyglove/core/symbolic/inferred.py +2 -2
  57. pyglove/core/symbolic/list.py +81 -50
  58. pyglove/core/symbolic/list_test.py +119 -97
  59. pyglove/core/symbolic/object.py +225 -113
  60. pyglove/core/symbolic/object_test.py +320 -108
  61. pyglove/core/symbolic/origin.py +17 -14
  62. pyglove/core/symbolic/origin_test.py +4 -2
  63. pyglove/core/symbolic/pure_symbolic.py +4 -3
  64. pyglove/core/symbolic/ref.py +108 -21
  65. pyglove/core/symbolic/ref_test.py +93 -0
  66. pyglove/core/symbolic/symbolize_test.py +10 -2
  67. pyglove/core/tuning/local_backend.py +2 -2
  68. pyglove/core/tuning/protocols.py +3 -3
  69. pyglove/core/tuning/sample_test.py +3 -3
  70. pyglove/core/typing/__init__.py +14 -5
  71. pyglove/core/typing/annotation_conversion.py +43 -27
  72. pyglove/core/typing/annotation_conversion_test.py +23 -0
  73. pyglove/core/typing/callable_ext.py +241 -3
  74. pyglove/core/typing/callable_ext_test.py +255 -0
  75. pyglove/core/typing/callable_signature.py +510 -66
  76. pyglove/core/typing/callable_signature_test.py +619 -99
  77. pyglove/core/typing/class_schema.py +229 -154
  78. pyglove/core/typing/class_schema_test.py +149 -95
  79. pyglove/core/typing/custom_typing.py +5 -4
  80. pyglove/core/typing/inspect.py +63 -0
  81. pyglove/core/typing/inspect_test.py +39 -0
  82. pyglove/core/typing/key_specs.py +10 -11
  83. pyglove/core/typing/key_specs_test.py +7 -4
  84. pyglove/core/typing/type_conversion.py +4 -5
  85. pyglove/core/typing/type_conversion_test.py +12 -12
  86. pyglove/core/typing/typed_missing.py +6 -7
  87. pyglove/core/typing/typed_missing_test.py +7 -8
  88. pyglove/core/typing/value_specs.py +604 -362
  89. pyglove/core/typing/value_specs_test.py +328 -90
  90. pyglove/core/utils/__init__.py +164 -0
  91. pyglove/core/{object_utils → utils}/common_traits.py +3 -67
  92. pyglove/core/utils/common_traits_test.py +36 -0
  93. pyglove/core/{object_utils → utils}/docstr_utils.py +23 -0
  94. pyglove/core/{object_utils → utils}/docstr_utils_test.py +36 -4
  95. pyglove/core/{object_utils → utils}/error_utils.py +78 -9
  96. pyglove/core/{object_utils → utils}/error_utils_test.py +61 -5
  97. pyglove/core/utils/formatting.py +464 -0
  98. pyglove/core/utils/formatting_test.py +453 -0
  99. pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
  100. pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
  101. pyglove/core/{object_utils → utils}/json_conversion.py +177 -52
  102. pyglove/core/{object_utils → utils}/json_conversion_test.py +97 -16
  103. pyglove/core/{object_utils → utils}/missing.py +3 -3
  104. pyglove/core/{object_utils → utils}/missing_test.py +2 -4
  105. pyglove/core/utils/text_color.py +128 -0
  106. pyglove/core/utils/text_color_test.py +94 -0
  107. pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
  108. pyglove/core/utils/timing.py +236 -0
  109. pyglove/core/utils/timing_test.py +154 -0
  110. pyglove/core/{object_utils → utils}/value_location.py +275 -6
  111. pyglove/core/utils/value_location_test.py +707 -0
  112. pyglove/core/views/__init__.py +32 -0
  113. pyglove/core/views/base.py +804 -0
  114. pyglove/core/views/base_test.py +580 -0
  115. pyglove/core/views/html/__init__.py +27 -0
  116. pyglove/core/views/html/base.py +547 -0
  117. pyglove/core/views/html/base_test.py +830 -0
  118. pyglove/core/views/html/controls/__init__.py +35 -0
  119. pyglove/core/views/html/controls/base.py +275 -0
  120. pyglove/core/views/html/controls/label.py +207 -0
  121. pyglove/core/views/html/controls/label_test.py +157 -0
  122. pyglove/core/views/html/controls/progress_bar.py +183 -0
  123. pyglove/core/views/html/controls/progress_bar_test.py +97 -0
  124. pyglove/core/views/html/controls/tab.py +320 -0
  125. pyglove/core/views/html/controls/tab_test.py +87 -0
  126. pyglove/core/views/html/controls/tooltip.py +99 -0
  127. pyglove/core/views/html/controls/tooltip_test.py +99 -0
  128. pyglove/core/views/html/tree_view.py +1517 -0
  129. pyglove/core/views/html/tree_view_test.py +1461 -0
  130. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501140808.dist-info}/METADATA +18 -4
  131. pyglove-0.4.5.dev202501140808.dist-info/RECORD +214 -0
  132. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501140808.dist-info}/WHEEL +1 -1
  133. pyglove/core/object_utils/__init__.py +0 -154
  134. pyglove/core/object_utils/common_traits_test.py +0 -82
  135. pyglove/core/object_utils/formatting.py +0 -234
  136. pyglove/core/object_utils/formatting_test.py +0 -223
  137. pyglove/core/object_utils/value_location_test.py +0 -385
  138. pyglove/core/symbolic/schema_utils.py +0 -327
  139. pyglove/core/symbolic/schema_utils_test.py +0 -57
  140. pyglove/core/typing/class_schema_utils.py +0 -202
  141. pyglove/core/typing/class_schema_utils_test.py +0 -194
  142. pyglove-0.4.5.dev20240319.dist-info/RECORD +0 -185
  143. /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
  144. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501140808.dist-info}/LICENSE +0 -0
  145. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501140808.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()