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.
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.dev202501132210.dist-info}/METADATA +18 -4
  131. pyglove-0.4.5.dev202501132210.dist-info/RECORD +214 -0
  132. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.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.dev202501132210.dist-info}/LICENSE +0 -0
  145. {pyglove-0.4.5.dev20240319.dist-info → pyglove-0.4.5.dev202501132210.dist-info}/top_level.txt +0 -0
@@ -1,327 +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 handling schema for symbolic classes."""
15
-
16
- import types
17
- from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
18
-
19
- from pyglove.core import object_utils
20
- from pyglove.core import typing as pg_typing
21
- from pyglove.core.symbolic import base
22
- from pyglove.core.symbolic import flags
23
-
24
-
25
- def augment_schema(
26
- schema: pg_typing.Schema,
27
- fields: List[
28
- Union[
29
- pg_typing.Field,
30
- Tuple[Union[str, pg_typing.KeySpec], pg_typing.ValueSpec, str],
31
- Tuple[Union[str, pg_typing.KeySpec], pg_typing.ValueSpec, str, Any],
32
- ]
33
- ],
34
- extend: bool = True,
35
- *,
36
- init_arg_list: Optional[Sequence[str]] = None,
37
- metadata: Optional[Dict[str, Any]] = None,
38
- description: Optional[str] = None,
39
- ) -> pg_typing.Schema:
40
- """Gets the augmented schema from class with extra fields and metadata.
41
-
42
- Args:
43
- schema: The original schema.
44
- fields: A list of `pg.typing.Field` or equivalent tuple representation as
45
- (<key>, <value-spec>, [description], [metadata-objects]). `key` should be
46
- a string. `value-spec` should be pg_typing.ValueSpec classes or
47
- equivalent, e.g. primitive values which will be converted to ValueSpec
48
- implementation according to its type and used as its default value.
49
- `description` is optional only when field overrides a field from its
50
- parent class. `metadata-objects` is an optional list of any type, which
51
- can be used to generate code according to the schema.
52
- extend: If True, extend existing schema using `fields`. Otherwise replace
53
- existing schema with a new schema created from `fields`.
54
- init_arg_list: An optional sequence of strings as the positional argument
55
- list for `__init__`. This is helpful when symbolic attributes are
56
- inherited from base classes or the user want to change its order. If not
57
- provided, the `init_arg_list` will be automatically generated from
58
- symbolic attributes defined from ``pg.members`` in their declaration
59
- order, from the base classes to the subclass.
60
- metadata: Optional dict of user objects as class-level metadata which will
61
- be attached to class schema.
62
- description: An optional description to set.
63
-
64
- Returns:
65
- The augmented schema (new copy).
66
- """
67
- metadata = metadata or {}
68
- if init_arg_list is None:
69
- init_arg_list = metadata.get('init_arg_list', None)
70
- metadata = object_utils.merge([schema.metadata, metadata])
71
-
72
- # NOTE(daiyip): Consider to inherit `init_arg_list` from the parent when
73
- # there is no new field.
74
- metadata['init_arg_list'] = init_arg_list
75
-
76
- return formalize_schema(
77
- pg_typing.create_schema(
78
- fields=fields,
79
- name=schema.name,
80
- base_schema_list=[schema] if extend else [],
81
- description=description or schema.description,
82
- allow_nonconst_keys=True,
83
- metadata=metadata,
84
- )
85
- )
86
-
87
-
88
- def update_schema(
89
- cls,
90
- fields: List[
91
- Union[
92
- pg_typing.Field,
93
- Tuple[Union[str, pg_typing.KeySpec], pg_typing.ValueSpec, str],
94
- Tuple[Union[str, pg_typing.KeySpec], pg_typing.ValueSpec, str, Any],
95
- ]
96
- ],
97
- extend: bool = True,
98
- *,
99
- init_arg_list: Optional[Sequence[str]] = None,
100
- metadata: Optional[Dict[str, Any]] = None,
101
- description: Optional[str] = None,
102
- serialization_key: Optional[str] = None,
103
- additional_keys: Optional[List[str]] = None,
104
- add_to_registry: bool = True,
105
- ) -> None:
106
- """Updates the schema for a ``pg.Object`` subclass.
107
-
108
- This function allows the user to update the symbolic fields associated
109
- with a symbolic class. It was intended to support meta-programming
110
- scenarios in which symbolic fields are dynamically generated.
111
-
112
- Example::
113
-
114
- class A(pg.Object):
115
- pass
116
-
117
- # Add symbolic field 'x' to class A.
118
- pg.symbolic.update_schema(A, [
119
- ('x', schema.Int())
120
- ])
121
-
122
- # B inherits the symbolic field 'x' from A.
123
- class B(A):
124
- pass
125
-
126
- # Wipe out the symbolic field 'x' from B.
127
- pg.symbolic.update_schema(B, [], extend=False)
128
-
129
- See also: :func:`pyglove.members`, :func:`pyglove.functor` and
130
- :func:`pyglove.symbolize`.
131
-
132
- Args:
133
- cls: A symbolic Object subclass.
134
- fields: A list of `pg.typing.Field` or equivalent tuple representation as
135
- (<key>, <value-spec>, [description], [metadata-objects]). `key` should be
136
- a string. `value-spec` should be pg_typing.ValueSpec classes or
137
- equivalent, e.g. primitive values which will be converted to ValueSpec
138
- implementation according to its type and used as its default value.
139
- `description` is optional only when field overrides a field from its
140
- parent class. `metadata-objects` is an optional list of any type, which
141
- can be used to generate code according to the schema.
142
- extend: If True, extend existing schema using `fields`. Otherwise replace
143
- existing schema with a new schema created from `fields`.
144
- init_arg_list: An optional sequence of strings as the positional argument
145
- list for `__init__`. This is helpful when symbolic attributes are
146
- inherited from base classes or the user want to change its order. If not
147
- provided, the `init_arg_list` will be automatically generated from
148
- symbolic attributes defined from ``pg.members`` in their declaration
149
- order, from the base classes to the subclass.
150
- metadata: Optional dict of user objects as class-level metadata which will
151
- be attached to class schema.
152
- description: An optional description to set.
153
- serialization_key: An optional string to be used as the serialization key
154
- for the class during `sym_jsonify`. If None, `cls.__type_name__` will be
155
- used. This is introduced for scenarios when we want to relocate a class,
156
- before the downstream can recognize the new location, we need the class to
157
- serialize it using previous key.
158
- additional_keys: An optional list of strings as additional keys to
159
- deserialize an object of the registered class. This can be useful when we
160
- need to relocate or rename the registered class while being able to load
161
- existing serialized JSON values.
162
- add_to_registry: If True, the newly created functor class will be added to
163
- the registry for deserialization.
164
- """
165
- cls.apply_schema(
166
- augment_schema(
167
- cls.__schema__,
168
- fields=fields,
169
- extend=extend,
170
- init_arg_list=init_arg_list,
171
- metadata=metadata,
172
- description=description,
173
- )
174
- )
175
-
176
- if add_to_registry:
177
- cls.register_for_deserialization(serialization_key, additional_keys)
178
-
179
-
180
- def function_schema(
181
- func: types.FunctionType,
182
- args: Optional[
183
- List[
184
- Union[
185
- Tuple[Tuple[str, pg_typing.KeySpec], pg_typing.ValueSpec, str],
186
- Tuple[
187
- Tuple[str, pg_typing.KeySpec], pg_typing.ValueSpec, str, Any
188
- ],
189
- ]
190
- ]
191
- ] = None, # pylint: disable=bad-continuation
192
- returns: Optional[pg_typing.ValueSpec] = None,
193
- *,
194
- auto_typing: bool = True,
195
- auto_doc: bool = True,
196
- ) -> pg_typing.Schema:
197
- """Returns the schema from the signature of a function."""
198
- args_docstr = None
199
- description = None
200
- if auto_doc:
201
- docstr = object_utils.docstr(func)
202
- if docstr:
203
- args_docstr = docstr.args
204
- description = schema_description_from_docstr(docstr)
205
-
206
- signature = pg_typing.get_signature(func, auto_typing=auto_typing)
207
- arg_fields = pg_typing.get_arg_fields(signature, args, args_docstr)
208
-
209
- if returns is not None and pg_typing.MISSING_VALUE != returns.default:
210
- raise ValueError('return value spec should not have default value.')
211
- returns = returns or signature.return_value
212
-
213
- # Generate init_arg_list from signature.
214
- init_arg_list = [arg.name for arg in signature.args]
215
- if signature.varargs:
216
- init_arg_list.append(f'*{signature.varargs.name}')
217
-
218
- return formalize_schema(
219
- pg_typing.create_schema(
220
- fields=arg_fields,
221
- name=f'{func.__module__}.{func.__name__}',
222
- metadata={
223
- 'init_arg_list': init_arg_list,
224
- 'varargs_name': getattr(signature.varargs, 'name', None),
225
- 'varkw_name': getattr(signature.varkw, 'name', None),
226
- 'returns': returns,
227
- },
228
- description=description,
229
- allow_nonconst_keys=True,
230
- )
231
- )
232
-
233
-
234
- def validate_init_arg_list(
235
- init_arg_list: List[str], cls_schema: pg_typing.Schema) -> None:
236
- """Validate init arg list."""
237
- for i, arg in enumerate(init_arg_list):
238
- is_vararg = False
239
- if i == len(init_arg_list) - 1 and arg.startswith('*'):
240
- arg = arg[1:]
241
- is_vararg = True
242
- field = cls_schema.get_field(arg)
243
- if field is None:
244
- raise TypeError(
245
- f'Argument {arg!r} from `init_arg_list` is not defined as a '
246
- f'symbolic field. init_arg_list={init_arg_list!r}.')
247
- if is_vararg and not isinstance(field.value, pg_typing.List):
248
- raise TypeError(
249
- f'Variable positional argument {arg!r} should be declared with '
250
- f'`pg.typing.List(...)`. Encountered {field.value!r}.')
251
-
252
-
253
- def auto_init_arg_list(cls):
254
- """Generate the init_arg_list metadata from an pg.Object subclass."""
255
- # Inherit from the first non-empty base if they have the same signature.
256
- # This allows to bypass interface-only bases.
257
- init_arg_list = None
258
- for base_cls in cls.__bases__:
259
- schema = getattr(base_cls, '__schema__', None)
260
- if isinstance(schema, pg_typing.Schema):
261
- if list(schema.keys()) == list(cls.__schema__.keys()):
262
- init_arg_list = base_cls.init_arg_list
263
- else:
264
- break
265
- if init_arg_list is None:
266
- # Automatically generate from the field definitions in their
267
- # declaration order from base classes to subclasses.
268
- init_arg_list = [
269
- str(key)
270
- for key in cls.__schema__.fields.keys()
271
- if isinstance(key, pg_typing.ConstStrKey)
272
- ]
273
- return init_arg_list
274
-
275
-
276
- def formalize_schema(schema: pg_typing.Schema) -> pg_typing.Schema: # pylint: disable=redefined-outer-name
277
- """Formalize default values in the schema."""
278
-
279
- def _formalize_field(path: object_utils.KeyPath, node: Any) -> bool:
280
- """Formalize field."""
281
- if isinstance(node, pg_typing.Field):
282
- field = node
283
- if (not flags.is_empty_field_description_allowed()
284
- and not field.description):
285
- raise ValueError(
286
- f'Field description must not be empty (path={path}).')
287
-
288
- field.value.set_default(
289
- field.apply(
290
- field.default_value,
291
- allow_partial=True,
292
- transform_fn=base.symbolic_transform_fn(allow_partial=True)),
293
- use_default_apply=False)
294
- if isinstance(field.value, pg_typing.Dict):
295
- if field.value.schema is not None:
296
- field.value.schema.set_name(f'{schema.name}.{path.path}')
297
- object_utils.traverse(field.value.schema.fields, _formalize_field,
298
- None, path)
299
- elif isinstance(field.value, pg_typing.List):
300
- _formalize_field(object_utils.KeyPath(0, path), field.value.element)
301
- elif isinstance(field.value, pg_typing.Tuple):
302
- for i, elem in enumerate(field.value.elements):
303
- _formalize_field(object_utils.KeyPath(i, path), elem)
304
- elif isinstance(field.value, pg_typing.Union):
305
- for i, c in enumerate(field.value.candidates):
306
- _formalize_field(
307
- object_utils.KeyPath(i, path),
308
- pg_typing.Field(field.key, c, 'Union sub-type.'))
309
- return True
310
-
311
- object_utils.traverse(schema.fields, _formalize_field)
312
- return schema
313
-
314
-
315
- def schema_description_from_docstr(
316
- docstr: Optional[object_utils.DocStr],
317
- include_long_description: bool = False) -> Optional[str]:
318
- """Gets schema description from DocStr."""
319
- if docstr is None:
320
- return None
321
- description = docstr.short_description or ''
322
- if include_long_description:
323
- if docstr.blank_after_short_description:
324
- description += '\n'
325
- if docstr.long_description:
326
- description += '\n' + docstr.long_description
327
- return description.rstrip('\n')
@@ -1,57 +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.symbolize."""
15
-
16
- import unittest
17
-
18
- from pyglove.core import object_utils
19
- from pyglove.core.symbolic import schema_utils
20
-
21
-
22
- class SchemaDescriptionFromDocStrTest(unittest.TestCase):
23
- """Tests for `schema_description_from_docstr`."""
24
-
25
- def test_none_doc_str(self):
26
- self.assertIsNone(schema_utils.schema_description_from_docstr(None))
27
-
28
- def test_short_description_only(self):
29
- docstr = object_utils.DocStr.parse(
30
- """This is a function.""")
31
- self.assertEqual(
32
- schema_utils.schema_description_from_docstr(docstr),
33
- 'This is a function.')
34
- self.assertEqual(
35
- schema_utils.schema_description_from_docstr(
36
- docstr, include_long_description=True),
37
- 'This is a function.')
38
-
39
- def test_long_description_only(self):
40
- docstr = object_utils.DocStr.parse(
41
- """This is a function.
42
-
43
- This is the longer explanation of the function.
44
-
45
- """)
46
- self.assertEqual(
47
- schema_utils.schema_description_from_docstr(docstr),
48
- 'This is a function.')
49
- self.assertEqual(
50
- schema_utils.schema_description_from_docstr(
51
- docstr, include_long_description=True),
52
- ('This is a function.\n\n'
53
- 'This is the longer explanation of the function.'))
54
-
55
-
56
- if __name__ == '__main__':
57
- unittest.main()
@@ -1,202 +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
- """Typing helpers."""
15
-
16
- from typing import Any, Dict, List, Optional, Tuple, Union
17
-
18
- from pyglove.core import object_utils
19
- from pyglove.core.typing import callable_signature
20
- from pyglove.core.typing import class_schema
21
- from pyglove.core.typing import key_specs as ks
22
- from pyglove.core.typing import value_specs as vs
23
-
24
-
25
- def get_arg_fields(
26
- signature: callable_signature.Signature,
27
- args: Optional[List[Union[
28
- Tuple[Union[str, class_schema.KeySpec], class_schema.ValueSpec, str],
29
- Tuple[Union[str, class_schema.KeySpec],
30
- class_schema.ValueSpec, str, Any]]]
31
- ] = None, # pylint: disable=bad-continuation
32
- args_docstr: Optional[Dict[str, object_utils.DocStrArgument]] = None
33
- ) -> List[class_schema.Field]:
34
- """Get schema fields for the arguments from a function or method signature.
35
-
36
- Args:
37
- signature: A `Signature` object.
38
- args: (Optional) explicit value specifications for the arguments, which is a
39
- list of tuples in:
40
- (<argumment-name>, <value-spec>, [description], [metadata-objects]).
41
-
42
- * `argument-name` - a string or a `StrKey` object. This name must exist
43
- in the signature's argument list, unless the signature has a
44
- ``**kwargs``, where the argument name can be an acceptable key in the
45
- dict that is passed to the ``**kwargs``. If the argument name is a
46
- ``StrKey`` object, it specifies a field that matches any keys beyond
47
- the regular arguments for the ``**kwargs``.
48
- * `value-spec` - a ``ValueSpec`` object asssociate with the argument
49
- name.
50
- * `description` - an optional string as the description for the argument.
51
- * `metadata-objects` - an optional list of any type, which can be
52
- used to generate code according to the schema.
53
- args_docstr: (Optional) a dict of argument names to
54
- :class:`pg.object_utils.DocStrArgument` object. If present, they will
55
- be used as the description for the ``Field`` objects.
56
-
57
- Returns:
58
- `Field` objects for the arguments from the `signature` in declaration order.
59
- If an argument is not present in `args`, it will be considered an `Any`.
60
- Otherwise it will create a `Field` from the explicit specifications. Default
61
- values for the arguments will be automatially propagated from the signature
62
- to the fields.
63
-
64
- Raises:
65
- KeyError: If argument names defined in `args` does not match with the
66
- arguments from the signature.
67
- TypeError: The value spec defined in `args` is not compatible with the value
68
- spec inspected from the signature.
69
- ValueError: The value spec defined in `args` does not align with the default
70
- values from the signature.
71
- """
72
- arg_dict = dict()
73
- kwarg_spec = None
74
- varargs_spec = None
75
-
76
- def maybe_add_description(arg_name, field):
77
- if args_docstr and not field.description:
78
- arg_docstr = args_docstr.get(arg_name, None)
79
- if arg_docstr is not None:
80
- field.set_description(arg_docstr.description)
81
- return field
82
-
83
- func_arg_names = set(signature.arg_names)
84
- # Extra legal argument names that are out of function signature, it is not
85
- # empty only when function allow **kwargs.
86
- extra_arg_names = []
87
- for arg in args or []:
88
- if isinstance(arg[0], ks.StrKey):
89
- if kwarg_spec is not None:
90
- raise KeyError(
91
- f'{signature.id}: multiple StrKey found in '
92
- f'symbolic arguments declaration.')
93
- kwarg_spec = arg
94
- else:
95
- assert isinstance(arg[0], (str, ks.ConstStrKey))
96
- if arg[0] in arg_dict:
97
- raise KeyError(
98
- f'{signature.id}: multiple symbolic fields '
99
- f'found for argument {arg[0]!r}.')
100
- if signature.varargs and signature.varargs.name == arg[0]:
101
- varargs_spec = arg
102
-
103
- elif arg[0] not in func_arg_names:
104
- if signature.has_varkw:
105
- extra_arg_names.append(arg[0])
106
- else:
107
- raise KeyError(
108
- f'{signature.id}: found extra symbolic argument {arg[0]!r}.')
109
- arg_dict[arg[0]] = arg
110
-
111
- def get_arg_field(arg_spec):
112
- arg_name = arg_spec.name
113
- decl_spec = arg_spec.value_spec
114
- if arg_name not in arg_dict:
115
- # Automatic generate symbolic declaration for missing arguments.
116
- arg_spec = (arg_name, decl_spec)
117
- else:
118
- arg_spec = arg_dict[arg_name]
119
- if not decl_spec.is_compatible(arg_spec[1]):
120
- raise TypeError(
121
- f'{signature.id}: the value spec ({arg_spec[1]!r}) of symbolic '
122
- f'argument {arg_name} is not compatible with the value spec '
123
- f'({decl_spec!r}) from function signature.')
124
- if arg_spec[1].default in [object_utils.MISSING_VALUE, None]:
125
- arg_spec[1].extend(decl_spec).set_default(decl_spec.default)
126
- elif (decl_spec.default != arg_spec[1].default
127
- and (not isinstance(arg_spec[1], vs.Dict)
128
- or decl_spec.default != object_utils.MISSING_VALUE)):
129
- raise ValueError(
130
- f'{signature.id}: the default value ({arg_spec[1].default!r}) '
131
- f'of symbolic argument {arg_name!r} does not equal to the default '
132
- f'value ({decl_spec.default!r}) specified at function signature '
133
- f'declaration.')
134
- return maybe_add_description(arg_name, class_schema.Field(*arg_spec))
135
- # Add positional named arguments.
136
- arg_fields: List[class_schema.Field] = [
137
- get_arg_field(arg) for arg in signature.args]
138
-
139
- # Add positional wildcard arguments.
140
- if signature.varargs:
141
- if varargs_spec is None:
142
- varargs_spec = (
143
- ks.ConstStrKey(signature.varargs.name),
144
- vs.List(vs.Any()))
145
- elif not isinstance(varargs_spec[1], vs.List):
146
- raise ValueError(
147
- f'{signature.id}: the value spec for positional wildcard argument '
148
- f'{varargs_spec[0]!r} must be a `pg.typing.List` instance. '
149
- f'Encountered: {varargs_spec[1]!r}.')
150
- varargs_spec[1].set_default([])
151
- vararg_field = maybe_add_description(
152
- f'*{signature.varargs.name}', class_schema.Field(*varargs_spec))
153
- arg_fields.append(vararg_field)
154
-
155
- # Add keyword-only arguments.
156
- arg_fields.extend([get_arg_field(arg) for arg in signature.kwonlyargs])
157
-
158
- # Add extra arguments that are keyword wildcard.
159
- for arg_name in extra_arg_names:
160
- arg_field = maybe_add_description(
161
- arg_name,
162
- class_schema.Field(*arg_dict[arg_name]))
163
- arg_fields.append(arg_field)
164
-
165
- # Add keyword wildcard arguments.
166
- if signature.varkw:
167
- if kwarg_spec is None:
168
- kwarg_spec = (ks.StrKey(), vs.Any())
169
- varkw_field = maybe_add_description(
170
- f'**{signature.varkw.name}', class_schema.Field(*kwarg_spec))
171
- arg_fields.append(varkw_field)
172
- return arg_fields
173
-
174
-
175
- def ensure_value_spec(
176
- value_spec: class_schema.ValueSpec,
177
- src_spec: class_schema.ValueSpec,
178
- root_path: Optional[object_utils.KeyPath] = None
179
- ) -> Optional[class_schema.ValueSpec]:
180
- """Extract counter part from value spec that matches dest spec type.
181
-
182
- Args:
183
- value_spec: Value spec.
184
- src_spec: Destination value spec.
185
- root_path: An optional path for the value to include in error message.
186
-
187
- Returns:
188
- value_spec of src_spec_type
189
-
190
- Raises:
191
- TypeError: When value_spec cannot match src_spec_type.
192
- """
193
- if isinstance(value_spec, vs.Union):
194
- value_spec = value_spec.get_candidate(src_spec)
195
- if isinstance(value_spec, vs.Any):
196
- return None
197
- if not src_spec.is_compatible(value_spec):
198
- raise TypeError(
199
- object_utils.message_on_path(
200
- f'Source spec {src_spec} is not compatible with destination '
201
- f'spec {value_spec}.', root_path))
202
- return value_spec