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
@@ -16,29 +16,120 @@
16
16
  import dataclasses
17
17
  import enum
18
18
  import inspect
19
+ import sys
20
+ import types
19
21
  import typing
20
- from typing import Any, Callable, Dict, List, Optional
22
+ from typing import Any, Callable, Dict, List, Optional, Union
21
23
 
22
- from pyglove.core import object_utils
24
+ from pyglove.core import coding
25
+ from pyglove.core import utils
23
26
  from pyglove.core.typing import class_schema
27
+ from pyglove.core.typing import key_specs as ks
24
28
 
25
29
 
26
30
  @dataclasses.dataclass
27
31
  class Argument:
28
32
  """Definition for a callable argument."""
33
+
34
+ class Kind(enum.Enum):
35
+ """Arugment kind."""
36
+ POSITIONAL_OR_KEYWORD = 1
37
+ VAR_POSITIONAL = 2
38
+ KEYWORD_ONLY = 3
39
+ VAR_KEYWORD = 4
40
+
41
+ @classmethod
42
+ def from_parameter(cls, parameter: inspect.Parameter) -> 'Argument.Kind':
43
+ """Returns Argument.Kind from inspect.Parameter."""
44
+ if parameter.kind == inspect.Parameter.POSITIONAL_ONLY:
45
+ return Argument.Kind.POSITIONAL_OR_KEYWORD
46
+ elif parameter.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
47
+ return Argument.Kind.POSITIONAL_OR_KEYWORD
48
+ elif parameter.kind == inspect.Parameter.VAR_POSITIONAL:
49
+ return Argument.Kind.VAR_POSITIONAL
50
+ elif parameter.kind == inspect.Parameter.KEYWORD_ONLY:
51
+ return Argument.Kind.KEYWORD_ONLY
52
+ else:
53
+ assert parameter.kind == inspect.Parameter.VAR_KEYWORD, parameter.kind
54
+ return Argument.Kind.VAR_KEYWORD
55
+
29
56
  name: str
57
+ kind: Kind
30
58
  value_spec: class_schema.ValueSpec
59
+ description: Optional[str] = None
60
+
61
+ def __post_init__(self):
62
+ if self.kind == Argument.Kind.VAR_POSITIONAL:
63
+ if not isinstance(self.value_spec, class_schema.ValueSpec.ListType):
64
+ raise TypeError(
65
+ f'Variable positional argument {self.name!r} should have a value '
66
+ f'of `pg.typing.List` type. Encountered: {self.value_spec!r}.'
67
+ )
68
+ self.value_spec.set_default([])
69
+
70
+ if (self.kind == Argument.Kind.VAR_KEYWORD
71
+ and not isinstance(self.value_spec, class_schema.ValueSpec.DictType)):
72
+ raise TypeError(
73
+ f'Variable keyword argument {self.name!r} should have a value of '
74
+ f'`pg.typing.Dict` type. Encountered: {self.value_spec!r}.'
75
+ )
31
76
 
32
77
  @classmethod
33
78
  def from_annotation(
34
79
  cls,
35
80
  name: str,
81
+ kind: Kind,
36
82
  annotation: Any = inspect.Parameter.empty,
37
- auto_typing: bool = False):
83
+ auto_typing: bool = False,
84
+ parent_module: Optional[types.ModuleType] = None) -> 'Argument':
38
85
  """Creates an argument from annotation."""
39
- return Argument(
40
- name, class_schema.ValueSpec.from_annotation(
41
- annotation, auto_typing=auto_typing))
86
+ return cls(
87
+ name,
88
+ kind,
89
+ class_schema.ValueSpec.from_annotation(
90
+ annotation, auto_typing=auto_typing, parent_module=parent_module
91
+ )
92
+ )
93
+
94
+ @classmethod
95
+ def from_parameter(
96
+ cls,
97
+ param: inspect.Parameter,
98
+ description: Optional[str] = None,
99
+ auto_typing: bool = True,
100
+ parent_module: Optional[types.ModuleType] = None,
101
+ ) -> 'Argument':
102
+ """Creates an argument from inspect.Parameter."""
103
+ value_spec = class_schema.ValueSpec.from_annotation(
104
+ param.annotation, auto_typing=auto_typing, parent_module=parent_module
105
+ )
106
+ if param.default != inspect.Parameter.empty:
107
+ value_spec.set_default(param.default)
108
+
109
+ # pytype: disable=wrong-arg-count
110
+ # pytype: disable=not-instantiable
111
+ if param.kind == inspect.Parameter.VAR_POSITIONAL:
112
+ value_spec = class_schema.ValueSpec.ListType(value_spec, default=[])
113
+ elif param.kind == inspect.Parameter.VAR_KEYWORD:
114
+ value_spec = class_schema.ValueSpec.DictType(value_spec)
115
+ # pytype: enable=wrong-arg-count
116
+ # pytype: enable=not-instantiable
117
+ return cls(
118
+ param.name,
119
+ Argument.Kind.from_parameter(param),
120
+ value_spec,
121
+ description=description
122
+ )
123
+
124
+ def to_field(self) -> class_schema.Field:
125
+ """Converts current argument to a pg.typing.Field object."""
126
+ if self.kind == Argument.Kind.VAR_KEYWORD:
127
+ key = ks.StrKey()
128
+ value = self.value_spec.schema.dynamic_field.value # pytype: disable=attribute-error
129
+ else:
130
+ key = ks.ConstStrKey(self.name)
131
+ value = self.value_spec
132
+ return class_schema.Field(key, value, description=self.description)
42
133
 
43
134
 
44
135
  class CallableType(enum.Enum):
@@ -50,7 +141,7 @@ class CallableType(enum.Enum):
50
141
  METHOD = 2
51
142
 
52
143
 
53
- class Signature(object_utils.Formattable):
144
+ class Signature(utils.Formattable):
54
145
  """PY3 function signature."""
55
146
 
56
147
  def __init__(self,
@@ -62,7 +153,8 @@ class Signature(object_utils.Formattable):
62
153
  varargs: Optional[Argument] = None,
63
154
  varkw: Optional[Argument] = None,
64
155
  return_value: Optional[class_schema.ValueSpec] = None,
65
- qualname: Optional[str] = None):
156
+ qualname: Optional[str] = None,
157
+ description: Optional[str] = None):
66
158
  """Constructor.
67
159
 
68
160
  Args:
@@ -77,6 +169,7 @@ class Signature(object_utils.Formattable):
77
169
  name for `**kwargs`.
78
170
  return_value: Optional value spec for return value.
79
171
  qualname: Optional qualified name.
172
+ description: Optional description of the signature.
80
173
  """
81
174
  args = args or []
82
175
  self.callable_type = callable_type
@@ -88,9 +181,10 @@ class Signature(object_utils.Formattable):
88
181
  self.varkw = varkw
89
182
  self.return_value = return_value
90
183
  self.qualname = qualname or name
184
+ self.description = description
91
185
 
92
186
  @property
93
- def named_args(self):
187
+ def named_args(self) -> List[Argument]:
94
188
  """Returns all named arguments according to their declaration order."""
95
189
  return self.args + self.kwonlyargs
96
190
 
@@ -114,7 +208,7 @@ class Signature(object_utils.Formattable):
114
208
  if arg.name == name:
115
209
  return arg.value_spec
116
210
  if self.varkw is not None:
117
- return self.varkw.value_spec
211
+ return self.varkw.value_spec.schema.dynamic_field.value # pytype: disable=attribute-error
118
212
  return None
119
213
 
120
214
  @property
@@ -155,24 +249,206 @@ class Signature(object_utils.Formattable):
155
249
  self.varargs == other.varargs and self.varkw == other.varkw and
156
250
  self.return_value == other.return_value)
157
251
 
158
- def format(self, *args, markdown: bool = False, **kwargs) -> str:
252
+ def format(
253
+ self,
254
+ compact: bool = False,
255
+ verbose: bool = True,
256
+ root_indent: int = 0,
257
+ **kwargs,
258
+ ) -> str:
159
259
  """Format current object."""
160
- details = object_utils.kvlist_str([
161
- ('', repr(self.id), ''),
162
- ('args', object_utils.format(self.args, **kwargs), '[]'),
163
- ('kwonlyargs', object_utils.format(self.kwonlyargs, **kwargs), '[]'),
164
- ('returns', object_utils.format(self.return_value, **kwargs), 'None'),
165
- ('varargs', object_utils.format(self.varargs, **kwargs), 'None'),
166
- ('varkw', object_utils.format(self.varkw, **kwargs), 'None'),
167
- ])
168
- return object_utils.maybe_markdown_quote(
169
- f'{self.__class__.__name__}({details})', markdown
260
+ return utils.kvlist_str(
261
+ [
262
+ ('', self.id, ''),
263
+ ('args', self.args, []),
264
+ ('kwonlyargs', self.kwonlyargs, []),
265
+ ('returns', self.return_value, None),
266
+ ('varargs', self.varargs, None),
267
+ ('varkw', self.varkw, None),
268
+ ('description', self.description, None),
269
+ ],
270
+ label=self.__class__.__name__,
271
+ compact=compact,
272
+ verbose=verbose,
273
+ root_indent=root_indent,
274
+ **kwargs,
275
+ )
276
+
277
+ def annotate(
278
+ self,
279
+ args: Union[
280
+ Dict[class_schema.FieldKeyDef, class_schema.FieldValueDef],
281
+ List[class_schema.FieldDef],
282
+ None,
283
+ ] = None,
284
+ return_value: Union[class_schema.ValueSpec, Any, None] = None,
285
+ ) -> 'Signature':
286
+ """Annotate arguments with extra typing."""
287
+ if return_value is not None:
288
+ return_value = class_schema.ValueSpec.from_annotation(
289
+ return_value, auto_typing=True
290
+ )
291
+ if utils.MISSING_VALUE != return_value.default:
292
+ raise ValueError('return value spec should not have default value.')
293
+ self.return_value = return_value
294
+
295
+ if not args:
296
+ return self
297
+
298
+ schema = class_schema.create_schema(args, allow_nonconst_keys=True) # pylint: disable=redefined-outer-name
299
+
300
+ arg_fields: Dict[str, class_schema.Field] = dict()
301
+ varargs_field = None
302
+ kwarg_field = None
303
+ existing_names = set(self.arg_names)
304
+ extra_arg_names = []
305
+
306
+ for arg_name, field in schema.fields.items():
307
+ if isinstance(arg_name, ks.StrKey):
308
+ if kwarg_field is not None:
309
+ raise KeyError(
310
+ f'{self.id}: multiple StrKey found in override args.'
311
+ )
312
+ kwarg_field = field
313
+ else:
314
+ assert isinstance(arg_name, (str, ks.ConstStrKey))
315
+ if self.varargs and self.varargs.name == arg_name:
316
+ varargs_field = field
317
+
318
+ elif self.varkw and self.varkw.name == arg_name:
319
+ if kwarg_field is not None:
320
+ raise KeyError(
321
+ f'{self.id}: multiple StrKey found in '
322
+ f'symbolic arguments declaration.')
323
+ kwarg_field = field
324
+ elif arg_name not in existing_names:
325
+ if self.has_varkw:
326
+ extra_arg_names.append(arg_name)
327
+ else:
328
+ raise KeyError(
329
+ f'{self.id}: found extra symbolic argument {arg_name.text!r}.')
330
+ arg_fields[arg_name.text] = field
331
+
332
+ def update_arg(arg: Argument, field: class_schema.Field):
333
+ """Updates an argument with override field."""
334
+ if arg.value_spec.has_default and (
335
+ not field.value.has_default
336
+ # Loose the default as user may mark it as noneable.
337
+ or field.value.default is None
338
+ ):
339
+ field.value.set_default(
340
+ arg.value_spec.default, root_path=utils.KeyPath(arg.name)
341
+ )
342
+ if arg.value_spec.default != field.value.default:
343
+ if field.value.is_noneable and not arg.value_spec.has_default:
344
+ # Special handling noneable which always comes with a default.
345
+ field.value.set_default(utils.MISSING_VALUE)
346
+ elif not (
347
+ # Special handling Dict type which always has default.
348
+ isinstance(field.value, class_schema.ValueSpec.DictType)
349
+ and not arg.value_spec.has_default
350
+ ):
351
+ raise ValueError(
352
+ f'The annotated default value ({field.default_value}) of '
353
+ f'symbolic argument {arg.name!r} is not equal to the default '
354
+ f'value ({arg.value_spec.default}) specified from the function '
355
+ 'signature.'
356
+ )
357
+ arg.value_spec = field.value
358
+ if field.description:
359
+ arg.description = field.description
360
+
361
+ # Named arguments.
362
+ for arg in self.named_args:
363
+ field = arg_fields.get(arg.name)
364
+ if field is not None:
365
+ update_arg(arg, field)
366
+
367
+ # Add extra arguments.
368
+ for arg_name in extra_arg_names:
369
+ field = arg_fields.get(arg_name)
370
+ assert field is not None
371
+ self.kwonlyargs.append(
372
+ Argument(
373
+ arg_name.text,
374
+ Argument.Kind.KEYWORD_ONLY,
375
+ field.value,
376
+ field.description
377
+ )
378
+ )
379
+
380
+ # Update varargs.
381
+ if varargs_field is not None:
382
+ assert self.varargs is not None
383
+ if not isinstance(varargs_field.value, class_schema.ValueSpec.ListType):
384
+ raise ValueError(
385
+ f'Variable positional argument {self.varargs.name!r} should have a '
386
+ 'value of `pg.typing.List` type. '
387
+ f'Encountered: {varargs_field.value!r}.'
388
+ )
389
+ update_arg(self.varargs, varargs_field)
390
+
391
+ # Update kwarg.
392
+ if kwarg_field is not None:
393
+ assert self.varkw is not None
394
+ value_spec = class_schema.ValueSpec.DictType(kwarg_field.value)
395
+ self.varkw.value_spec = value_spec
396
+ if kwarg_field.description:
397
+ self.varkw.description = kwarg_field.description
398
+
399
+ return self
400
+
401
+ def fields(
402
+ self,
403
+ remove_self: bool = True,
404
+ include_return: bool = False,
405
+ ) -> List[class_schema.Field]:
406
+ """Returns the fields of this signature."""
407
+ fields = [
408
+ arg.to_field() for i, arg in enumerate(self.args)
409
+ if not remove_self or i > 0 or arg.name != 'self'
410
+ ]
411
+ if self.varargs:
412
+ fields.append(self.varargs.to_field())
413
+ fields.extend([arg.to_field() for arg in self.kwonlyargs])
414
+ if self.varkw:
415
+ fields.append(self.varkw.to_field())
416
+ if include_return and self.return_value:
417
+ fields.append(
418
+ class_schema.Field('return', self.return_value, 'Return value.')
419
+ )
420
+ return fields
421
+
422
+ def to_schema(
423
+ self,
424
+ remove_self: bool = True,
425
+ include_return: bool = False,
426
+ ) -> class_schema.Schema:
427
+ """Returns the schema of this signature."""
428
+ init_arg_list = [arg.name for arg in self.args]
429
+ if init_arg_list and init_arg_list[0] == 'self':
430
+ init_arg_list.pop(0)
431
+
432
+ if self.varargs:
433
+ init_arg_list.append(f'*{self.varargs.name}')
434
+
435
+ return class_schema.Schema(
436
+ self.fields(remove_self=remove_self, include_return=include_return),
437
+ name=f'{self.module_name}.{self.qualname}',
438
+ description=self.description,
439
+ allow_nonconst_keys=True,
440
+ metadata=dict(
441
+ init_arg_list=init_arg_list,
442
+ varargs_name=self.varargs.name if self.varargs else None,
443
+ varkw_name=self.varkw.name if self.varkw else None,
444
+ returns=self.return_value,
445
+ ),
170
446
  )
171
447
 
172
448
  @classmethod
173
449
  def from_schema(
174
450
  cls,
175
- schema: class_schema.Schema,
451
+ schema: class_schema.Schema, # pylint: disable=redefined-outer-name
176
452
  module_name: str,
177
453
  name: str,
178
454
  qualname: Optional[str] = None,
@@ -205,20 +481,24 @@ class Signature(object_utils.Formattable):
205
481
 
206
482
  args = []
207
483
  if is_method:
208
- args.append(Argument.from_annotation('self'))
484
+ args.append(
485
+ Argument.from_annotation('self', Argument.Kind.POSITIONAL_OR_KEYWORD)
486
+ )
209
487
 
210
488
  # Prepare positional arguments.
211
- args.extend([Argument(n, get_arg_spec(n)) for n in arg_names])
489
+ args.extend([
490
+ Argument(n, Argument.Kind.POSITIONAL_OR_KEYWORD, get_arg_spec(n))
491
+ for n in arg_names
492
+ ])
212
493
 
213
494
  # Prepare varargs.
214
495
  varargs = None
215
496
  if vararg_name:
216
- vararg_spec = get_arg_spec(vararg_name)
217
- if not isinstance(vararg_spec, class_schema.ValueSpec.ListType):
218
- raise ValueError(
219
- f'Variable positional argument {vararg_name!r} should have a value '
220
- f'of `pg.typing.List` type. Encountered: {vararg_spec!r}.')
221
- varargs = Argument(vararg_name, vararg_spec.element.value) # pytype: disable=attribute-error
497
+ varargs = Argument(
498
+ vararg_name,
499
+ Argument.Kind.VAR_POSITIONAL,
500
+ get_arg_spec(vararg_name)
501
+ ) # pytype: disable=attribute-error
222
502
 
223
503
  # Prepare keyword-only arguments.
224
504
  existing_names = set(arg_names)
@@ -228,58 +508,154 @@ class Signature(object_utils.Formattable):
228
508
  kwonlyargs = []
229
509
  varkw = None
230
510
  for key, field in schema.fields.items():
231
- if key not in existing_names:
511
+ if key not in existing_names and not field.frozen:
232
512
  if key.is_const:
233
- kwonlyargs.append(Argument(str(key), field.value))
513
+ kwonlyargs.append(
514
+ Argument(str(key), Argument.Kind.KEYWORD_ONLY, field.value)
515
+ )
234
516
  else:
517
+ # pytype: disable=not-instantiable
518
+ # pytype: disable=wrong-arg-count
235
519
  varkw = Argument(
236
520
  schema.metadata.get('varkw_name', None) or 'kwargs',
237
- field.value)
521
+ Argument.Kind.VAR_KEYWORD,
522
+ class_schema.ValueSpec.DictType(field.value)
523
+ )
524
+ # pytype: enable=wrong-arg-count
525
+ # pytype: enable=not-instantiable
238
526
 
239
527
  return Signature(
240
528
  callable_type=CallableType.FUNCTION,
241
529
  name=name,
242
530
  module_name=module_name,
243
531
  qualname=qualname,
532
+ description=schema.description,
244
533
  args=args,
245
534
  kwonlyargs=kwonlyargs,
246
535
  varargs=varargs,
247
536
  varkw=varkw,
248
- return_value=schema.metadata.get('returns', None))
537
+ return_value=schema.metadata.get('returns', None)
538
+ )
249
539
 
250
540
  @classmethod
251
541
  def from_callable(
252
542
  cls,
253
543
  callable_object: Callable[..., Any],
254
- auto_typing: bool = False) -> 'Signature':
544
+ auto_typing: bool = False,
545
+ auto_doc: bool = False,
546
+ ) -> 'Signature':
255
547
  """Creates Signature from a callable object."""
256
548
  callable_object = typing.cast(object, callable_object)
257
549
  if not callable(callable_object):
258
550
  raise TypeError(f'{callable_object!r} is not callable.')
259
551
 
260
- if isinstance(callable_object, object_utils.Functor):
552
+ if isinstance(callable_object, utils.Functor):
261
553
  assert callable_object.__signature__ is not None
262
554
  return callable_object.__signature__
263
555
 
264
556
  func = callable_object
265
- if not inspect.isroutine(func):
266
- if not inspect.isroutine(callable_object.__call__):
267
- raise TypeError(f'{callable_object!r}.__call__ is not a method.')
268
- func = callable_object.__call__
557
+ docstr = None
558
+ if inspect.isclass(func):
559
+ callable_type = CallableType.METHOD
560
+ try:
561
+ sig = inspect.signature(func)
562
+ except ValueError:
563
+ sig = inspect.signature(func.__init__)
564
+
565
+ if auto_doc:
566
+ description = None
567
+ args_doc = {}
568
+ if func.__doc__:
569
+ cls_doc = utils.DocStr.parse(func.__doc__)
570
+ description = cls_doc.short_description
571
+ args_doc.update(cls_doc.args)
572
+
573
+ if func.__init__.__doc__:
574
+ init_doc = utils.DocStr.parse(func.__init__.__doc__)
575
+ args_doc.update(init_doc.args)
576
+ docstr = utils.DocStr(
577
+ utils.DocStrStyle.GOOGLE,
578
+ short_description=description,
579
+ long_description=None,
580
+ examples=[],
581
+ args=args_doc,
582
+ returns=None,
583
+ raises=[],
584
+ blank_after_short_description=True,
585
+ )
586
+ else:
587
+ if not inspect.isroutine(func):
588
+ if not inspect.isroutine(callable_object.__call__):
589
+ raise TypeError(f'{callable_object!r}.__call__ is not a method.')
590
+ func = callable_object.__call__
591
+ callable_type = (
592
+ CallableType.METHOD if inspect.ismethod(func)
593
+ else CallableType.FUNCTION
594
+ )
595
+ if auto_doc:
596
+ docstr = utils.docstr(func)
597
+ sig = inspect.signature(func)
598
+
599
+ module_name = getattr(func, '__module__', None)
600
+ return cls.from_signature(
601
+ sig=sig,
602
+ name=func.__name__,
603
+ qualname=func.__qualname__,
604
+ callable_type=callable_type,
605
+ module_name=module_name or 'wrapper',
606
+ auto_typing=auto_typing,
607
+ docstr=docstr,
608
+ parent_module=sys.modules[module_name] if module_name else None
609
+ )
269
610
 
270
- def make_arg_spec(param: inspect.Parameter) -> Argument:
271
- value_spec = class_schema.ValueSpec.from_annotation(
272
- param.annotation, auto_typing=auto_typing)
273
- if param.default != inspect.Parameter.empty:
274
- value_spec.set_default(param.default)
275
- return Argument(param.name, value_spec)
611
+ @classmethod
612
+ def from_signature(
613
+ cls,
614
+ sig: inspect.Signature,
615
+ name: str,
616
+ callable_type: CallableType,
617
+ module_name: Optional[str] = None,
618
+ qualname: Optional[str] = None,
619
+ auto_typing: bool = False,
620
+ docstr: Union[str, utils.DocStr, None] = None,
621
+ parent_module: Optional[types.ModuleType] = None,
622
+ ) -> 'Signature':
623
+ """Returns PyGlove signature from Python signature.
624
+
625
+ Args:
626
+ sig: Python signature.
627
+ name: Name of the entity (class name or function/method name).
628
+ callable_type: the type of this callable.
629
+ module_name: Module name of the entity.
630
+ qualname: (Optional) qualified name of the entity.
631
+ auto_typing: If True, automatically convert argument annotations
632
+ to PyGlove ValueSpec objects. Otherwise use pg.typing.Any()
633
+ with annotations.
634
+ docstr: (Optional) DocStr for this entity.
635
+ parent_module: (Optional) Parent module from where the signature is
636
+ derived. This is useful to infer classes with forward declarations.
276
637
 
277
- sig = inspect.signature(func)
638
+ Returns:
639
+ A PyGlove Signature object.
640
+ """
278
641
  args = []
279
642
  kwonly_args = []
280
643
  varargs = None
281
644
  varkw = None
282
645
 
646
+ if isinstance(docstr, str):
647
+ docstr = utils.DocStr.parse(docstr)
648
+
649
+ def make_arg_spec(param: inspect.Parameter) -> Argument:
650
+ """Makes argument spec from inspect.Parameter."""
651
+ docstr_arg = docstr.parameter(param) if docstr else None
652
+ return Argument.from_parameter(
653
+ param,
654
+ description=docstr_arg.description if docstr_arg else None,
655
+ auto_typing=auto_typing,
656
+ parent_module=parent_module,
657
+ )
658
+
283
659
  for param in sig.parameters.values():
284
660
  arg_spec = make_arg_spec(param)
285
661
  if (param.kind == inspect.Parameter.POSITIONAL_ONLY
@@ -296,20 +672,23 @@ class Signature(object_utils.Formattable):
296
672
  return_value = None
297
673
  if sig.return_annotation is not inspect.Parameter.empty:
298
674
  return_value = class_schema.ValueSpec.from_annotation(
299
- sig.return_annotation, auto_typing=auto_typing)
300
-
301
- if inspect.ismethod(func):
302
- callable_type = CallableType.METHOD
303
- else:
304
- callable_type = CallableType.FUNCTION
675
+ sig.return_annotation,
676
+ auto_typing=auto_typing,
677
+ parent_module=parent_module
678
+ )
305
679
 
306
- return Signature(
680
+ return cls(
307
681
  callable_type=callable_type,
308
- name=func.__name__,
309
- module_name=getattr(func, '__module__', 'wrapper'),
310
- qualname=func.__qualname__,
311
- args=args, kwonlyargs=kwonly_args, varargs=varargs, varkw=varkw,
312
- return_value=return_value)
682
+ name=name,
683
+ module_name=module_name,
684
+ qualname=qualname,
685
+ description=docstr.short_description if docstr else None,
686
+ args=args,
687
+ kwonlyargs=kwonly_args,
688
+ varargs=varargs,
689
+ varkw=varkw,
690
+ return_value=return_value,
691
+ )
313
692
 
314
693
  def make_function(
315
694
  self,
@@ -329,7 +708,7 @@ class Signature(object_utils.Formattable):
329
708
  force_missing_as_default: bool = False,
330
709
  arg_prefix: str = ''):
331
710
  s = [f'{arg_prefix}{arg_name}']
332
- if arg_spec.annotation != object_utils.MISSING_VALUE:
711
+ if arg_spec.annotation != utils.MISSING_VALUE:
333
712
  s.append(f': _annotation_{arg_name}')
334
713
  exec_locals[f'_annotation_{arg_name}'] = arg_spec.annotation
335
714
  if not arg_prefix and (force_missing_as_default or arg_spec.has_default):
@@ -346,7 +725,15 @@ class Signature(object_utils.Formattable):
346
725
 
347
726
  # Build variable positional arguments.
348
727
  if self.varargs:
349
- _append_arg(self.varargs.name, self.varargs.value_spec, arg_prefix='*')
728
+ assert isinstance(
729
+ self.varargs.value_spec,
730
+ class_schema.ValueSpec.ListType
731
+ ), self.varargs
732
+ _append_arg(
733
+ self.varargs.name,
734
+ getattr(self.varargs.value_spec, 'element'),
735
+ arg_prefix='*',
736
+ )
350
737
  elif self.kwonlyargs:
351
738
  args.append('*')
352
739
 
@@ -356,23 +743,80 @@ class Signature(object_utils.Formattable):
356
743
 
357
744
  # Build variable keyword arguments.
358
745
  if self.varkw:
359
- _append_arg(self.varkw.name, self.varkw.value_spec, arg_prefix='**')
746
+ assert isinstance(
747
+ self.varkw.value_spec,
748
+ class_schema.ValueSpec.DictType
749
+ ), self.varkw
750
+ _append_arg(
751
+ self.varkw.name,
752
+ self.varkw.value_spec.schema.dynamic_field.value, # pytype: disable=attribute-error
753
+ arg_prefix='**'
754
+ )
360
755
 
361
756
  # Generate function.
362
- fn = object_utils.make_function(
757
+ fn = coding.make_function(
363
758
  self.name,
364
759
  args=args,
365
760
  body=body,
366
761
  exec_globals=exec_globals,
367
762
  exec_locals=exec_locals,
368
763
  return_type=getattr(
369
- self.return_value, 'annotation', object_utils.MISSING_VALUE))
764
+ self.return_value, 'annotation', coding.NO_TYPE_ANNOTATION
765
+ ),
766
+ )
370
767
  fn.__module__ = self.module_name
371
768
  fn.__name__ = self.name
372
769
  fn.__qualname__ = self.qualname
373
770
  return fn
374
771
 
375
772
 
376
- def get_signature(func: Callable, auto_typing: bool = False) -> Signature: # pylint:disable=g-bare-generic
773
+ def signature(
774
+ func: Callable[..., Any],
775
+ auto_typing: bool = True,
776
+ auto_doc: bool = True,
777
+ ) -> Signature: # pylint:disable=g-bare-generic
377
778
  """Gets signature from a python callable."""
378
- return Signature.from_callable(func, auto_typing)
779
+ return Signature.from_callable(func, auto_typing, auto_doc)
780
+
781
+
782
+ def schema(
783
+ cls_or_fn: Callable[..., Any],
784
+ args: Union[
785
+ List[Union[class_schema.Field, class_schema.FieldDef]],
786
+ Dict[class_schema.FieldKeyDef, class_schema.FieldValueDef],
787
+ None
788
+ ] = None,
789
+ returns: Optional[class_schema.ValueSpec] = None,
790
+ *,
791
+ auto_typing: bool = True,
792
+ auto_doc: bool = True,
793
+ remove_self: bool = True,
794
+ include_return: bool = False,
795
+ ) -> class_schema.Schema:
796
+ """Returns the schema from the signature of a class or a function.
797
+
798
+ Args:
799
+ cls_or_fn: A class or a function.
800
+ args: (Optional) additional annotations for arguments.
801
+ returns: (Optional) additional annotation for return value.
802
+ auto_typing: If True, enable type inference from annotations.
803
+ auto_doc: If True, extract schema/field description form docstrs.
804
+ remove_self: If True, remove the first `self` argument if it appears in the
805
+ signature.
806
+ include_return: If True, include the return value spec in the schema with
807
+ key 'return_value'.
808
+
809
+ Returns:
810
+ A pg.typing.Schema object.
811
+ """
812
+ s = getattr(cls_or_fn, '__schema__', None)
813
+ if isinstance(s, class_schema.Schema):
814
+ return s
815
+ return signature(
816
+ cls_or_fn, auto_typing=auto_typing, auto_doc=auto_doc
817
+ ).annotate(
818
+ args, return_value=returns
819
+ ).to_schema(
820
+ remove_self=remove_self,
821
+ include_return=include_return,
822
+ )