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
@@ -11,18 +11,110 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- """Tests for pyglove.core.typing.callable_signature."""
15
-
16
14
  import copy
15
+ import dataclasses
17
16
  import inspect
17
+ from typing import List
18
18
  import unittest
19
19
 
20
+ from pyglove.core import utils
20
21
  from pyglove.core.typing import annotation_conversion # pylint: disable=unused-import
21
22
  from pyglove.core.typing import callable_signature
22
23
  from pyglove.core.typing import class_schema
23
24
  from pyglove.core.typing import key_specs as ks
24
25
  from pyglove.core.typing import value_specs as vs
25
26
 
27
+ Argument = callable_signature.Argument
28
+ Signature = callable_signature.Signature
29
+
30
+
31
+ class ArgumentTest(unittest.TestCase):
32
+ """Tests for `Argument` class."""
33
+
34
+ def test_kind(self):
35
+
36
+ class Foo:
37
+ def bar(self, x, *args, y, **kwargs):
38
+ del x, args, y, kwargs
39
+
40
+ sig = inspect.signature(Foo.bar)
41
+ self.assertEqual(
42
+ Argument.Kind.from_parameter(sig.parameters['self']),
43
+ Argument.Kind.POSITIONAL_OR_KEYWORD
44
+ )
45
+ self.assertEqual(
46
+ Argument.Kind.from_parameter(sig.parameters['x']),
47
+ Argument.Kind.POSITIONAL_OR_KEYWORD
48
+ )
49
+ self.assertEqual(
50
+ Argument.Kind.from_parameter(sig.parameters['args']),
51
+ Argument.Kind.VAR_POSITIONAL
52
+ )
53
+ self.assertEqual(
54
+ Argument.Kind.from_parameter(sig.parameters['y']),
55
+ Argument.Kind.KEYWORD_ONLY
56
+ )
57
+ self.assertEqual(
58
+ Argument.Kind.from_parameter(sig.parameters['kwargs']),
59
+ Argument.Kind.VAR_KEYWORD
60
+ )
61
+
62
+ def test_init(self):
63
+ self.assertEqual(
64
+ Argument(
65
+ 'x', Argument.Kind.VAR_POSITIONAL, vs.List(vs.Int())).value_spec,
66
+ vs.List(vs.Int(), default=[])
67
+ )
68
+ with self.assertRaisesRegex(
69
+ TypeError,
70
+ 'Variable positional argument .* should have a value of .*List'
71
+ ):
72
+ Argument('x', Argument.Kind.VAR_POSITIONAL, vs.Int())
73
+
74
+ with self.assertRaisesRegex(
75
+ TypeError,
76
+ 'Variable keyword argument .* should have a value of .*Dict'
77
+ ):
78
+ Argument('x', Argument.Kind.VAR_KEYWORD, vs.Int())
79
+
80
+ def test_from_parameter(self):
81
+
82
+ def bar(x: int, *args, y: str, **kwargs):
83
+ del x, args, y, kwargs
84
+
85
+ sig = inspect.signature(bar)
86
+ self.assertEqual(
87
+ Argument.from_parameter(sig.parameters['x'], 'arg x', auto_typing=True),
88
+ Argument(
89
+ 'x', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Int(),
90
+ description='arg x'
91
+ )
92
+ )
93
+ self.assertEqual(
94
+ Argument.from_parameter(sig.parameters['args'], 'varargs'),
95
+ Argument(
96
+ 'args',
97
+ Argument.Kind.VAR_POSITIONAL,
98
+ vs.List(vs.Any(), default=[]),
99
+ 'varargs'
100
+ )
101
+ )
102
+ self.assertEqual(
103
+ Argument.from_parameter(sig.parameters['y'], 'arg y', auto_typing=True),
104
+ Argument(
105
+ 'y', Argument.Kind.KEYWORD_ONLY, vs.Str(), description='arg y'
106
+ )
107
+ )
108
+ self.assertEqual(
109
+ Argument.from_parameter(sig.parameters['kwargs'], 'kwargs'),
110
+ Argument(
111
+ 'kwargs',
112
+ Argument.Kind.VAR_KEYWORD,
113
+ vs.Dict(vs.Any()),
114
+ 'kwargs'
115
+ )
116
+ )
117
+
26
118
 
27
119
  class SignatureTest(unittest.TestCase):
28
120
  """Tests for `Signature` class."""
@@ -30,26 +122,37 @@ class SignatureTest(unittest.TestCase):
30
122
  def test_basics(self):
31
123
  """Test basics of `Signature` class."""
32
124
 
33
- def foo(a, b: int = 1):
125
+ def foo(a, *, b: int = 1):
34
126
  del a, b
35
127
 
36
- signature = callable_signature.get_signature(foo)
128
+ signature = callable_signature.signature(
129
+ foo, auto_typing=False, auto_doc=False
130
+ )
37
131
  self.assertEqual(signature.module_name, 'pyglove.core.typing.callable_signature_test')
38
132
  self.assertEqual(signature.name, 'foo')
39
- self.assertEqual(signature.id,
40
- 'pyglove.core.typing.callable_signature_test.SignatureTest.test_basics.<locals>.foo')
133
+ self.assertEqual(
134
+ signature.id, 'pyglove.core.typing.callable_signature_test.SignatureTest.test_basics.<locals>.foo'
135
+ )
41
136
  self.assertEqual(
42
137
  str(signature),
43
- 'Signature(\'pyglove.core.typing.callable_signature_test.SignatureTest.test_basics.<locals>.foo\', '
44
- 'args=[\n'
45
- ' Argument(name=\'a\', value_spec=Any()),\n'
46
- ' Argument(name=\'b\', value_spec=Any('
47
- 'default=1, annotation=<class \'int\'>))\n])')
138
+ inspect.cleandoc("""
139
+ Signature(
140
+ 'pyglove.core.typing.callable_signature_test.SignatureTest.test_basics.<locals>.foo',
141
+ args=[
142
+ Argument(name='a', kind=<Kind.POSITIONAL_OR_KEYWORD: 1>, value_spec=Any(), description=None)
143
+ ],
144
+ kwonlyargs=[
145
+ Argument(name='b', kind=<Kind.KEYWORD_ONLY: 3>, value_spec=Any(default=1, annotation=<class 'int'>), description=None)
146
+ ]
147
+ )
148
+ """)
149
+ )
48
150
 
49
151
  self.assertEqual(signature.named_args, [
50
- callable_signature.Argument('a', vs.Any()),
51
- callable_signature.Argument(
52
- 'b', vs.Any(default=1).annotate(int)),
152
+ Argument('a', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Any()),
153
+ Argument(
154
+ 'b', Argument.Kind.KEYWORD_ONLY, vs.Any(default=1).annotate(int)
155
+ ),
53
156
  ])
54
157
  self.assertEqual(signature.arg_names, ['a', 'b'])
55
158
 
@@ -61,42 +164,249 @@ class SignatureTest(unittest.TestCase):
61
164
 
62
165
  assert_not_equal(signature, 'name', 'bar')
63
166
  assert_not_equal(signature, 'module_name', 'other_module')
64
- assert_not_equal(signature, 'args',
65
- [signature.args[0],
66
- callable_signature.Argument('b', vs.Int())])
167
+ assert_not_equal(
168
+ signature, 'args',
169
+ [signature.args[0],
170
+ Argument('b', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Int())]
171
+ )
67
172
  assert_not_equal(
68
173
  signature, 'kwonlyargs',
69
174
  list(signature.kwonlyargs) + [
70
- callable_signature.Argument('x', vs.Any())])
71
- assert_not_equal(signature, 'varargs',
72
- callable_signature.Argument('args', vs.Any()))
73
- assert_not_equal(signature, 'varkw',
74
- callable_signature.Argument('kwargs', vs.Any()))
175
+ Argument('x', Argument.Kind.KEYWORD_ONLY, vs.Any())]
176
+ )
177
+ assert_not_equal(
178
+ signature, 'varargs',
179
+ Argument('args', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Any())
180
+ )
181
+ assert_not_equal(
182
+ signature, 'varkw',
183
+ Argument(
184
+ 'kwargs',
185
+ Argument.Kind.VAR_KEYWORD,
186
+ vs.Dict(vs.Any())
187
+ )
188
+ )
75
189
  self.assertNotEqual(signature, 1)
76
190
  self.assertEqual(signature, signature)
77
191
  self.assertEqual(signature, copy.deepcopy(signature))
78
192
 
79
193
  with self.assertRaisesRegex(TypeError, '.* is not callable'):
80
- callable_signature.get_signature(1)
194
+ callable_signature.signature(1)
195
+
196
+ def test_annotate(self):
197
+ def foo(a, *args, b=1, **kwargs):
198
+ del a, b, args, kwargs
199
+ return 1
200
+
201
+ signature = callable_signature.signature(foo).annotate(
202
+ dict(
203
+ a=int,
204
+ b=(int, 'Field b'),
205
+ c=(vs.Bool(default=True), 'Field c', dict(meta=1)),
206
+ args=(List[int], 'Varargs'),
207
+ kwargs=(str, 'Kwargs'),
208
+ ),
209
+ return_value=int,
210
+ )
211
+ self.assertEqual(signature.args, [
212
+ Argument('a', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Int()),
213
+ ])
214
+ self.assertEqual(signature.kwonlyargs, [
215
+ Argument(
216
+ 'b', Argument.Kind.KEYWORD_ONLY, vs.Int(default=1), 'Field b'
217
+ ),
218
+ Argument(
219
+ 'c', Argument.Kind.KEYWORD_ONLY, vs.Bool(default=True), 'Field c'
220
+ ),
221
+ ])
222
+ self.assertEqual(
223
+ signature.varargs,
224
+ Argument(
225
+ 'args',
226
+ Argument.Kind.VAR_POSITIONAL,
227
+ vs.List(vs.Int(), default=[]),
228
+ 'Varargs'
229
+ )
230
+ )
231
+ self.assertEqual(
232
+ signature.varkw,
233
+ Argument(
234
+ 'kwargs', Argument.Kind.VAR_KEYWORD,
235
+ vs.Dict(vs.Str()), 'Kwargs'
236
+ )
237
+ )
238
+ self.assertEqual(signature.return_value, vs.Int())
239
+
240
+ # Customize the typing of kwargs.
241
+ signature = callable_signature.signature(foo).annotate({ks.StrKey(): int})
242
+
243
+ self.assertEqual(
244
+ signature.varkw,
245
+ Argument(
246
+ 'kwargs', Argument.Kind.VAR_KEYWORD,
247
+ vs.Dict(vs.Int())
248
+ )
249
+ )
250
+
251
+ # Special handling noneable type specification.
252
+ signature = callable_signature.signature(
253
+ foo).annotate({'a': vs.Int().noneable()})
254
+
255
+ self.assertEqual(
256
+ signature.args,
257
+ [Argument(
258
+ 'a', Argument.Kind.POSITIONAL_OR_KEYWORD,
259
+ # No default value shall be specified.
260
+ vs.Int(is_noneable=True),
261
+ )]
262
+ )
263
+
264
+ # Special handling dict type specification.
265
+ signature = callable_signature.signature(
266
+ foo).annotate({'a': vs.Dict([('x', vs.Int())])})
267
+
268
+ self.assertEqual(
269
+ signature.args,
270
+ [Argument(
271
+ 'a', Argument.Kind.POSITIONAL_OR_KEYWORD,
272
+ vs.Dict([('x', vs.Int())])
273
+ )]
274
+ )
275
+
276
+ # Bad override.
277
+ signature = callable_signature.signature(foo)
278
+
279
+ with self.assertRaisesRegex(
280
+ ValueError, 'return value spec should not have default value'
281
+ ):
282
+ signature.annotate(return_value=vs.Int(default=1))
283
+
284
+ with self.assertRaisesRegex(ValueError, '.*pg.typing.List'):
285
+ signature.annotate(dict(args=int))
286
+
287
+ with self.assertRaisesRegex(KeyError, '.*multiple StrKey'):
288
+ signature.annotate([(ks.StrKey(), int), ('kwargs', str)])
289
+
290
+ with self.assertRaisesRegex(KeyError, '.*multiple StrKey'):
291
+ signature.annotate([('kwargs', str), (ks.StrKey(), int)])
292
+
293
+ with self.assertRaisesRegex(ValueError, 'The annotated default value'):
294
+ signature.annotate([('b', vs.Int(default=2))])
295
+
296
+ with self.assertRaisesRegex(TypeError, 'Expect .* but encountered'):
297
+ signature.annotate([('b', vs.Str())])
298
+
299
+ signature = callable_signature.signature(lambda a: 1)
300
+ with self.assertRaisesRegex(KeyError, '.*found extra symbolic argument'):
301
+ signature.annotate([('b', vs.Int())])
302
+
303
+ def test_to_schema(self):
304
+
305
+ class Foo:
306
+ def foo(self, a: int, *args, b: str = 'x', **kwargs) -> str:
307
+ """Function foo.
308
+
309
+ Args:
310
+ a: An int.
311
+ *args: Varargs.
312
+ b: A str.
313
+ **kwargs: Kwargs.
314
+
315
+ Returns:
316
+ A str.
317
+ """
318
+ del a, args, kwargs
319
+ return b
320
+
321
+ schema = Signature.from_callable(
322
+ Foo.foo, auto_typing=True, auto_doc=True
323
+ ).to_schema()
324
+ self.assertEqual(
325
+ schema,
326
+ class_schema.Schema(
327
+ [
328
+ class_schema.Field('a', vs.Int(), 'An int.'),
329
+ class_schema.Field(
330
+ 'args', vs.List(vs.Any(), default=[]), 'Varargs.'
331
+ ),
332
+ class_schema.Field('b', vs.Str(default='x'), 'A str.'),
333
+ class_schema.Field(ks.StrKey(), vs.Any(), 'Kwargs.'),
334
+ ],
335
+ allow_nonconst_keys=True,
336
+ )
337
+ )
338
+ self.assertEqual(
339
+ schema.name, f'{Foo.foo.__module__}.{Foo.foo.__qualname__}'
340
+ )
341
+ self.assertEqual(schema.description, 'Function foo.')
342
+ self.assertTrue(schema.allow_nonconst_keys)
343
+ self.assertEqual(
344
+ schema.metadata,
345
+ dict(
346
+ init_arg_list=['a', '*args'],
347
+ varargs_name='args',
348
+ varkw_name='kwargs',
349
+ returns=vs.Str(),
350
+ )
351
+ )
352
+
353
+ def test_make_function(self):
354
+ """Tests `Signature.make_function`."""
355
+
356
+ def func1(x, y=1) -> int:
357
+ del x, y
358
+
359
+ def func2(x=1, *, y):
360
+ del x, y
361
+
362
+ def func3(x=1, *y): # pylint: disable=keyword-arg-before-vararg
363
+ del x, y
364
+
365
+ def func4(*y):
366
+ del y
367
+
368
+ def func5(*, x=1, y):
369
+ del x, y
370
+
371
+ def func6(x=1, *, y, **z) -> str:
372
+ del x, y, z
373
+
374
+ for func in [func1, func2, func3, func4, func5, func6]:
375
+ new_func = callable_signature.signature(func).make_function(['pass'])
376
+ old_signature = inspect.signature(func)
377
+ new_signature = inspect.signature(new_func)
378
+ self.assertEqual(old_signature, new_signature)
379
+
380
+
381
+ class FromCallableTest(unittest.TestCase):
382
+ """Tests for `Signature.from_callable`."""
81
383
 
82
384
  def test_function(self):
83
- """Tests `get_signature` on regular functions."""
385
+ """Tests `from_callable` on regular functions."""
84
386
 
85
- def foo(a, b: int = 1, **kwargs):
387
+ def foo(a, *, b: int = 1, **kwargs):
86
388
  del a, b, kwargs
87
389
 
88
- signature = callable_signature.get_signature(foo)
390
+ signature = Signature.from_callable(foo)
89
391
  self.assertEqual(
90
392
  signature.callable_type, callable_signature.CallableType.FUNCTION)
91
393
  self.assertEqual(signature.args, [
92
- callable_signature.Argument('a', vs.Any()),
93
- callable_signature.Argument(
94
- 'b', vs.Any(default=1).annotate(int)),
394
+ Argument('a', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Any()),
395
+ ])
396
+ self.assertEqual(signature.kwonlyargs, [
397
+ Argument(
398
+ 'b', Argument.Kind.KEYWORD_ONLY, vs.Any(default=1).annotate(int)
399
+ ),
95
400
  ])
96
- self.assertEqual(signature.kwonlyargs, [])
97
401
  self.assertIsNone(signature.varargs)
98
- self.assertEqual(signature.varkw,
99
- callable_signature.Argument('kwargs', vs.Any()))
402
+ self.assertEqual(
403
+ signature.varkw,
404
+ Argument(
405
+ 'kwargs',
406
+ Argument.Kind.VAR_KEYWORD,
407
+ vs.Dict(vs.Any())
408
+ )
409
+ )
100
410
  self.assertFalse(signature.has_varargs)
101
411
  self.assertTrue(signature.has_varkw)
102
412
  self.assertTrue(signature.has_wildcard_args)
@@ -107,12 +417,14 @@ class SignatureTest(unittest.TestCase):
107
417
  self.assertEqual(signature.get_value_spec('x'), vs.Any())
108
418
 
109
419
  def test_lambda(self):
110
- """Tests `get_signature` on lambda function."""
111
- signature = callable_signature.get_signature(lambda x: x)
420
+ """Tests `from_callable` on lambda function."""
421
+ signature = Signature.from_callable(lambda x: x)
112
422
  self.assertEqual(
113
423
  signature.callable_type, callable_signature.CallableType.FUNCTION)
114
424
  self.assertEqual(
115
- signature.args, [callable_signature.Argument('x', vs.Any())])
425
+ signature.args,
426
+ [Argument('x', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Any())]
427
+ )
116
428
  self.assertEqual(signature.kwonlyargs, [])
117
429
  self.assertIsNone(signature.varargs)
118
430
  self.assertIsNone(signature.varkw)
@@ -122,7 +434,7 @@ class SignatureTest(unittest.TestCase):
122
434
  self.assertIsNone(signature.get_value_spec('y'))
123
435
 
124
436
  def test_method(self):
125
- """Tests get_signature on class methods."""
437
+ """Tests `from_callable` on class methods."""
126
438
 
127
439
  class A:
128
440
 
@@ -139,163 +451,283 @@ class SignatureTest(unittest.TestCase):
139
451
  return z
140
452
 
141
453
  # Test class static method.
142
- signature = callable_signature.get_signature(A.foo)
454
+ signature = Signature.from_callable(A.foo)
143
455
  self.assertEqual(
144
456
  signature.callable_type, callable_signature.CallableType.METHOD)
145
457
  self.assertEqual(
146
458
  signature.args,
147
- [callable_signature.Argument(
148
- 'x', vs.Any(default=1).annotate(int))])
459
+ [Argument(
460
+ 'x',
461
+ Argument.Kind.POSITIONAL_OR_KEYWORD,
462
+ vs.Any(default=1).annotate(int)
463
+ )]
464
+ )
149
465
  self.assertEqual(signature.kwonlyargs, [])
150
466
 
151
467
  # Test instance method.
152
- signature = callable_signature.get_signature(A().bar)
468
+ signature = Signature.from_callable(A().bar)
153
469
  self.assertEqual(
154
470
  signature.callable_type, callable_signature.CallableType.METHOD)
155
471
  self.assertEqual(
156
472
  signature.args,
157
- [callable_signature.Argument('y', vs.Any().annotate(int))])
473
+ [Argument(
474
+ 'y', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Any().annotate(int)
475
+ )]
476
+ )
158
477
  self.assertEqual(
159
478
  signature.kwonlyargs,
160
- [callable_signature.Argument('z', vs.Any(default=1))])
479
+ [Argument('z', Argument.Kind.KEYWORD_ONLY, vs.Any(default=1))]
480
+ )
161
481
  self.assertEqual(
162
482
  signature.varargs,
163
- callable_signature.Argument('args', vs.Any()))
483
+ Argument(
484
+ 'args',
485
+ Argument.Kind.VAR_POSITIONAL,
486
+ vs.List(vs.Any(), default=[])
487
+ )
488
+ )
164
489
  self.assertTrue(signature.has_varargs)
165
490
  self.assertFalse(signature.has_varkw)
166
491
 
167
492
  # Test unbound instance method
168
- signature = callable_signature.get_signature(A.bar)
493
+ signature = Signature.from_callable(A.bar)
169
494
  self.assertEqual(
170
495
  signature.callable_type, callable_signature.CallableType.FUNCTION)
171
496
  self.assertEqual(signature.args, [
172
- callable_signature.Argument('self', vs.Any()),
173
- callable_signature.Argument('y', vs.Any().annotate(int))
497
+ Argument('self', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Any()),
498
+ Argument(
499
+ 'y', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Any().annotate(int)
500
+ )
174
501
  ])
175
502
  self.assertEqual(
176
503
  signature.kwonlyargs,
177
- [callable_signature.Argument('z', vs.Any(default=1))])
504
+ [Argument('z', Argument.Kind.KEYWORD_ONLY, vs.Any(default=1))]
505
+ )
178
506
  self.assertEqual(
179
507
  signature.varargs,
180
- callable_signature.Argument('args', vs.Any()))
508
+ Argument(
509
+ 'args',
510
+ Argument.Kind.VAR_POSITIONAL,
511
+ vs.List(vs.Any(), default=[])
512
+ )
513
+ )
181
514
  self.assertTrue(signature.has_varargs)
182
515
  self.assertFalse(signature.has_varkw)
183
516
 
184
517
  # Test object as callable.
185
- signature = callable_signature.get_signature(A())
518
+ signature = Signature.from_callable(A())
186
519
  self.assertEqual(
187
520
  signature.callable_type, callable_signature.CallableType.METHOD)
188
521
  self.assertEqual(
189
522
  signature.args,
190
- [callable_signature.Argument('z', vs.Any().annotate(int))])
523
+ [Argument(
524
+ 'z', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Any().annotate(int)
525
+ )]
526
+ )
191
527
  self.assertEqual(signature.kwonlyargs, [])
192
528
  self.assertFalse(signature.has_varargs)
193
529
  self.assertTrue(signature.has_varkw)
194
530
  self.assertEqual(
195
531
  signature.varkw,
196
- callable_signature.Argument('kwargs', vs.Any()))
197
-
198
- def test_make_function(self):
199
- """Tests `Signature.make_function`."""
532
+ Argument(
533
+ 'kwargs', Argument.Kind.VAR_KEYWORD,
534
+ vs.Dict(vs.Any())
535
+ )
536
+ )
200
537
 
201
- def func1(x, y=1) -> int:
202
- del x, y
538
+ def test_class(self):
539
+ """Tests `from_callable` on classes."""
203
540
 
204
- def func2(x=1, *, y):
205
- del x, y
206
-
207
- def func3(x=1, *y): # pylint: disable=keyword-arg-before-vararg
208
- del x, y
541
+ class A:
542
+ def __init__(self, x: int, *, y: str, **kwargs):
543
+ """Constructor.
209
544
 
210
- def func4(*y):
211
- del y
545
+ Args:
546
+ x: An int.
547
+ y: A str.
548
+ **kwargs: Kwargs.
549
+ """
212
550
 
213
- def func5(*, x=1, y):
214
- del x, y
551
+ signature = Signature.from_callable(A, auto_typing=True, auto_doc=True)
552
+ self.assertEqual(
553
+ signature.callable_type, callable_signature.CallableType.METHOD
554
+ )
555
+ self.assertEqual(signature.name, A.__name__)
556
+ self.assertEqual(signature.module_name, A.__module__)
557
+ self.assertEqual(signature.qualname, A.__qualname__)
558
+ self.assertIsNone(signature.description)
215
559
 
216
- def func6(x=1, *, y, **z) -> str:
217
- del x, y, z
560
+ self.assertEqual(
561
+ signature.args,
562
+ [Argument(
563
+ 'x', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Int(),
564
+ description='An int.'
565
+ )]
566
+ )
567
+ self.assertEqual(
568
+ signature.kwonlyargs,
569
+ [Argument(
570
+ 'y', Argument.Kind.KEYWORD_ONLY, vs.Str(),
571
+ description='A str.'
572
+ )]
573
+ )
574
+ self.assertEqual(
575
+ signature.varkw,
576
+ Argument(
577
+ 'kwargs',
578
+ Argument.Kind.VAR_KEYWORD,
579
+ vs.Dict(vs.Any()),
580
+ description='Kwargs.'
581
+ )
582
+ )
583
+
584
+ @dataclasses.dataclass
585
+ class B:
586
+ """Class B.
587
+
588
+ Params:
589
+ x: An int.
590
+ y: A str.
591
+ """
592
+ x: int
593
+ y: str
594
+
595
+ signature = Signature.from_callable(B, auto_typing=True, auto_doc=True)
596
+ self.assertEqual(
597
+ signature.callable_type, callable_signature.CallableType.METHOD
598
+ )
599
+ self.assertEqual(signature.name, B.__name__)
600
+ self.assertEqual(signature.module_name, B.__module__)
601
+ self.assertEqual(signature.qualname, B.__qualname__)
602
+ self.assertEqual(signature.description, 'Class B.')
603
+ self.assertEqual(
604
+ signature.args,
605
+ [
606
+ Argument(
607
+ 'x', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Int(),
608
+ description='An int.'
609
+ ),
610
+ Argument(
611
+ 'y', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Str(),
612
+ description='A str.'
613
+ )
614
+ ]
615
+ )
616
+
617
+ # Signature for builtin classes
618
+ signature = callable_signature.signature(bytes)
619
+ self.assertEqual(
620
+ signature.callable_type, callable_signature.CallableType.METHOD
621
+ )
622
+ self.assertEqual(signature.name, bytes.__name__)
623
+ self.assertEqual(signature.module_name, bytes.__module__)
624
+ self.assertEqual(signature.qualname, bytes.__qualname__)
625
+ self.assertIsNotNone(signature.varargs)
626
+ self.assertIsNotNone(signature.varkw)
218
627
 
219
- for func in [func1, func2, func3, func4, func5, func6]:
220
- new_func = callable_signature.get_signature(func).make_function(['pass'])
221
- old_signature = inspect.signature(func)
222
- new_signature = inspect.signature(new_func)
223
- self.assertEqual(old_signature, new_signature)
628
+ def test_signature_with_forward_declarations(self):
629
+ signature = callable_signature.signature(utils.KeyPath)
630
+ self.assertIs(signature.get_value_spec('parent').cls, utils.KeyPath)
224
631
 
225
632
 
226
633
  class FromSchemaTest(unittest.TestCase):
227
634
  """Tests for `Signature.from_schema`."""
228
635
 
229
- def _get_signature(self, init_arg_list, is_method: bool = True):
636
+ def _signature(self, init_arg_list, is_method: bool = True):
230
637
  s = class_schema.Schema([
231
638
  class_schema.Field('x', vs.Int(), 'x'),
232
639
  class_schema.Field('y', vs.Int(), 'y'),
640
+ # Frozen fields will be ignored.
641
+ class_schema.Field('v', vs.Bool().freeze(True), 'v'),
233
642
  class_schema.Field('z', vs.List(vs.Int()), 'z'),
234
643
  class_schema.Field(ks.StrKey(), vs.Str(), 'kwargs'),
235
644
  ], metadata=dict(init_arg_list=init_arg_list), allow_nonconst_keys=True)
236
- return callable_signature.Signature.from_schema(
645
+ return Signature.from_schema(
237
646
  s, 'bar', 'foo', is_method=is_method)
238
647
 
239
648
  def test_classmethod_with_regular_args(self):
240
649
  self.assertEqual(
241
- self._get_signature(['x', 'y', 'z']),
242
- callable_signature.Signature(
650
+ self._signature(['x', 'y', 'z']),
651
+ Signature(
243
652
  callable_type=callable_signature.CallableType.FUNCTION,
244
653
  module_name='bar',
245
654
  name='foo',
246
655
  args=[
247
- callable_signature.Argument('self', vs.Any()),
248
- callable_signature.Argument('x', vs.Int()),
249
- callable_signature.Argument('y', vs.Int()),
250
- callable_signature.Argument('z', vs.List(vs.Int())),
656
+ Argument('self', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Any()),
657
+ Argument('x', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Int()),
658
+ Argument('y', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Int()),
659
+ Argument(
660
+ 'z', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.List(vs.Int())
661
+ ),
251
662
  ],
252
- varkw=callable_signature.Argument('kwargs', vs.Str())))
663
+ varkw=Argument(
664
+ 'kwargs',
665
+ Argument.Kind.VAR_KEYWORD,
666
+ vs.Dict(vs.Str())
667
+ )
668
+ )
669
+ )
253
670
 
254
671
  def test_function_with_varargs(self):
255
672
  self.assertEqual(
256
- self._get_signature(['x', '*z'], is_method=False),
257
- callable_signature.Signature(
673
+ self._signature(['x', '*z'], is_method=False),
674
+ Signature(
258
675
  callable_type=callable_signature.CallableType.FUNCTION,
259
676
  module_name='bar',
260
677
  name='foo',
261
678
  args=[
262
- callable_signature.Argument('x', vs.Int()),
679
+ Argument('x', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Int()),
263
680
  ],
264
681
  kwonlyargs=[
265
- callable_signature.Argument('y', vs.Int()),
682
+ Argument('y', Argument.Kind.KEYWORD_ONLY, vs.Int()),
266
683
  ],
267
- varargs=callable_signature.Argument('z', vs.Int()),
268
- varkw=callable_signature.Argument('kwargs', vs.Str())))
684
+ varargs=Argument(
685
+ 'z', Argument.Kind.VAR_POSITIONAL, vs.List(vs.Int())
686
+ ),
687
+ varkw=Argument(
688
+ 'kwargs',
689
+ Argument.Kind.VAR_KEYWORD,
690
+ vs.Dict(vs.Str())
691
+ )
692
+ )
693
+ )
269
694
 
270
695
  def test_classmethod_with_kwonly_args(self):
271
696
  self.assertEqual(
272
- self._get_signature([]),
273
- callable_signature.Signature(
697
+ self._signature([]),
698
+ Signature(
274
699
  callable_type=callable_signature.CallableType.FUNCTION,
275
700
  module_name='bar',
276
701
  name='foo',
277
702
  args=[
278
- callable_signature.Argument('self', vs.Any()),
703
+ Argument('self', Argument.Kind.POSITIONAL_OR_KEYWORD, vs.Any()),
279
704
  ],
280
705
  kwonlyargs=[
281
- callable_signature.Argument('x', vs.Int()),
282
- callable_signature.Argument('y', vs.Int()),
283
- callable_signature.Argument(
284
- 'z', vs.List(vs.Int())),
706
+ Argument('x', Argument.Kind.KEYWORD_ONLY, vs.Int()),
707
+ Argument('y', Argument.Kind.KEYWORD_ONLY, vs.Int()),
708
+ Argument(
709
+ 'z', Argument.Kind.KEYWORD_ONLY, vs.List(vs.Int())
710
+ ),
285
711
  ],
286
- varkw=callable_signature.Argument('kwargs', vs.Str())))
712
+ varkw=Argument(
713
+ 'kwargs',
714
+ Argument.Kind.VAR_KEYWORD,
715
+ vs.Dict(vs.Str())
716
+ )
717
+ )
718
+ )
287
719
 
288
720
  def test_bad_cases(self):
289
721
  with self.assertRaisesRegex(
290
- ValueError,
722
+ TypeError,
291
723
  'Variable positional argument \'x\' should have a value of '
292
724
  '`pg.typing.List` type'):
293
- _ = self._get_signature(['*x'])
725
+ _ = self._signature(['*x'])
294
726
 
295
727
  with self.assertRaisesRegex(
296
728
  ValueError,
297
729
  'Argument \'a\' is not a symbolic field.'):
298
- _ = callable_signature.Signature.from_schema(
730
+ _ = Signature.from_schema(
299
731
  class_schema.Schema([], metadata=dict(init_arg_list=['a'])),
300
732
  '__main__', 'foo')
301
733
 
@@ -304,7 +736,95 @@ class FromSchemaTest(unittest.TestCase):
304
736
 
305
737
  with self.assertRaisesRegex(
306
738
  TypeError, '.*__call__ is not a method'):
307
- callable_signature.Signature.from_callable(Foo)
739
+ Signature.from_callable(Foo())
740
+
741
+
742
+ class GetSchemaTest(unittest.TestCase):
743
+ """Tests for `schema`."""
744
+
745
+ def test_function_schema(self):
746
+ def foo(x: int, *args, y: str, **kwargs) -> float:
747
+ """A function.
748
+
749
+ Args:
750
+ x: Input 1.
751
+ *args: Variable positional args.
752
+ y: Input 2.
753
+ **kwargs: Variable keyword args.
754
+
755
+ Returns:
756
+ The result.
757
+ """
758
+ del x, y, args, kwargs
759
+
760
+ schema = callable_signature.schema(foo, auto_typing=True, auto_doc=True)
761
+ self.assertEqual(schema.name, f'{foo.__module__}.{foo.__qualname__}')
762
+ self.assertEqual(schema.description, 'A function.')
763
+ self.assertEqual(
764
+ list(schema.fields.values()),
765
+ [
766
+ class_schema.Field('x', vs.Int(), description='Input 1.'),
767
+ class_schema.Field(
768
+ 'args',
769
+ vs.List(vs.Any(), default=[]),
770
+ description='Variable positional args.',
771
+ ),
772
+ class_schema.Field('y', vs.Str(), description='Input 2.'),
773
+ class_schema.Field(
774
+ ks.StrKey(),
775
+ vs.Any(),
776
+ description='Variable keyword args.',
777
+ ),
778
+ ],
779
+ )
780
+
781
+ def test_schema_on_symbolic_classes(self):
782
+
783
+ class A:
784
+ pass
785
+
786
+ setattr(A, '__schema__', class_schema.create_schema([]))
787
+ self.assertIs(callable_signature.schema(A), A.__schema__)
788
+
789
+ def test_class_init_schema(self):
790
+ class A:
791
+
792
+ def __init__(self, x: int, *args, y: str, **kwargs) -> float:
793
+ """Constructor.
794
+
795
+ Args:
796
+ x: Input 1.
797
+ *args: Variable positional args.
798
+ y: Input 2.
799
+ **kwargs: Variable keyword args.
800
+
801
+ Returns:
802
+ The result.
803
+ """
804
+ del x, y, args, kwargs
805
+
806
+ schema = callable_signature.schema(
807
+ A.__init__, auto_typing=True, auto_doc=True, remove_self=True
808
+ )
809
+ self.assertEqual(schema.name, f'{A.__module__}.{A.__init__.__qualname__}')
810
+ self.assertEqual(schema.description, 'Constructor.')
811
+ self.assertEqual(
812
+ list(schema.fields.values()),
813
+ [
814
+ class_schema.Field('x', vs.Int(), description='Input 1.'),
815
+ class_schema.Field(
816
+ 'args',
817
+ vs.List(vs.Any(), default=[]),
818
+ description='Variable positional args.',
819
+ ),
820
+ class_schema.Field('y', vs.Str(), description='Input 2.'),
821
+ class_schema.Field(
822
+ ks.StrKey(),
823
+ vs.Any(),
824
+ description='Variable keyword args.',
825
+ ),
826
+ ],
827
+ )
308
828
 
309
829
 
310
830
  if __name__ == '__main__':