brainstate 0.1.10__py2.py3-none-any.whl → 0.2.1__py2.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 (163) hide show
  1. brainstate/__init__.py +169 -58
  2. brainstate/_compatible_import.py +340 -148
  3. brainstate/_compatible_import_test.py +681 -0
  4. brainstate/_deprecation.py +210 -0
  5. brainstate/_deprecation_test.py +2319 -0
  6. brainstate/{util/error.py → _error.py} +45 -55
  7. brainstate/_state.py +1652 -1605
  8. brainstate/_state_test.py +52 -52
  9. brainstate/_utils.py +47 -47
  10. brainstate/environ.py +1495 -563
  11. brainstate/environ_test.py +1223 -62
  12. brainstate/graph/__init__.py +22 -29
  13. brainstate/graph/_node.py +240 -0
  14. brainstate/graph/_node_test.py +589 -0
  15. brainstate/graph/{_graph_operation.py → _operation.py} +1624 -1738
  16. brainstate/graph/_operation_test.py +1147 -0
  17. brainstate/mixin.py +1433 -365
  18. brainstate/mixin_test.py +1017 -77
  19. brainstate/nn/__init__.py +137 -135
  20. brainstate/nn/_activations.py +1100 -808
  21. brainstate/nn/_activations_test.py +354 -331
  22. brainstate/nn/_collective_ops.py +633 -514
  23. brainstate/nn/_collective_ops_test.py +774 -43
  24. brainstate/nn/_common.py +226 -178
  25. brainstate/nn/_common_test.py +154 -0
  26. brainstate/nn/_conv.py +2010 -501
  27. brainstate/nn/_conv_test.py +849 -238
  28. brainstate/nn/_delay.py +575 -588
  29. brainstate/nn/_delay_test.py +243 -238
  30. brainstate/nn/_dropout.py +618 -426
  31. brainstate/nn/_dropout_test.py +477 -100
  32. brainstate/nn/_dynamics.py +1267 -1343
  33. brainstate/nn/_dynamics_test.py +67 -78
  34. brainstate/nn/_elementwise.py +1298 -1119
  35. brainstate/nn/_elementwise_test.py +830 -169
  36. brainstate/nn/_embedding.py +408 -58
  37. brainstate/nn/_embedding_test.py +156 -0
  38. brainstate/nn/{_fixedprob.py → _event_fixedprob.py} +233 -239
  39. brainstate/nn/{_fixedprob_test.py → _event_fixedprob_test.py} +115 -114
  40. brainstate/nn/{_linear_mv.py → _event_linear.py} +83 -83
  41. brainstate/nn/{_linear_mv_test.py → _event_linear_test.py} +121 -120
  42. brainstate/nn/_exp_euler.py +254 -92
  43. brainstate/nn/_exp_euler_test.py +377 -35
  44. brainstate/nn/_linear.py +744 -424
  45. brainstate/nn/_linear_test.py +475 -107
  46. brainstate/nn/_metrics.py +1070 -0
  47. brainstate/nn/_metrics_test.py +611 -0
  48. brainstate/nn/_module.py +384 -377
  49. brainstate/nn/_module_test.py +40 -40
  50. brainstate/nn/_normalizations.py +1334 -975
  51. brainstate/nn/_normalizations_test.py +699 -73
  52. brainstate/nn/_paddings.py +1020 -0
  53. brainstate/nn/_paddings_test.py +723 -0
  54. brainstate/nn/_poolings.py +2239 -1177
  55. brainstate/nn/_poolings_test.py +953 -217
  56. brainstate/nn/{_rate_rnns.py → _rnns.py} +946 -554
  57. brainstate/nn/_rnns_test.py +593 -0
  58. brainstate/nn/_utils.py +216 -89
  59. brainstate/nn/_utils_test.py +402 -0
  60. brainstate/{init/_random_inits.py → nn/init.py} +809 -553
  61. brainstate/{init/_random_inits_test.py → nn/init_test.py} +180 -149
  62. brainstate/random/__init__.py +270 -24
  63. brainstate/random/_rand_funs.py +3938 -3616
  64. brainstate/random/_rand_funs_test.py +640 -567
  65. brainstate/random/_rand_seed.py +675 -210
  66. brainstate/random/_rand_seed_test.py +48 -48
  67. brainstate/random/_rand_state.py +1617 -1409
  68. brainstate/random/_rand_state_test.py +551 -0
  69. brainstate/transform/__init__.py +59 -0
  70. brainstate/transform/_ad_checkpoint.py +176 -0
  71. brainstate/{compile → transform}/_ad_checkpoint_test.py +49 -49
  72. brainstate/{augment → transform}/_autograd.py +1025 -778
  73. brainstate/{augment → transform}/_autograd_test.py +1289 -1289
  74. brainstate/transform/_conditions.py +316 -0
  75. brainstate/{compile → transform}/_conditions_test.py +220 -220
  76. brainstate/{compile → transform}/_error_if.py +94 -92
  77. brainstate/{compile → transform}/_error_if_test.py +52 -52
  78. brainstate/transform/_eval_shape.py +145 -0
  79. brainstate/{augment → transform}/_eval_shape_test.py +38 -38
  80. brainstate/{compile → transform}/_jit.py +399 -346
  81. brainstate/{compile → transform}/_jit_test.py +143 -143
  82. brainstate/{compile → transform}/_loop_collect_return.py +675 -536
  83. brainstate/{compile → transform}/_loop_collect_return_test.py +58 -58
  84. brainstate/{compile → transform}/_loop_no_collection.py +283 -184
  85. brainstate/{compile → transform}/_loop_no_collection_test.py +50 -50
  86. brainstate/transform/_make_jaxpr.py +2016 -0
  87. brainstate/transform/_make_jaxpr_test.py +1510 -0
  88. brainstate/transform/_mapping.py +529 -0
  89. brainstate/transform/_mapping_test.py +194 -0
  90. brainstate/{compile → transform}/_progress_bar.py +255 -202
  91. brainstate/{augment → transform}/_random.py +171 -151
  92. brainstate/{compile → transform}/_unvmap.py +256 -159
  93. brainstate/transform/_util.py +286 -0
  94. brainstate/typing.py +837 -304
  95. brainstate/typing_test.py +780 -0
  96. brainstate/util/__init__.py +27 -50
  97. brainstate/util/_others.py +1025 -0
  98. brainstate/util/_others_test.py +962 -0
  99. brainstate/util/_pretty_pytree.py +1301 -0
  100. brainstate/util/_pretty_pytree_test.py +675 -0
  101. brainstate/util/{pretty_repr.py → _pretty_repr.py} +462 -328
  102. brainstate/util/_pretty_repr_test.py +696 -0
  103. brainstate/util/filter.py +945 -469
  104. brainstate/util/filter_test.py +912 -0
  105. brainstate/util/struct.py +910 -523
  106. brainstate/util/struct_test.py +602 -0
  107. {brainstate-0.1.10.dist-info → brainstate-0.2.1.dist-info}/METADATA +108 -91
  108. brainstate-0.2.1.dist-info/RECORD +111 -0
  109. {brainstate-0.1.10.dist-info → brainstate-0.2.1.dist-info}/licenses/LICENSE +202 -202
  110. brainstate/augment/__init__.py +0 -30
  111. brainstate/augment/_eval_shape.py +0 -99
  112. brainstate/augment/_mapping.py +0 -1060
  113. brainstate/augment/_mapping_test.py +0 -597
  114. brainstate/compile/__init__.py +0 -38
  115. brainstate/compile/_ad_checkpoint.py +0 -204
  116. brainstate/compile/_conditions.py +0 -256
  117. brainstate/compile/_make_jaxpr.py +0 -888
  118. brainstate/compile/_make_jaxpr_test.py +0 -156
  119. brainstate/compile/_util.py +0 -147
  120. brainstate/functional/__init__.py +0 -27
  121. brainstate/graph/_graph_node.py +0 -244
  122. brainstate/graph/_graph_node_test.py +0 -73
  123. brainstate/graph/_graph_operation_test.py +0 -563
  124. brainstate/init/__init__.py +0 -26
  125. brainstate/init/_base.py +0 -52
  126. brainstate/init/_generic.py +0 -244
  127. brainstate/init/_regular_inits.py +0 -105
  128. brainstate/init/_regular_inits_test.py +0 -50
  129. brainstate/nn/_inputs.py +0 -608
  130. brainstate/nn/_ltp.py +0 -28
  131. brainstate/nn/_neuron.py +0 -705
  132. brainstate/nn/_neuron_test.py +0 -161
  133. brainstate/nn/_others.py +0 -46
  134. brainstate/nn/_projection.py +0 -486
  135. brainstate/nn/_rate_rnns_test.py +0 -63
  136. brainstate/nn/_readout.py +0 -209
  137. brainstate/nn/_readout_test.py +0 -53
  138. brainstate/nn/_stp.py +0 -236
  139. brainstate/nn/_synapse.py +0 -505
  140. brainstate/nn/_synapse_test.py +0 -131
  141. brainstate/nn/_synaptic_projection.py +0 -423
  142. brainstate/nn/_synouts.py +0 -162
  143. brainstate/nn/_synouts_test.py +0 -57
  144. brainstate/nn/metrics.py +0 -388
  145. brainstate/optim/__init__.py +0 -38
  146. brainstate/optim/_base.py +0 -64
  147. brainstate/optim/_lr_scheduler.py +0 -448
  148. brainstate/optim/_lr_scheduler_test.py +0 -50
  149. brainstate/optim/_optax_optimizer.py +0 -152
  150. brainstate/optim/_optax_optimizer_test.py +0 -53
  151. brainstate/optim/_sgd_optimizer.py +0 -1104
  152. brainstate/random/_random_for_unit.py +0 -52
  153. brainstate/surrogate.py +0 -1957
  154. brainstate/transform.py +0 -23
  155. brainstate/util/caller.py +0 -98
  156. brainstate/util/others.py +0 -540
  157. brainstate/util/pretty_pytree.py +0 -945
  158. brainstate/util/pretty_pytree_test.py +0 -159
  159. brainstate/util/pretty_table.py +0 -2954
  160. brainstate/util/scaling.py +0 -258
  161. brainstate-0.1.10.dist-info/RECORD +0 -130
  162. {brainstate-0.1.10.dist-info → brainstate-0.2.1.dist-info}/WHEEL +0 -0
  163. {brainstate-0.1.10.dist-info → brainstate-0.2.1.dist-info}/top_level.txt +0 -0
@@ -1,328 +1,462 @@
1
- # The file is adapted from the Flax library (https://github.com/google/flax).
2
- # The credit should go to the Flax authors.
3
- #
4
- # Copyright 2024 The Flax Authors.
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
-
18
- import dataclasses
19
- import threading
20
- from abc import ABC, abstractmethod
21
- from functools import partial
22
- from typing import Any, Iterator, Mapping, TypeVar, Union, Callable, Optional
23
-
24
- __all__ = [
25
- 'yield_unique_pretty_repr_items',
26
- 'PrettyType',
27
- 'PrettyAttr',
28
- 'PrettyRepr',
29
- 'PrettyMapping',
30
- 'MappingReprMixin',
31
- ]
32
-
33
- A = TypeVar('A')
34
- B = TypeVar('B')
35
-
36
-
37
- @dataclasses.dataclass
38
- class PrettyType:
39
- """
40
- Configuration for pretty representation of objects.
41
- """
42
- type: Union[str, type]
43
- start: str = '('
44
- end: str = ')'
45
- value_sep: str = '='
46
- elem_indent: str = ' '
47
- empty_repr: str = ''
48
-
49
-
50
- @dataclasses.dataclass
51
- class PrettyAttr:
52
- """
53
- Configuration for pretty representation of attributes.
54
- """
55
- key: str
56
- value: Union[str, Any]
57
- start: str = ''
58
- end: str = ''
59
-
60
-
61
- class PrettyRepr(ABC):
62
- """
63
- Interface for pretty representation of objects.
64
-
65
- Example::
66
-
67
- >>> class MyObject(PrettyRepr):
68
- >>> def __pretty_repr__(self):
69
- >>> yield PrettyType(type='MyObject', start='{', end='}')
70
- >>> yield PrettyAttr('key', self.key)
71
- >>> yield PrettyAttr('value', self.value)
72
-
73
- """
74
- __slots__ = ()
75
-
76
- @abstractmethod
77
- def __pretty_repr__(self) -> Iterator[Union[PrettyType, PrettyAttr]]:
78
- raise NotImplementedError
79
-
80
- def __repr__(self) -> str:
81
- # repr the individual object with the pretty representation
82
- return pretty_repr_object(self)
83
-
84
-
85
- def pretty_repr_elem(obj: PrettyType, elem: Any) -> str:
86
- """
87
- Constructs a string representation of a single element within a pretty representation.
88
-
89
- This function takes a `PrettyType` object and an element, which must be an instance
90
- of `PrettyAttr`, and generates a formatted string that represents the element. The
91
- formatting is based on the configuration provided by the `PrettyType` object.
92
-
93
- Parameters
94
- ----------
95
- obj : PrettyType
96
- The configuration object that defines how the element should be formatted.
97
- It includes details such as indentation, separators, and surrounding characters.
98
- elem : Any
99
- The element to be represented. It must be an instance of `PrettyAttr`, which
100
- contains the key and value to be formatted.
101
-
102
- Returns
103
- -------
104
- str
105
- A string that represents the element in a formatted manner, adhering to the
106
- configuration specified by the `PrettyType` object.
107
-
108
- Raises
109
- ------
110
- TypeError
111
- If the provided element is not an instance of `PrettyAttr`.
112
- """
113
- if not isinstance(elem, PrettyAttr):
114
- raise TypeError(f'Item must be Elem, got {type(elem).__name__}')
115
-
116
- value = elem.value if isinstance(elem.value, str) else repr(elem.value)
117
- value = value.replace('\n', '\n' + obj.elem_indent)
118
-
119
- return f'{obj.elem_indent}{elem.start}{elem.key}{obj.value_sep}{value}{elem.end}'
120
-
121
-
122
- def pretty_repr_object(obj: PrettyRepr) -> str:
123
- """
124
- Generates a pretty string representation of an object that implements the PrettyRepr interface.
125
-
126
- This function utilizes the __pretty_repr__ method of the PrettyRepr interface to obtain
127
- a structured representation of the object, which includes both the type and attributes
128
- of the object in a human-readable format.
129
-
130
- Parameters
131
- ----------
132
- obj : PrettyRepr
133
- The object for which the pretty representation is to be generated. The object must
134
- implement the PrettyRepr interface.
135
-
136
- Returns
137
- -------
138
- str
139
- A string that represents the object in a pretty format, including its type and attributes.
140
- The format is determined by the PrettyType and PrettyAttr instances yielded by the
141
- __pretty_repr__ method of the object.
142
-
143
- Raises
144
- ------
145
- TypeError
146
- If the provided object does not implement the PrettyRepr interface or if the first item
147
- yielded by the __pretty_repr__ method is not an instance of PrettyType.
148
- """
149
- if not isinstance(obj, PrettyRepr):
150
- raise TypeError(f'Object {obj!r} is not representable')
151
-
152
- iterator = obj.__pretty_repr__()
153
- obj_repr = next(iterator)
154
-
155
- # repr object
156
- if not isinstance(obj_repr, PrettyType):
157
- raise TypeError(f'First item must be PrettyType, got {type(obj_repr).__name__}')
158
-
159
- # repr attributes
160
- elem_reprs = tuple(map(partial(pretty_repr_elem, obj_repr), iterator))
161
- elems = ',\n'.join(elem_reprs)
162
- if elems:
163
- elems = '\n' + elems + '\n'
164
- else:
165
- elems = obj_repr.empty_repr
166
-
167
- # repr object type
168
- type_repr = obj_repr.type if isinstance(obj_repr.type, str) else obj_repr.type.__name__
169
-
170
- # return repr
171
- return f'{type_repr}{obj_repr.start}{elems}{obj_repr.end}'
172
-
173
-
174
- class MappingReprMixin(Mapping[A, B]):
175
- """
176
- Mapping mixin for pretty representation.
177
- """
178
-
179
- def __pretty_repr__(self):
180
- yield PrettyType(type='', value_sep=': ', start='{', end='}')
181
-
182
- for key, value in self.items():
183
- yield PrettyAttr(repr(key), value)
184
-
185
-
186
- @dataclasses.dataclass(repr=False)
187
- class PrettyMapping(PrettyRepr):
188
- """
189
- Pretty representation of a mapping.
190
- """
191
- mapping: Mapping
192
- type_name: str = ''
193
-
194
- def __pretty_repr__(self):
195
- yield PrettyType(type=self.type_name, value_sep=': ', start='{', end='}')
196
-
197
- for key, value in self.mapping.items():
198
- yield PrettyAttr(repr(key), value)
199
-
200
-
201
- @dataclasses.dataclass
202
- class PrettyReprContext(threading.local):
203
- """
204
- A thread-local context for managing the state of pretty representation.
205
-
206
- This class is used to keep track of objects that have been seen during
207
- the generation of pretty representations, preventing infinite recursion
208
- in cases of circular references.
209
-
210
- Attributes
211
- ----------
212
- seen_modules_repr : dict[int, Any] | None
213
- A dictionary mapping object IDs to objects that have been seen
214
- during the pretty representation process. This is used to avoid
215
- representing the same object multiple times.
216
- """
217
- seen_modules_repr: dict[int, Any] | None = None
218
-
219
-
220
- CONTEXT = PrettyReprContext()
221
-
222
-
223
- def _default_repr_object(node):
224
- """
225
- Generates a default pretty representation for an object.
226
-
227
- This function yields a `PrettyType` instance that represents the type
228
- of the given object. It is used as a default method for representing
229
- objects when no custom representation function is provided.
230
-
231
- Parameters
232
- ----------
233
- node : Any
234
- The object for which the pretty representation is to be generated.
235
-
236
- Yields
237
- ------
238
- PrettyType
239
- An instance of `PrettyType` that contains the type information of
240
- the object.
241
- """
242
- yield PrettyType(type=type(node))
243
-
244
-
245
- def _default_repr_attr(node):
246
- """
247
- Generates a default pretty representation for the attributes of an object.
248
-
249
- This function iterates over the attributes of the given object and yields
250
- a `PrettyAttr` instance for each attribute that does not start with an
251
- underscore. The `PrettyAttr` instances contain the attribute name and its
252
- string representation.
253
-
254
- Parameters
255
- ----------
256
- node : Any
257
- The object whose attributes are to be represented.
258
-
259
- Yields
260
- ------
261
- PrettyAttr
262
- An instance of `PrettyAttr` for each non-private attribute of the object,
263
- containing the attribute name and its string representation.
264
- """
265
- for name, value in vars(node).items():
266
- if name.startswith('_'):
267
- continue
268
- yield PrettyAttr(name, repr(value))
269
-
270
-
271
- def yield_unique_pretty_repr_items(
272
- node,
273
- repr_object: Optional[Callable] = None,
274
- repr_attr: Optional[Callable] = None
275
- ):
276
- """
277
- Generates a pretty representation of an object while avoiding duplicate representations.
278
-
279
- This function is designed to yield a structured representation of an object,
280
- using custom or default methods for representing the object itself and its attributes.
281
- It ensures that each object is only represented once to prevent infinite recursion
282
- in cases of circular references.
283
-
284
- Parameters:
285
- node : Any
286
- The object to be represented.
287
- repr_object : Optional[Callable], optional
288
- A callable that yields the representation of the object itself.
289
- If not provided, a default representation function is used.
290
- repr_attr : Optional[Callable], optional
291
- A callable that yields the representation of the object's attributes.
292
- If not provided, a default attribute representation function is used.
293
-
294
- Yields:
295
- Union[PrettyType, PrettyAttr]
296
- The pretty representation of the object and its attributes,
297
- avoiding duplicates by tracking seen objects.
298
- """
299
- if repr_object is None:
300
- repr_object = _default_repr_object
301
- if repr_attr is None:
302
- repr_attr = _default_repr_attr
303
-
304
- if CONTEXT.seen_modules_repr is None:
305
- # CONTEXT.seen_modules_repr = set()
306
- CONTEXT.seen_modules_repr = dict()
307
- clear_seen = True
308
- else:
309
- clear_seen = False
310
-
311
- # Avoid infinite recursion
312
- if id(node) in CONTEXT.seen_modules_repr:
313
- yield PrettyType(type=type(node), empty_repr='...')
314
- return
315
-
316
- # repr object
317
- yield from repr_object(node)
318
-
319
- # Add to seen modules
320
- # CONTEXT.seen_modules_repr.add(id(node))
321
- CONTEXT.seen_modules_repr[id(node)] = node
322
-
323
- try:
324
- # repr attributes
325
- yield from repr_attr(node)
326
- finally:
327
- if clear_seen:
328
- CONTEXT.seen_modules_repr = None
1
+ # The file is adapted from the Flax library (https://github.com/google/flax).
2
+ # The credit should go to the Flax authors.
3
+ #
4
+ # Copyright 2024 The Flax Authors.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ """
19
+ Pretty representation utilities for creating human-readable string representations.
20
+
21
+ This module provides utilities for creating customizable pretty representations of
22
+ objects, with support for nested structures and circular reference detection.
23
+ """
24
+
25
+ import dataclasses
26
+ import threading
27
+ from abc import ABC, abstractmethod
28
+ from functools import partial
29
+ from typing import Any, Iterator, Mapping, TypeVar, Union, Callable, Optional
30
+
31
+ __all__ = [
32
+ 'yield_unique_pretty_repr_items',
33
+ 'PrettyType',
34
+ 'PrettyAttr',
35
+ 'PrettyRepr',
36
+ 'PrettyMapping',
37
+ 'MappingReprMixin',
38
+ ]
39
+
40
+ A = TypeVar('A')
41
+ B = TypeVar('B')
42
+
43
+
44
+ @dataclasses.dataclass
45
+ class PrettyType:
46
+ """
47
+ Configuration for pretty representation of objects.
48
+
49
+ Attributes
50
+ ----------
51
+ type : Union[str, type]
52
+ The type name or type object to display.
53
+ start : str, default='('
54
+ The opening delimiter for the representation.
55
+ end : str, default=')'
56
+ The closing delimiter for the representation.
57
+ value_sep : str, default='='
58
+ The separator between keys and values.
59
+ elem_indent : str, default=' '
60
+ The indentation for nested elements.
61
+ empty_repr : str, default=''
62
+ The representation for empty objects.
63
+ """
64
+ type: Union[str, type]
65
+ start: str = '('
66
+ end: str = ')'
67
+ value_sep: str = '='
68
+ elem_indent: str = ' '
69
+ empty_repr: str = ''
70
+
71
+
72
+ @dataclasses.dataclass
73
+ class PrettyAttr:
74
+ """
75
+ Configuration for pretty representation of attributes.
76
+
77
+ Attributes
78
+ ----------
79
+ key : str
80
+ The attribute name or key.
81
+ value : Union[str, Any]
82
+ The attribute value.
83
+ start : str, default=''
84
+ Optional prefix for the attribute.
85
+ end : str, default=''
86
+ Optional suffix for the attribute.
87
+ """
88
+ key: str
89
+ value: Union[str, Any]
90
+ start: str = ''
91
+ end: str = ''
92
+
93
+
94
+ class PrettyRepr(ABC):
95
+ """
96
+ Interface for pretty representation of objects.
97
+
98
+ This abstract base class provides a framework for creating custom
99
+ pretty representations of objects by yielding PrettyType and PrettyAttr
100
+ instances.
101
+
102
+ Examples
103
+ --------
104
+ .. code-block:: python
105
+
106
+ >>> class MyObject(PrettyRepr):
107
+ ... def __init__(self, key, value):
108
+ ... self.key = key
109
+ ... self.value = value
110
+ ...
111
+ ... def __pretty_repr__(self):
112
+ ... yield PrettyType(type='MyObject', start='{', end='}')
113
+ ... yield PrettyAttr('key', self.key)
114
+ ... yield PrettyAttr('value', self.value)
115
+ ...
116
+ >>> obj = MyObject('foo', 42)
117
+ >>> print(obj)
118
+ MyObject{
119
+ key=foo,
120
+ value=42
121
+ }
122
+ """
123
+ __slots__ = ()
124
+
125
+ @abstractmethod
126
+ def __pretty_repr__(self) -> Iterator[Union[PrettyType, PrettyAttr]]:
127
+ """
128
+ Generate the pretty representation of the object.
129
+
130
+ Yields
131
+ ------
132
+ Union[PrettyType, PrettyAttr]
133
+ First yield should be PrettyType, followed by PrettyAttr instances.
134
+ """
135
+ raise NotImplementedError
136
+
137
+ def __repr__(self) -> str:
138
+ """
139
+ Generate string representation using pretty representation.
140
+
141
+ Returns
142
+ -------
143
+ str
144
+ The formatted string representation of the object.
145
+ """
146
+ return pretty_repr_object(self)
147
+
148
+
149
+ def pretty_repr_elem(obj: PrettyType, elem: Any) -> str:
150
+ """
151
+ Constructs a string representation of a single element within a pretty representation.
152
+
153
+ This function takes a `PrettyType` object and an element, which must be an instance
154
+ of `PrettyAttr`, and generates a formatted string that represents the element. The
155
+ formatting is based on the configuration provided by the `PrettyType` object.
156
+
157
+ Parameters
158
+ ----------
159
+ obj : PrettyType
160
+ The configuration object that defines how the element should be formatted.
161
+ It includes details such as indentation, separators, and surrounding characters.
162
+ elem : Any
163
+ The element to be represented. It must be an instance of `PrettyAttr`, which
164
+ contains the key and value to be formatted.
165
+
166
+ Returns
167
+ -------
168
+ str
169
+ A string that represents the element in a formatted manner, adhering to the
170
+ configuration specified by the `PrettyType` object.
171
+
172
+ Raises
173
+ ------
174
+ TypeError
175
+ If the provided element is not an instance of `PrettyAttr`.
176
+ """
177
+ if not isinstance(elem, PrettyAttr):
178
+ raise TypeError(f'Item must be Elem, got {type(elem).__name__}')
179
+
180
+ value = elem.value if isinstance(elem.value, str) else repr(elem.value)
181
+ value = value.replace('\n', '\n' + obj.elem_indent)
182
+
183
+ return f'{obj.elem_indent}{elem.start}{elem.key}{obj.value_sep}{value}{elem.end}'
184
+
185
+
186
+ def pretty_repr_object(obj: PrettyRepr) -> str:
187
+ """
188
+ Generates a pretty string representation of an object that implements the PrettyRepr interface.
189
+
190
+ This function utilizes the __pretty_repr__ method of the PrettyRepr interface to obtain
191
+ a structured representation of the object, which includes both the type and attributes
192
+ of the object in a human-readable format.
193
+
194
+ Parameters
195
+ ----------
196
+ obj : PrettyRepr
197
+ The object for which the pretty representation is to be generated. The object must
198
+ implement the PrettyRepr interface.
199
+
200
+ Returns
201
+ -------
202
+ str
203
+ A string that represents the object in a pretty format, including its type and attributes.
204
+ The format is determined by the PrettyType and PrettyAttr instances yielded by the
205
+ __pretty_repr__ method of the object.
206
+
207
+ Raises
208
+ ------
209
+ TypeError
210
+ If the provided object does not implement the PrettyRepr interface or if the first item
211
+ yielded by the __pretty_repr__ method is not an instance of PrettyType.
212
+ """
213
+ if not isinstance(obj, PrettyRepr):
214
+ raise TypeError(f'Object {obj!r} is not representable')
215
+
216
+ iterator = obj.__pretty_repr__()
217
+ obj_repr = next(iterator)
218
+
219
+ # repr object
220
+ if not isinstance(obj_repr, PrettyType):
221
+ raise TypeError(f'First item must be PrettyType, got {type(obj_repr).__name__}')
222
+
223
+ # repr attributes
224
+ elem_reprs = tuple(map(partial(pretty_repr_elem, obj_repr), iterator))
225
+ elems = ',\n'.join(elem_reprs)
226
+ if elems:
227
+ elems = '\n' + elems + '\n'
228
+ else:
229
+ elems = obj_repr.empty_repr
230
+
231
+ # repr object type
232
+ type_repr = obj_repr.type if isinstance(obj_repr.type, str) else obj_repr.type.__name__
233
+
234
+ # return repr
235
+ return f'{type_repr}{obj_repr.start}{elems}{obj_repr.end}'
236
+
237
+
238
+ class MappingReprMixin(Mapping[A, B]):
239
+ """
240
+ Mapping mixin for pretty representation.
241
+
242
+ This mixin provides a default pretty representation for mapping-like objects.
243
+
244
+ Examples
245
+ --------
246
+ .. code-block:: python
247
+
248
+ >>> class MyMapping(dict, MappingReprMixin):
249
+ ... pass
250
+ ...
251
+ >>> m = MyMapping({'a': 1, 'b': 2})
252
+ >>> print(m)
253
+ {
254
+ 'a': 1,
255
+ 'b': 2
256
+ }
257
+ """
258
+
259
+ def __pretty_repr__(self) -> Iterator[Union[PrettyType, PrettyAttr]]:
260
+ """
261
+ Generate pretty representation for mapping.
262
+
263
+ Yields
264
+ ------
265
+ Union[PrettyType, PrettyAttr]
266
+ PrettyType followed by PrettyAttr for each key-value pair.
267
+ """
268
+ yield PrettyType(type='', value_sep=': ', start='{', end='}')
269
+
270
+ for key, value in self.items():
271
+ yield PrettyAttr(repr(key), value)
272
+
273
+
274
+ @dataclasses.dataclass(repr=False)
275
+ class PrettyMapping(PrettyRepr):
276
+ """
277
+ Pretty representation of a mapping.
278
+
279
+ Attributes
280
+ ----------
281
+ mapping : Mapping
282
+ The mapping to represent.
283
+ type_name : str, default=''
284
+ Optional type name to display.
285
+
286
+ Examples
287
+ --------
288
+ .. code-block:: python
289
+
290
+ >>> m = PrettyMapping({'a': 1, 'b': 2}, type_name='MyDict')
291
+ >>> print(m)
292
+ MyDict{
293
+ 'a': 1,
294
+ 'b': 2
295
+ }
296
+ """
297
+ mapping: Mapping
298
+ type_name: str = ''
299
+
300
+ def __pretty_repr__(self) -> Iterator[Union[PrettyType, PrettyAttr]]:
301
+ """
302
+ Generate pretty representation for the mapping.
303
+
304
+ Yields
305
+ ------
306
+ Union[PrettyType, PrettyAttr]
307
+ PrettyType followed by PrettyAttr for each key-value pair.
308
+ """
309
+ yield PrettyType(type=self.type_name, value_sep=': ', start='{', end='}')
310
+
311
+ for key, value in self.mapping.items():
312
+ yield PrettyAttr(repr(key), value)
313
+
314
+
315
+ @dataclasses.dataclass
316
+ class PrettyReprContext(threading.local):
317
+ """
318
+ A thread-local context for managing the state of pretty representation.
319
+
320
+ This class is used to keep track of objects that have been seen during
321
+ the generation of pretty representations, preventing infinite recursion
322
+ in cases of circular references.
323
+
324
+ Attributes
325
+ ----------
326
+ seen_modules_repr : dict[int, Any] | None
327
+ A dictionary mapping object IDs to objects that have been seen
328
+ during the pretty representation process. This is used to avoid
329
+ representing the same object multiple times.
330
+ """
331
+ seen_modules_repr: dict[int, Any] | None = None
332
+
333
+
334
+ CONTEXT = PrettyReprContext()
335
+
336
+
337
+ def _default_repr_object(node: Any) -> Iterator[PrettyType]:
338
+ """
339
+ Generate a default pretty representation for an object.
340
+
341
+ This function yields a `PrettyType` instance that represents the type
342
+ of the given object. It is used as a default method for representing
343
+ objects when no custom representation function is provided.
344
+
345
+ Parameters
346
+ ----------
347
+ node : Any
348
+ The object for which the pretty representation is to be generated.
349
+
350
+ Yields
351
+ ------
352
+ PrettyType
353
+ An instance of `PrettyType` that contains the type information of
354
+ the object.
355
+ """
356
+ yield PrettyType(type=type(node))
357
+
358
+
359
+ def _default_repr_attr(node: Any) -> Iterator[PrettyAttr]:
360
+ """
361
+ Generate a default pretty representation for the attributes of an object.
362
+
363
+ This function iterates over the attributes of the given object and yields
364
+ a `PrettyAttr` instance for each attribute that does not start with an
365
+ underscore. The `PrettyAttr` instances contain the attribute name and its
366
+ string representation.
367
+
368
+ Parameters
369
+ ----------
370
+ node : Any
371
+ The object whose attributes are to be represented.
372
+
373
+ Yields
374
+ ------
375
+ PrettyAttr
376
+ An instance of `PrettyAttr` for each non-private attribute of the object,
377
+ containing the attribute name and its string representation.
378
+ """
379
+ for name, value in vars(node).items():
380
+ if name.startswith('_'):
381
+ continue
382
+ yield PrettyAttr(name, repr(value))
383
+
384
+
385
+ def yield_unique_pretty_repr_items(
386
+ node: Any,
387
+ repr_object: Optional[Callable] = None,
388
+ repr_attr: Optional[Callable] = None
389
+ ) -> Iterator[Union[PrettyType, PrettyAttr]]:
390
+ """
391
+ Generate a pretty representation of an object while avoiding duplicate representations.
392
+
393
+ This function yields a structured representation of an object, using custom or default
394
+ methods for representing the object itself and its attributes. It ensures that each
395
+ object is only represented once to prevent infinite recursion in cases of circular
396
+ references.
397
+
398
+ Parameters
399
+ ----------
400
+ node : Any
401
+ The object to be represented.
402
+ repr_object : Optional[Callable], optional
403
+ A callable that yields the representation of the object itself.
404
+ If not provided, a default representation function is used.
405
+ repr_attr : Optional[Callable], optional
406
+ A callable that yields the representation of the object's attributes.
407
+ If not provided, a default attribute representation function is used.
408
+
409
+ Yields
410
+ ------
411
+ Union[PrettyType, PrettyAttr]
412
+ The pretty representation of the object and its attributes,
413
+ avoiding duplicates by tracking seen objects.
414
+
415
+ Examples
416
+ --------
417
+ .. code-block:: python
418
+
419
+ >>> class Node:
420
+ ... def __init__(self, value, next=None):
421
+ ... self.value = value
422
+ ... self.next = next
423
+ ...
424
+ >>> # Create circular reference
425
+ >>> node1 = Node(1)
426
+ >>> node2 = Node(2, node1)
427
+ >>> node1.next = node2
428
+ ...
429
+ >>> # This will handle circular reference gracefully
430
+ >>> for item in yield_unique_pretty_repr_items(node1):
431
+ ... print(item)
432
+ """
433
+ if repr_object is None:
434
+ repr_object = _default_repr_object
435
+ if repr_attr is None:
436
+ repr_attr = _default_repr_attr
437
+
438
+ if CONTEXT.seen_modules_repr is None:
439
+ # CONTEXT.seen_modules_repr = set()
440
+ CONTEXT.seen_modules_repr = dict()
441
+ clear_seen = True
442
+ else:
443
+ clear_seen = False
444
+
445
+ # Avoid infinite recursion
446
+ if id(node) in CONTEXT.seen_modules_repr:
447
+ yield PrettyType(type=type(node), empty_repr='...')
448
+ return
449
+
450
+ # repr object
451
+ yield from repr_object(node)
452
+
453
+ # Add to seen modules
454
+ # CONTEXT.seen_modules_repr.add(id(node))
455
+ CONTEXT.seen_modules_repr[id(node)] = node
456
+
457
+ try:
458
+ # repr attributes
459
+ yield from repr_attr(node)
460
+ finally:
461
+ if clear_seen:
462
+ CONTEXT.seen_modules_repr = None