pyglove 0.5.0.dev202510150810__py3-none-any.whl → 0.5.0.dev202510170226__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.
Potentially problematic release.
This version of pyglove might be problematic. Click here for more details.
- pyglove/core/typing/class_schema.py +20 -0
- pyglove/core/typing/json_schema.py +221 -8
- pyglove/core/typing/json_schema_test.py +508 -12
- pyglove/core/utils/contextual.py +9 -4
- pyglove/core/utils/contextual_test.py +10 -0
- {pyglove-0.5.0.dev202510150810.dist-info → pyglove-0.5.0.dev202510170226.dist-info}/METADATA +1 -1
- {pyglove-0.5.0.dev202510150810.dist-info → pyglove-0.5.0.dev202510170226.dist-info}/RECORD +10 -10
- {pyglove-0.5.0.dev202510150810.dist-info → pyglove-0.5.0.dev202510170226.dist-info}/WHEEL +0 -0
- {pyglove-0.5.0.dev202510150810.dist-info → pyglove-0.5.0.dev202510170226.dist-info}/licenses/LICENSE +0 -0
- {pyglove-0.5.0.dev202510150810.dist-info → pyglove-0.5.0.dev202510170226.dist-info}/top_level.txt +0 -0
|
@@ -582,6 +582,16 @@ class ValueSpec(utils.Formattable, utils.JSONConvertible):
|
|
|
582
582
|
del include_type_name, include_subclasses, inline_nested_refs, kwargs
|
|
583
583
|
assert False, 'Overridden in `json_schema.py`.'
|
|
584
584
|
|
|
585
|
+
@classmethod
|
|
586
|
+
def from_json_schema(
|
|
587
|
+
cls,
|
|
588
|
+
json_schema: Dict[str, Any],
|
|
589
|
+
class_fn: Optional[Callable[[str, 'Schema'], Type[Any]]] = None
|
|
590
|
+
) -> 'ValueSpec':
|
|
591
|
+
"""Converts a JSON schema to a value spec."""
|
|
592
|
+
del json_schema, class_fn
|
|
593
|
+
assert False, 'Overridden in `json_schema.py`.'
|
|
594
|
+
|
|
585
595
|
|
|
586
596
|
class Field(utils.Formattable, utils.JSONConvertible):
|
|
587
597
|
"""Class that represents the definition of one or a group of attributes.
|
|
@@ -1375,6 +1385,16 @@ class Schema(utils.Formattable, utils.JSONConvertible):
|
|
|
1375
1385
|
def __ne__(self, other: Any) -> bool:
|
|
1376
1386
|
return not self.__eq__(other)
|
|
1377
1387
|
|
|
1388
|
+
@classmethod
|
|
1389
|
+
def from_json_schema(
|
|
1390
|
+
cls,
|
|
1391
|
+
json_schema: Dict[str, Any],
|
|
1392
|
+
class_fn: Optional[Callable[[str, 'Schema'], Type[Any]]] = None
|
|
1393
|
+
) -> 'Schema':
|
|
1394
|
+
"""Converts a JSON schema to a schema."""
|
|
1395
|
+
del json_schema, class_fn
|
|
1396
|
+
assert False, 'Overridden in `json_schema.py`.'
|
|
1397
|
+
|
|
1378
1398
|
|
|
1379
1399
|
FieldDef = Union[
|
|
1380
1400
|
# Key, Value spec/annotation.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import dataclasses
|
|
17
17
|
import inspect
|
|
18
|
-
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
18
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union
|
|
19
19
|
|
|
20
20
|
from pyglove.core import utils
|
|
21
21
|
from pyglove.core.typing import callable_signature
|
|
@@ -53,8 +53,8 @@ def _json_schema_from_schema(
|
|
|
53
53
|
required = []
|
|
54
54
|
|
|
55
55
|
if type_name and include_type_name:
|
|
56
|
-
properties[
|
|
57
|
-
required.append(
|
|
56
|
+
properties[utils.JSONConvertible.TYPE_NAME_KEY] = {'const': type_name}
|
|
57
|
+
required.append(utils.JSONConvertible.TYPE_NAME_KEY)
|
|
58
58
|
|
|
59
59
|
for key, field in schema.items():
|
|
60
60
|
if isinstance(key, ks.ConstStrKey):
|
|
@@ -145,7 +145,6 @@ def _json_schema_from_value_spec(
|
|
|
145
145
|
}
|
|
146
146
|
if not isinstance(value_spec.element.value, vs.Any):
|
|
147
147
|
definition['items'] = _child_json_schema(value_spec.element.value)
|
|
148
|
-
return definition
|
|
149
148
|
elif isinstance(value_spec, vs.Dict):
|
|
150
149
|
if value_spec.schema is None:
|
|
151
150
|
definition = {
|
|
@@ -168,9 +167,7 @@ def _json_schema_from_value_spec(
|
|
|
168
167
|
include_type_name=include_type_name,
|
|
169
168
|
include_subclasses=include_subclasses,
|
|
170
169
|
)
|
|
171
|
-
definitions = [
|
|
172
|
-
_json_schema_from_cls(value_spec.cls)
|
|
173
|
-
]
|
|
170
|
+
definitions = [_json_schema_from_cls(value_spec.cls)]
|
|
174
171
|
|
|
175
172
|
if include_subclasses:
|
|
176
173
|
for subclass in value_spec.cls.__subclasses__():
|
|
@@ -206,12 +203,26 @@ def _json_schema_from_value_spec(
|
|
|
206
203
|
f'Value spec {value_spec!r} cannot be converted to JSON schema.'
|
|
207
204
|
)
|
|
208
205
|
|
|
206
|
+
if (value_spec.has_default
|
|
207
|
+
and value_spec.default is not None
|
|
208
|
+
and not isinstance(value_spec, vs.Dict)):
|
|
209
|
+
default = utils.to_json(value_spec.default)
|
|
210
|
+
if not include_type_name:
|
|
211
|
+
def _remove_type_name(_, v: Dict[str, Any]):
|
|
212
|
+
if isinstance(v, dict):
|
|
213
|
+
v.pop(utils.JSONConvertible.TYPE_NAME_KEY, None)
|
|
214
|
+
return v
|
|
215
|
+
default = utils.transform(default, _remove_type_name)
|
|
216
|
+
definition['default'] = default
|
|
217
|
+
|
|
209
218
|
if not ignore_nonable and value_spec.is_noneable:
|
|
210
219
|
nullable = {'type': 'null'}
|
|
211
220
|
if 'anyOf' in definition:
|
|
212
221
|
definition['anyOf'].append(nullable)
|
|
213
222
|
else:
|
|
214
223
|
definition = {'anyOf': [definition, nullable]}
|
|
224
|
+
if value_spec.default is None:
|
|
225
|
+
definition['default'] = None
|
|
215
226
|
return definition
|
|
216
227
|
|
|
217
228
|
|
|
@@ -294,6 +305,8 @@ def _canonicalize_schema(
|
|
|
294
305
|
}
|
|
295
306
|
canonical_form = {'$defs': referenced_defs} if referenced_defs else {}
|
|
296
307
|
canonical_form.update(new_root)
|
|
308
|
+
if 'default' in root:
|
|
309
|
+
canonical_form['default'] = root['default']
|
|
297
310
|
return canonical_form
|
|
298
311
|
|
|
299
312
|
#
|
|
@@ -336,5 +349,205 @@ def _schema_to_json_schema(
|
|
|
336
349
|
**kwargs
|
|
337
350
|
)
|
|
338
351
|
|
|
339
|
-
|
|
352
|
+
class_schema.ValueSpec.to_json_schema = _value_spec_to_json_schema
|
|
340
353
|
class_schema.Schema.to_json_schema = _schema_to_json_schema
|
|
354
|
+
|
|
355
|
+
#
|
|
356
|
+
# from JSON schema to PyGlove value spec.
|
|
357
|
+
#
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _json_schema_to_value_spec(
|
|
361
|
+
json_schema: Dict[str, Any],
|
|
362
|
+
defs: Dict[str, Type[Any]],
|
|
363
|
+
class_fn: Optional[Callable[[str, class_schema.Schema], Type[Any]]],
|
|
364
|
+
add_json_schema_as_metadata: bool,
|
|
365
|
+
) -> class_schema.ValueSpec:
|
|
366
|
+
"""Converts a JSON schema to a value spec."""
|
|
367
|
+
# Generate code to convert JSON schema to value spec.
|
|
368
|
+
def _value_spec(value_schema: Dict[str, Any]):
|
|
369
|
+
return _json_schema_to_value_spec(
|
|
370
|
+
value_schema, defs, class_fn, add_json_schema_as_metadata
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if '$ref' in json_schema:
|
|
374
|
+
# TODO(daiyip): Support circular references.
|
|
375
|
+
ref_key = json_schema['$ref'].split('/')[-1]
|
|
376
|
+
type_ref = defs.get(ref_key)
|
|
377
|
+
if type_ref is None:
|
|
378
|
+
raise ValueError(
|
|
379
|
+
f'Reference {ref_key!r} not defined in defs. '
|
|
380
|
+
'Please make sure classes being referenced are defined '
|
|
381
|
+
'before the referencing classes. '
|
|
382
|
+
)
|
|
383
|
+
return type_ref
|
|
384
|
+
type_str = json_schema.get('type')
|
|
385
|
+
default = json_schema.get('default', utils.MISSING_VALUE)
|
|
386
|
+
if type_str is None:
|
|
387
|
+
if 'enum' in json_schema:
|
|
388
|
+
for v in json_schema['enum']:
|
|
389
|
+
if not isinstance(v, (str, int, float, bool)):
|
|
390
|
+
raise ValueError(
|
|
391
|
+
f'Enum candidate {v!r} is not supported for JSON schema '
|
|
392
|
+
'conversion.'
|
|
393
|
+
)
|
|
394
|
+
return vs.Enum(
|
|
395
|
+
default,
|
|
396
|
+
[v for v in json_schema['enum'] if v is not None]
|
|
397
|
+
)
|
|
398
|
+
elif 'anyOf' in json_schema:
|
|
399
|
+
candidates = []
|
|
400
|
+
accepts_none = False
|
|
401
|
+
for v in json_schema['anyOf']:
|
|
402
|
+
candidate = _value_spec(v)
|
|
403
|
+
if candidate.frozen and candidate.default is None:
|
|
404
|
+
accepts_none = True
|
|
405
|
+
continue
|
|
406
|
+
candidates.append(candidate)
|
|
407
|
+
|
|
408
|
+
if len(candidates) == 1:
|
|
409
|
+
spec = candidates[0]
|
|
410
|
+
else:
|
|
411
|
+
spec = vs.Union(candidates)
|
|
412
|
+
if accepts_none:
|
|
413
|
+
spec = spec.noneable()
|
|
414
|
+
return spec
|
|
415
|
+
elif type_str == 'null':
|
|
416
|
+
return vs.Any().freeze(None)
|
|
417
|
+
elif type_str == 'boolean':
|
|
418
|
+
return vs.Bool(default=default)
|
|
419
|
+
elif type_str == 'integer':
|
|
420
|
+
minimum = json_schema.get('minimum')
|
|
421
|
+
maximum = json_schema.get('maximum')
|
|
422
|
+
return vs.Int(min_value=minimum, max_value=maximum, default=default)
|
|
423
|
+
elif type_str == 'number':
|
|
424
|
+
minimum = json_schema.get('minimum')
|
|
425
|
+
maximum = json_schema.get('maximum')
|
|
426
|
+
return vs.Float(min_value=minimum, max_value=maximum, default=default)
|
|
427
|
+
elif type_str == 'string':
|
|
428
|
+
pattern = json_schema.get('pattern')
|
|
429
|
+
return vs.Str(regex=pattern, default=default)
|
|
430
|
+
elif type_str == 'array':
|
|
431
|
+
items = json_schema.get('items')
|
|
432
|
+
return vs.List(_value_spec(items) if items else vs.Any(), default=default)
|
|
433
|
+
elif type_str == 'object':
|
|
434
|
+
schema = _json_schema_to_schema(
|
|
435
|
+
json_schema, defs, class_fn, add_json_schema_as_metadata
|
|
436
|
+
)
|
|
437
|
+
if class_fn is not None and 'title' in json_schema:
|
|
438
|
+
return vs.Object(class_fn(json_schema['title'], schema))
|
|
439
|
+
return vs.Dict(schema=schema if schema.fields else None)
|
|
440
|
+
raise ValueError(f'Unsupported type {type_str!r} in JSON schema.')
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def _json_schema_to_schema(
|
|
444
|
+
json_schema: Dict[str, Any],
|
|
445
|
+
defs: Dict[str, Type[Any]],
|
|
446
|
+
class_fn: Optional[Callable[[str, class_schema.Schema], Type[Any]]],
|
|
447
|
+
add_json_schema_as_metadata: bool,
|
|
448
|
+
) -> class_schema.Schema:
|
|
449
|
+
"""Converts a JSON schema to a schema."""
|
|
450
|
+
title = json_schema.get('title')
|
|
451
|
+
properties = json_schema.get('properties', {})
|
|
452
|
+
fields = []
|
|
453
|
+
required = set(json_schema.get('required', []))
|
|
454
|
+
for name, property_schema in properties.items():
|
|
455
|
+
value_spec = _json_schema_to_value_spec(
|
|
456
|
+
property_schema, defs, class_fn, add_json_schema_as_metadata
|
|
457
|
+
)
|
|
458
|
+
if name not in required and not value_spec.has_default:
|
|
459
|
+
value_spec = value_spec.noneable()
|
|
460
|
+
fields.append(
|
|
461
|
+
class_schema.Field(
|
|
462
|
+
name,
|
|
463
|
+
value_spec,
|
|
464
|
+
description=property_schema.get('description'),
|
|
465
|
+
metadata=(
|
|
466
|
+
dict(json_schema=property_schema)
|
|
467
|
+
if add_json_schema_as_metadata else None
|
|
468
|
+
)
|
|
469
|
+
)
|
|
470
|
+
)
|
|
471
|
+
additional_properties = json_schema.get('additionalProperties')
|
|
472
|
+
if additional_properties:
|
|
473
|
+
if isinstance(additional_properties, dict):
|
|
474
|
+
value_spec = _json_schema_to_value_spec(
|
|
475
|
+
additional_properties, defs, class_fn, add_json_schema_as_metadata
|
|
476
|
+
)
|
|
477
|
+
else:
|
|
478
|
+
value_spec = vs.Any()
|
|
479
|
+
fields.append(class_schema.Field(ks.StrKey(), value_spec))
|
|
480
|
+
return class_schema.Schema(
|
|
481
|
+
name=title,
|
|
482
|
+
description=json_schema.get('description'),
|
|
483
|
+
fields=fields,
|
|
484
|
+
allow_nonconst_keys=True,
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
@classmethod
|
|
489
|
+
def _value_spec_from_json_schema(
|
|
490
|
+
cls,
|
|
491
|
+
json_schema: Dict[str, Any],
|
|
492
|
+
class_fn: Optional[Callable[[str, class_schema.Schema], Type[Any]]] = None,
|
|
493
|
+
add_json_schema_as_metadata: bool = False,
|
|
494
|
+
) -> class_schema.ValueSpec:
|
|
495
|
+
"""Creates a PyGlove value spec from a JSON schema.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
json_schema: The JSON schema for a value spec.
|
|
499
|
+
class_fn: A function that creates a PyGlove class from a class name and a
|
|
500
|
+
schema. If None, all "object" type properties will be converted to
|
|
501
|
+
`pg.typing.Dict`. Otherwise, "object" type properties will be converted to
|
|
502
|
+
a class.
|
|
503
|
+
add_json_schema_as_metadata: Whether to add the JSON schema as field
|
|
504
|
+
metadata.
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
A PyGlove value spec.
|
|
508
|
+
"""
|
|
509
|
+
del cls
|
|
510
|
+
defs = {}
|
|
511
|
+
if '$defs' in json_schema:
|
|
512
|
+
for key, def_entry in json_schema['$defs'].items():
|
|
513
|
+
defs[key] = _json_schema_to_value_spec(
|
|
514
|
+
def_entry, defs, class_fn, add_json_schema_as_metadata
|
|
515
|
+
)
|
|
516
|
+
return _json_schema_to_value_spec(
|
|
517
|
+
json_schema, defs, class_fn, add_json_schema_as_metadata
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
@classmethod
|
|
522
|
+
def _schema_from_json_schema(
|
|
523
|
+
cls,
|
|
524
|
+
json_schema: Dict[str, Any],
|
|
525
|
+
class_fn: Optional[Callable[[str, class_schema.Schema], Type[Any]]] = None,
|
|
526
|
+
add_json_schema_as_metadata: bool = False,
|
|
527
|
+
) -> class_schema.Schema:
|
|
528
|
+
"""Creates a PyGlove schema from a JSON schema.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
json_schema: The JSON schema to convert.
|
|
532
|
+
class_fn: A function that creates a PyGlove class from a class name and a
|
|
533
|
+
schema. If None, all "object" type properties will be converted to
|
|
534
|
+
`pg.typing.Dict`. Otherwise, "object" type properties will be converted to
|
|
535
|
+
a class.
|
|
536
|
+
add_json_schema_as_metadata: Whether to add the JSON schema as field
|
|
537
|
+
metadata.
|
|
538
|
+
|
|
539
|
+
Returns:
|
|
540
|
+
A PyGlove schema.
|
|
541
|
+
"""
|
|
542
|
+
del cls
|
|
543
|
+
if json_schema.get('type') != 'object':
|
|
544
|
+
raise ValueError(
|
|
545
|
+
f'JSON schema is not an object type: {json_schema!r}'
|
|
546
|
+
)
|
|
547
|
+
return _json_schema_to_schema(
|
|
548
|
+
json_schema, {}, class_fn, add_json_schema_as_metadata
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class_schema.ValueSpec.from_json_schema = _value_spec_from_json_schema
|
|
553
|
+
class_schema.Schema.from_json_schema = _schema_from_json_schema
|
|
@@ -31,7 +31,7 @@ class Bar(pg_object.Object):
|
|
|
31
31
|
z: Optional[Foo]
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
class
|
|
34
|
+
class ToJsonSchemaTest(unittest.TestCase):
|
|
35
35
|
|
|
36
36
|
maxDiff = None
|
|
37
37
|
|
|
@@ -53,6 +53,10 @@ class JsonSchemaTest(unittest.TestCase):
|
|
|
53
53
|
self.assert_json_schema(bool, {
|
|
54
54
|
'type': 'boolean',
|
|
55
55
|
})
|
|
56
|
+
self.assert_json_schema(vs.Bool(default=True), {
|
|
57
|
+
'type': 'boolean',
|
|
58
|
+
'default': True,
|
|
59
|
+
})
|
|
56
60
|
|
|
57
61
|
def test_int(self):
|
|
58
62
|
self.assert_json_schema(int, {
|
|
@@ -62,9 +66,10 @@ class JsonSchemaTest(unittest.TestCase):
|
|
|
62
66
|
'type': 'integer',
|
|
63
67
|
'minimum': 0,
|
|
64
68
|
})
|
|
65
|
-
self.assert_json_schema(vs.Int(max_value=1), {
|
|
69
|
+
self.assert_json_schema(vs.Int(max_value=1, default=0), {
|
|
66
70
|
'type': 'integer',
|
|
67
71
|
'maximum': 1,
|
|
72
|
+
'default': 0,
|
|
68
73
|
})
|
|
69
74
|
|
|
70
75
|
def test_float(self):
|
|
@@ -75,30 +80,43 @@ class JsonSchemaTest(unittest.TestCase):
|
|
|
75
80
|
'type': 'number',
|
|
76
81
|
'minimum': 0.0,
|
|
77
82
|
})
|
|
78
|
-
self.assert_json_schema(vs.Float(max_value=1.0), {
|
|
83
|
+
self.assert_json_schema(vs.Float(max_value=1.0, default=0.0), {
|
|
79
84
|
'type': 'number',
|
|
80
85
|
'maximum': 1.0,
|
|
86
|
+
'default': 0.0,
|
|
81
87
|
})
|
|
82
88
|
|
|
83
89
|
def test_str(self):
|
|
84
90
|
self.assert_json_schema(str, {
|
|
85
91
|
'type': 'string',
|
|
86
92
|
})
|
|
87
|
-
self.assert_json_schema(vs.Str(regex='a.*'), {
|
|
93
|
+
self.assert_json_schema(vs.Str(regex='a.*', default='a1'), {
|
|
88
94
|
'type': 'string',
|
|
89
95
|
'pattern': 'a.*',
|
|
96
|
+
'default': 'a1',
|
|
90
97
|
})
|
|
91
98
|
|
|
92
99
|
def test_enum(self):
|
|
93
100
|
self.assert_json_schema(Literal['a', 1], {
|
|
94
101
|
'enum': ['a', 1]
|
|
95
102
|
})
|
|
103
|
+
self.assert_json_schema(vs.Enum(1, ['a', 1]), {
|
|
104
|
+
'enum': ['a', 1],
|
|
105
|
+
'default': 1,
|
|
106
|
+
})
|
|
96
107
|
self.assert_json_schema(Literal['a', 1, None], {
|
|
97
108
|
'anyOf': [
|
|
98
109
|
{'enum': ['a', 1]},
|
|
99
110
|
{'type': 'null'}
|
|
100
111
|
]
|
|
101
112
|
})
|
|
113
|
+
self.assert_json_schema(vs.Enum(None, ['a', 1, None]), {
|
|
114
|
+
'anyOf': [
|
|
115
|
+
{'enum': ['a', 1]},
|
|
116
|
+
{'type': 'null'}
|
|
117
|
+
],
|
|
118
|
+
'default': None
|
|
119
|
+
})
|
|
102
120
|
with self.assertRaisesRegex(
|
|
103
121
|
ValueError, 'Enum candidate .* is not supported'
|
|
104
122
|
):
|
|
@@ -111,6 +129,13 @@ class JsonSchemaTest(unittest.TestCase):
|
|
|
111
129
|
'type': 'integer',
|
|
112
130
|
}
|
|
113
131
|
})
|
|
132
|
+
self.assert_json_schema(vs.List(int, default=[1, 2]), {
|
|
133
|
+
'type': 'array',
|
|
134
|
+
'items': {
|
|
135
|
+
'type': 'integer',
|
|
136
|
+
},
|
|
137
|
+
'default': [1, 2],
|
|
138
|
+
})
|
|
114
139
|
|
|
115
140
|
def test_dict(self):
|
|
116
141
|
self.assert_json_schema(vs.Dict(), {
|
|
@@ -143,6 +168,15 @@ class JsonSchemaTest(unittest.TestCase):
|
|
|
143
168
|
{'type': 'null'},
|
|
144
169
|
]
|
|
145
170
|
})
|
|
171
|
+
self.assert_json_schema(vs.Union([int, vs.Union([str, int]).noneable()]), {
|
|
172
|
+
'anyOf': [
|
|
173
|
+
{'type': 'integer'},
|
|
174
|
+
{'type': 'string'},
|
|
175
|
+
# TODO(daiyip): Remove duplicates for nested Union in future.
|
|
176
|
+
{'type': 'integer'},
|
|
177
|
+
{'type': 'null'},
|
|
178
|
+
]
|
|
179
|
+
})
|
|
146
180
|
|
|
147
181
|
def test_any(self):
|
|
148
182
|
self.assert_json_schema(vs.Any(), {
|
|
@@ -155,6 +189,17 @@ class JsonSchemaTest(unittest.TestCase):
|
|
|
155
189
|
{'type': 'null'},
|
|
156
190
|
]
|
|
157
191
|
})
|
|
192
|
+
self.assert_json_schema(vs.Any(default=1), {
|
|
193
|
+
'anyOf': [
|
|
194
|
+
{'type': 'boolean'},
|
|
195
|
+
{'type': 'number'},
|
|
196
|
+
{'type': 'string'},
|
|
197
|
+
{'type': 'array'},
|
|
198
|
+
{'type': 'object', 'additionalProperties': True},
|
|
199
|
+
{'type': 'null'},
|
|
200
|
+
],
|
|
201
|
+
'default': 1,
|
|
202
|
+
})
|
|
158
203
|
|
|
159
204
|
def test_object(self):
|
|
160
205
|
class A:
|
|
@@ -176,9 +221,16 @@ class JsonSchemaTest(unittest.TestCase):
|
|
|
176
221
|
'additionalProperties': False,
|
|
177
222
|
}, include_type_name=False)
|
|
178
223
|
|
|
179
|
-
|
|
224
|
+
class B(pg_object.Object):
|
|
225
|
+
x: int
|
|
226
|
+
y: str
|
|
227
|
+
|
|
228
|
+
self.assert_json_schema(vs.Object(B), {
|
|
180
229
|
'type': 'object',
|
|
181
230
|
'properties': {
|
|
231
|
+
'_type': {
|
|
232
|
+
'const': B.__type_name__,
|
|
233
|
+
},
|
|
182
234
|
'x': {
|
|
183
235
|
'type': 'integer',
|
|
184
236
|
},
|
|
@@ -186,11 +238,30 @@ class JsonSchemaTest(unittest.TestCase):
|
|
|
186
238
|
'type': 'string',
|
|
187
239
|
},
|
|
188
240
|
},
|
|
189
|
-
'required': ['x', 'y'],
|
|
190
|
-
'title': '
|
|
241
|
+
'required': ['_type', 'x', 'y'],
|
|
242
|
+
'title': 'B',
|
|
191
243
|
'additionalProperties': False,
|
|
192
244
|
}, include_type_name=True)
|
|
193
245
|
|
|
246
|
+
self.assert_json_schema(vs.Object(B, default=B(x=1, y='a')), {
|
|
247
|
+
'type': 'object',
|
|
248
|
+
'properties': {
|
|
249
|
+
'x': {
|
|
250
|
+
'type': 'integer',
|
|
251
|
+
},
|
|
252
|
+
'y': {
|
|
253
|
+
'type': 'string',
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
'required': ['x', 'y'],
|
|
257
|
+
'title': 'B',
|
|
258
|
+
'additionalProperties': False,
|
|
259
|
+
'default': {
|
|
260
|
+
'x': 1,
|
|
261
|
+
'y': 'a',
|
|
262
|
+
},
|
|
263
|
+
}, include_type_name=False)
|
|
264
|
+
|
|
194
265
|
def test_pg_object(self):
|
|
195
266
|
|
|
196
267
|
class A(pg_object.Object):
|
|
@@ -230,14 +301,14 @@ class JsonSchemaTest(unittest.TestCase):
|
|
|
230
301
|
'additionalProperties': False,
|
|
231
302
|
}, include_type_name=True)
|
|
232
303
|
|
|
233
|
-
def
|
|
304
|
+
def test_pg_object_nested(self):
|
|
234
305
|
|
|
235
306
|
class A(pg_object.Object):
|
|
236
307
|
x: Annotated[int, 'field x']
|
|
237
308
|
y: str
|
|
238
309
|
|
|
239
310
|
class B(pg_object.Object):
|
|
240
|
-
z: A
|
|
311
|
+
z: A = A(x=1, y='a')
|
|
241
312
|
|
|
242
313
|
self.assert_json_schema(vs.Object(B), {
|
|
243
314
|
'$defs': {
|
|
@@ -266,10 +337,15 @@ class JsonSchemaTest(unittest.TestCase):
|
|
|
266
337
|
'const': B.__type_name__,
|
|
267
338
|
},
|
|
268
339
|
'z': {
|
|
269
|
-
'$ref': '#/$defs/A'
|
|
340
|
+
'$ref': '#/$defs/A',
|
|
341
|
+
'default': {
|
|
342
|
+
'_type': A.__type_name__,
|
|
343
|
+
'x': 1,
|
|
344
|
+
'y': 'a',
|
|
345
|
+
},
|
|
270
346
|
},
|
|
271
347
|
},
|
|
272
|
-
'required': ['_type'
|
|
348
|
+
'required': ['_type'],
|
|
273
349
|
'title': 'B',
|
|
274
350
|
'additionalProperties': False,
|
|
275
351
|
}, include_type_name=True)
|
|
@@ -299,7 +375,7 @@ class JsonSchemaTest(unittest.TestCase):
|
|
|
299
375
|
'additionalProperties': False,
|
|
300
376
|
},
|
|
301
377
|
},
|
|
302
|
-
'required': ['_type'
|
|
378
|
+
'required': ['_type'],
|
|
303
379
|
'title': 'B',
|
|
304
380
|
'additionalProperties': False,
|
|
305
381
|
}, include_type_name=True, inline_nested_refs=True)
|
|
@@ -357,6 +433,7 @@ class JsonSchemaTest(unittest.TestCase):
|
|
|
357
433
|
'type': 'null',
|
|
358
434
|
}
|
|
359
435
|
],
|
|
436
|
+
'default': None,
|
|
360
437
|
},
|
|
361
438
|
include_type_name=True,
|
|
362
439
|
include_subclasses=True,
|
|
@@ -473,5 +550,424 @@ class JsonSchemaTest(unittest.TestCase):
|
|
|
473
550
|
}
|
|
474
551
|
)
|
|
475
552
|
|
|
553
|
+
|
|
554
|
+
class FromJsonSchemaTest(unittest.TestCase):
|
|
555
|
+
|
|
556
|
+
def assert_value_spec(self, input_json_schema, expected_value_spec):
|
|
557
|
+
value_spec = vs.ValueSpec.from_json_schema(input_json_schema)
|
|
558
|
+
self.assertEqual(value_spec, expected_value_spec)
|
|
559
|
+
|
|
560
|
+
def test_bool(self):
|
|
561
|
+
self.assert_value_spec(
|
|
562
|
+
{
|
|
563
|
+
'type': 'boolean',
|
|
564
|
+
},
|
|
565
|
+
vs.Bool(),
|
|
566
|
+
)
|
|
567
|
+
self.assert_value_spec(
|
|
568
|
+
{
|
|
569
|
+
'type': 'boolean',
|
|
570
|
+
'default': True
|
|
571
|
+
},
|
|
572
|
+
vs.Bool(default=True),
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
def test_int(self):
|
|
576
|
+
self.assert_value_spec(
|
|
577
|
+
{
|
|
578
|
+
'type': 'integer',
|
|
579
|
+
},
|
|
580
|
+
vs.Int(),
|
|
581
|
+
)
|
|
582
|
+
self.assert_value_spec(
|
|
583
|
+
{
|
|
584
|
+
'type': 'integer',
|
|
585
|
+
'minimum': 0,
|
|
586
|
+
},
|
|
587
|
+
vs.Int(min_value=0),
|
|
588
|
+
)
|
|
589
|
+
self.assert_value_spec(
|
|
590
|
+
{
|
|
591
|
+
'type': 'integer',
|
|
592
|
+
'maximum': 1,
|
|
593
|
+
'default': 0,
|
|
594
|
+
},
|
|
595
|
+
vs.Int(max_value=1, default=0),
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
def test_number(self):
|
|
599
|
+
self.assert_value_spec(
|
|
600
|
+
{
|
|
601
|
+
'type': 'number',
|
|
602
|
+
},
|
|
603
|
+
vs.Float(),
|
|
604
|
+
)
|
|
605
|
+
self.assert_value_spec(
|
|
606
|
+
{
|
|
607
|
+
'type': 'number',
|
|
608
|
+
'minimum': 0.0,
|
|
609
|
+
},
|
|
610
|
+
vs.Float(min_value=0.0),
|
|
611
|
+
)
|
|
612
|
+
self.assert_value_spec(
|
|
613
|
+
{
|
|
614
|
+
'type': 'number',
|
|
615
|
+
'maximum': 1.0,
|
|
616
|
+
'default': 0.0,
|
|
617
|
+
},
|
|
618
|
+
vs.Float(max_value=1.0, default=0.0),
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
def test_str(self):
|
|
622
|
+
self.assert_value_spec(
|
|
623
|
+
{
|
|
624
|
+
'type': 'string',
|
|
625
|
+
},
|
|
626
|
+
vs.Str(),
|
|
627
|
+
)
|
|
628
|
+
self.assert_value_spec(
|
|
629
|
+
{
|
|
630
|
+
'type': 'string',
|
|
631
|
+
'pattern': 'a.*',
|
|
632
|
+
'default': 'a1',
|
|
633
|
+
},
|
|
634
|
+
vs.Str(regex='a.*', default='a1'),
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
def test_enum(self):
|
|
638
|
+
self.assert_value_spec(
|
|
639
|
+
{
|
|
640
|
+
'enum': ['a', 'b', 'c'],
|
|
641
|
+
'default': 'b',
|
|
642
|
+
},
|
|
643
|
+
vs.Enum('b', ['a', 'b', 'c']),
|
|
644
|
+
)
|
|
645
|
+
with self.assertRaisesRegex(
|
|
646
|
+
ValueError, 'Enum candidate .* is not supported'
|
|
647
|
+
):
|
|
648
|
+
vs.ValueSpec.from_json_schema({'enum': [{'x': 1}, {'y': 'abc'}]})
|
|
649
|
+
|
|
650
|
+
def test_null(self):
|
|
651
|
+
self.assert_value_spec(
|
|
652
|
+
{
|
|
653
|
+
'type': 'null',
|
|
654
|
+
},
|
|
655
|
+
vs.Any().freeze(None),
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
def test_any_of(self):
|
|
659
|
+
self.assert_value_spec(
|
|
660
|
+
{
|
|
661
|
+
'anyOf': [
|
|
662
|
+
{'type': 'integer'},
|
|
663
|
+
],
|
|
664
|
+
},
|
|
665
|
+
vs.Int(),
|
|
666
|
+
)
|
|
667
|
+
self.assert_value_spec(
|
|
668
|
+
{
|
|
669
|
+
'anyOf': [
|
|
670
|
+
{'type': 'integer'},
|
|
671
|
+
{'type': 'string'},
|
|
672
|
+
],
|
|
673
|
+
},
|
|
674
|
+
vs.Union([vs.Int(), vs.Str()]),
|
|
675
|
+
)
|
|
676
|
+
self.assert_value_spec(
|
|
677
|
+
{
|
|
678
|
+
'anyOf': [
|
|
679
|
+
{'type': 'integer'},
|
|
680
|
+
{'type': 'string'},
|
|
681
|
+
{'type': 'null'},
|
|
682
|
+
],
|
|
683
|
+
},
|
|
684
|
+
vs.Union([vs.Int(), vs.Str()]).noneable(),
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
def test_list(self):
|
|
688
|
+
self.assert_value_spec(
|
|
689
|
+
{
|
|
690
|
+
'type': 'array',
|
|
691
|
+
},
|
|
692
|
+
vs.List(vs.Any()),
|
|
693
|
+
)
|
|
694
|
+
self.assert_value_spec(
|
|
695
|
+
{
|
|
696
|
+
'type': 'array',
|
|
697
|
+
'items': {
|
|
698
|
+
'type': 'integer',
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
vs.List(vs.Int()),
|
|
702
|
+
)
|
|
703
|
+
self.assert_value_spec(
|
|
704
|
+
{
|
|
705
|
+
'type': 'array',
|
|
706
|
+
'items': {
|
|
707
|
+
'type': 'integer',
|
|
708
|
+
},
|
|
709
|
+
'default': [1, 2],
|
|
710
|
+
},
|
|
711
|
+
vs.List(vs.Int(), default=[1, 2]),
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
def test_dict(self):
|
|
715
|
+
self.assert_value_spec(
|
|
716
|
+
{
|
|
717
|
+
'type': 'object',
|
|
718
|
+
},
|
|
719
|
+
vs.Dict(),
|
|
720
|
+
)
|
|
721
|
+
self.assert_value_spec(
|
|
722
|
+
{
|
|
723
|
+
'type': 'object',
|
|
724
|
+
'properties': {
|
|
725
|
+
'a': {
|
|
726
|
+
'type': 'integer',
|
|
727
|
+
},
|
|
728
|
+
},
|
|
729
|
+
'required': ['a'],
|
|
730
|
+
'additionalProperties': False,
|
|
731
|
+
},
|
|
732
|
+
vs.Dict({'a': vs.Int()}),
|
|
733
|
+
)
|
|
734
|
+
self.assert_value_spec(
|
|
735
|
+
{
|
|
736
|
+
'type': 'object',
|
|
737
|
+
'additionalProperties': {'type': 'integer'},
|
|
738
|
+
},
|
|
739
|
+
vs.Dict([(ks.StrKey(), vs.Int())]),
|
|
740
|
+
)
|
|
741
|
+
self.assert_value_spec(
|
|
742
|
+
{
|
|
743
|
+
'type': 'object',
|
|
744
|
+
'additionalProperties': True,
|
|
745
|
+
},
|
|
746
|
+
vs.Dict([(ks.StrKey(), vs.Any())]),
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
def _cls_value_spec(self, input_json_schema):
|
|
750
|
+
def schema_to_class(name, schema):
|
|
751
|
+
class _Class(pg_object.Object):
|
|
752
|
+
pass
|
|
753
|
+
cls = _Class
|
|
754
|
+
cls.__name__ = name
|
|
755
|
+
cls.__doc__ = schema.description
|
|
756
|
+
cls.apply_schema(schema)
|
|
757
|
+
return cls
|
|
758
|
+
return vs.ValueSpec.from_json_schema(
|
|
759
|
+
input_json_schema, class_fn=schema_to_class
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
def test_simple_object(self):
|
|
763
|
+
cls_spec = self._cls_value_spec(
|
|
764
|
+
{
|
|
765
|
+
'type': 'object',
|
|
766
|
+
'title': 'A',
|
|
767
|
+
'description': 'Class A',
|
|
768
|
+
'properties': {
|
|
769
|
+
'x': {
|
|
770
|
+
'type': 'integer',
|
|
771
|
+
'description': 'field x',
|
|
772
|
+
},
|
|
773
|
+
'y': {
|
|
774
|
+
'type': 'string',
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
'required': ['x'],
|
|
778
|
+
'additionalProperties': False,
|
|
779
|
+
},
|
|
780
|
+
)
|
|
781
|
+
self.assertIsNone(cls_spec.cls(x=1).y)
|
|
782
|
+
self.assertEqual(cls_spec.cls.__name__, 'A')
|
|
783
|
+
self.assertEqual(cls_spec.cls.__doc__, 'Class A')
|
|
784
|
+
self.assertEqual(
|
|
785
|
+
cls_spec.cls.__schema__['x'], vs.Field('x', vs.Int(), 'field x')
|
|
786
|
+
)
|
|
787
|
+
self.assertEqual(
|
|
788
|
+
cls_spec.cls.__schema__['y'], vs.Field('y', vs.Str().noneable())
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
def test_nested_object(self):
|
|
792
|
+
cls_spec = self._cls_value_spec(
|
|
793
|
+
{
|
|
794
|
+
'type': 'object',
|
|
795
|
+
'title': 'A',
|
|
796
|
+
'description': 'Class A',
|
|
797
|
+
'properties': {
|
|
798
|
+
'x': {
|
|
799
|
+
'type': 'integer',
|
|
800
|
+
'description': 'field x',
|
|
801
|
+
},
|
|
802
|
+
'y': {
|
|
803
|
+
'type': 'object',
|
|
804
|
+
'title': 'B',
|
|
805
|
+
'description': 'Class B',
|
|
806
|
+
'properties': {
|
|
807
|
+
'z': {
|
|
808
|
+
'type': 'string',
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
'required': ['z'],
|
|
812
|
+
'additionalProperties': False,
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
'required': ['x'],
|
|
816
|
+
'additionalProperties': False,
|
|
817
|
+
},
|
|
818
|
+
)
|
|
819
|
+
self.assertIsNone(cls_spec.cls(x=1).y)
|
|
820
|
+
self.assertEqual(cls_spec.cls.__name__, 'A')
|
|
821
|
+
self.assertEqual(cls_spec.cls.__doc__, 'Class A')
|
|
822
|
+
self.assertEqual(
|
|
823
|
+
cls_spec.cls.__schema__['x'], vs.Field('x', vs.Int(), 'field x')
|
|
824
|
+
)
|
|
825
|
+
b_cls = cls_spec.cls.__schema__['y'].value.cls
|
|
826
|
+
self.assertEqual(b_cls.__schema__['z'], vs.Field('z', vs.Str()))
|
|
827
|
+
|
|
828
|
+
def test_simple_object_with_def(self):
|
|
829
|
+
cls_spec = self._cls_value_spec(
|
|
830
|
+
{
|
|
831
|
+
'$defs': {
|
|
832
|
+
'A': {
|
|
833
|
+
'type': 'object',
|
|
834
|
+
'title': 'A',
|
|
835
|
+
'description': 'Class A',
|
|
836
|
+
'properties': {
|
|
837
|
+
'x': {
|
|
838
|
+
'type': 'integer',
|
|
839
|
+
'description': 'field x',
|
|
840
|
+
'default': 1,
|
|
841
|
+
},
|
|
842
|
+
'y': {
|
|
843
|
+
'type': 'string',
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
'required': ['x'],
|
|
847
|
+
'additionalProperties': False,
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
'$ref': '#/$defs/A',
|
|
851
|
+
}
|
|
852
|
+
)
|
|
853
|
+
self.assertEqual(cls_spec.cls(y='a').x, 1)
|
|
854
|
+
self.assertEqual(cls_spec.cls.__name__, 'A')
|
|
855
|
+
self.assertEqual(cls_spec.cls.__doc__, 'Class A')
|
|
856
|
+
|
|
857
|
+
def test_complex_object_with_def(self):
|
|
858
|
+
cls_spec = self._cls_value_spec(
|
|
859
|
+
{
|
|
860
|
+
'$defs': {
|
|
861
|
+
'B': {
|
|
862
|
+
'type': 'object',
|
|
863
|
+
'title': 'B',
|
|
864
|
+
'description': 'Class B',
|
|
865
|
+
'properties': {
|
|
866
|
+
'z': {
|
|
867
|
+
'type': 'string',
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
'required': ['z'],
|
|
871
|
+
'additionalProperties': False,
|
|
872
|
+
},
|
|
873
|
+
'A': {
|
|
874
|
+
'type': 'object',
|
|
875
|
+
'title': 'A',
|
|
876
|
+
'description': 'Class A',
|
|
877
|
+
'properties': {
|
|
878
|
+
'x': {
|
|
879
|
+
'type': 'integer',
|
|
880
|
+
'description': 'field x',
|
|
881
|
+
'default': 1,
|
|
882
|
+
},
|
|
883
|
+
'y': {
|
|
884
|
+
'$ref': '#/$defs/B',
|
|
885
|
+
}
|
|
886
|
+
},
|
|
887
|
+
'required': ['x'],
|
|
888
|
+
'additionalProperties': False,
|
|
889
|
+
},
|
|
890
|
+
},
|
|
891
|
+
'$ref': '#/$defs/A',
|
|
892
|
+
}
|
|
893
|
+
)
|
|
894
|
+
self.assertIsNone(cls_spec.cls(x=1).y)
|
|
895
|
+
self.assertEqual(cls_spec.cls.__name__, 'A')
|
|
896
|
+
self.assertEqual(cls_spec.cls.__doc__, 'Class A')
|
|
897
|
+
self.assertEqual(
|
|
898
|
+
cls_spec.cls.__schema__['x'],
|
|
899
|
+
vs.Field('x', vs.Int(default=1), 'field x')
|
|
900
|
+
)
|
|
901
|
+
b_cls = cls_spec.cls.__schema__['y'].value.cls
|
|
902
|
+
self.assertEqual(b_cls.__name__, 'B')
|
|
903
|
+
self.assertEqual(b_cls.__doc__, 'Class B')
|
|
904
|
+
self.assertEqual(b_cls.__schema__['z'], vs.Field('z', vs.Str()))
|
|
905
|
+
|
|
906
|
+
with self.assertRaisesRegex(
|
|
907
|
+
ValueError, 'Reference .* not defined'
|
|
908
|
+
):
|
|
909
|
+
self._cls_value_spec(
|
|
910
|
+
{
|
|
911
|
+
'$defs': {
|
|
912
|
+
'A': {
|
|
913
|
+
'type': 'object',
|
|
914
|
+
'title': 'A',
|
|
915
|
+
'description': 'Class A',
|
|
916
|
+
'properties': {
|
|
917
|
+
'x': {
|
|
918
|
+
'$ref': '#/$defs/B',
|
|
919
|
+
}
|
|
920
|
+
},
|
|
921
|
+
'required': ['x'],
|
|
922
|
+
'additionalProperties': False,
|
|
923
|
+
},
|
|
924
|
+
# B should go before A.
|
|
925
|
+
'B': {
|
|
926
|
+
'type': 'object',
|
|
927
|
+
'title': 'B',
|
|
928
|
+
'description': 'Class B',
|
|
929
|
+
'properties': {
|
|
930
|
+
'z': {
|
|
931
|
+
'type': 'string',
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
'required': ['z'],
|
|
935
|
+
'additionalProperties': False,
|
|
936
|
+
},
|
|
937
|
+
},
|
|
938
|
+
'$ref': '#/$defs/A',
|
|
939
|
+
}
|
|
940
|
+
)
|
|
941
|
+
|
|
942
|
+
def test_unsupported_json_schema(self):
|
|
943
|
+
with self.assertRaisesRegex(
|
|
944
|
+
ValueError, 'Unsupported type .* in JSON schema'
|
|
945
|
+
):
|
|
946
|
+
vs.ValueSpec.from_json_schema({'type': 'oneOf'})
|
|
947
|
+
|
|
948
|
+
def test_schema_from_json_schema(self):
|
|
949
|
+
schema = vs.Schema.from_json_schema(
|
|
950
|
+
{
|
|
951
|
+
'type': 'object',
|
|
952
|
+
'title': 'A',
|
|
953
|
+
'description': 'Class A',
|
|
954
|
+
'properties': {
|
|
955
|
+
'x': {
|
|
956
|
+
'type': 'integer',
|
|
957
|
+
},
|
|
958
|
+
},
|
|
959
|
+
'required': ['x'],
|
|
960
|
+
'additionalProperties': False,
|
|
961
|
+
},
|
|
962
|
+
)
|
|
963
|
+
self.assertEqual(schema.description, 'Class A')
|
|
964
|
+
self.assertEqual(list(schema.fields.keys()), ['x'])
|
|
965
|
+
self.assertEqual(schema.fields['x'].value, vs.Int())
|
|
966
|
+
|
|
967
|
+
with self.assertRaisesRegex(
|
|
968
|
+
ValueError, 'JSON schema is not an object type'
|
|
969
|
+
):
|
|
970
|
+
vs.Schema.from_json_schema({'type': 'integer'})
|
|
971
|
+
|
|
476
972
|
if __name__ == '__main__':
|
|
477
973
|
unittest.main()
|
pyglove/core/utils/contextual.py
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import contextlib
|
|
17
17
|
import dataclasses
|
|
18
|
+
import inspect
|
|
18
19
|
import threading
|
|
19
20
|
from typing import Any, Callable, ContextManager, Iterator, Optional
|
|
20
21
|
|
|
@@ -89,10 +90,14 @@ def with_contextual_override(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
|
89
90
|
with contextual_override() as current_context:
|
|
90
91
|
pass
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
if inspect.iscoroutinefunction(func):
|
|
94
|
+
async def _func(*args, **kwargs) -> Any:
|
|
95
|
+
with contextual_override(**current_context):
|
|
96
|
+
return await func(*args, **kwargs)
|
|
97
|
+
else:
|
|
98
|
+
def _func(*args, **kwargs) -> Any:
|
|
99
|
+
with contextual_override(**current_context):
|
|
100
|
+
return func(*args, **kwargs)
|
|
96
101
|
return _func
|
|
97
102
|
|
|
98
103
|
|
|
@@ -11,6 +11,7 @@
|
|
|
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
|
+
import asyncio
|
|
14
15
|
import concurrent.futures
|
|
15
16
|
import unittest
|
|
16
17
|
from pyglove.core.utils import contextual
|
|
@@ -83,6 +84,15 @@ class ContextualTest(unittest.TestCase):
|
|
|
83
84
|
[3]
|
|
84
85
|
)
|
|
85
86
|
|
|
87
|
+
def test_with_contextual_override_async_func(self):
|
|
88
|
+
async def func(i):
|
|
89
|
+
del i
|
|
90
|
+
return contextual.contextual_value('x')
|
|
91
|
+
|
|
92
|
+
with contextual.contextual_override(x=3):
|
|
93
|
+
self.assertEqual(
|
|
94
|
+
asyncio.run(contextual.with_contextual_override(func)(0)), 3
|
|
95
|
+
)
|
|
86
96
|
|
|
87
97
|
if __name__ == '__main__':
|
|
88
98
|
unittest.main()
|
|
@@ -120,13 +120,13 @@ pyglove/core/typing/callable_ext.py,sha256=PiBQWPeUAH7Lgmf2xKCZqgK7N0OSrTdbnEkV8
|
|
|
120
120
|
pyglove/core/typing/callable_ext_test.py,sha256=TnWKU4_ZjvpbHZFtFHgFvCMDiCos8VmLlODcM_7Xg8M,10156
|
|
121
121
|
pyglove/core/typing/callable_signature.py,sha256=DRpt7aShfkn8pb3SCiZzS_27eHbkQ_d2UB8BUhJjs0Q,27176
|
|
122
122
|
pyglove/core/typing/callable_signature_test.py,sha256=iQmHsKPhJPQlMikDhEyxKyq7yWyXI9juKCLYgKhrH3U,25145
|
|
123
|
-
pyglove/core/typing/class_schema.py,sha256=
|
|
123
|
+
pyglove/core/typing/class_schema.py,sha256=jICxKo6Ubu35FJO1ei6iOqHB9I204Wtea5UGng2_hTE,55897
|
|
124
124
|
pyglove/core/typing/class_schema_test.py,sha256=sJkE7ndDSIKb0EUcjZiVFOeJYDI7Hdu2GdPJCMgZxrI,29556
|
|
125
125
|
pyglove/core/typing/custom_typing.py,sha256=qdnIKHWNt5kZAAFdpQXra8bBu6RljMbbJ_YDG2mhAUA,2205
|
|
126
126
|
pyglove/core/typing/inspect.py,sha256=VLSz1KAunNm2hx0eEMjiwxKLl9FHlKr9nHelLT25iEA,7726
|
|
127
127
|
pyglove/core/typing/inspect_test.py,sha256=xclevobF0X8c_B5b1q1dkBJZN1TsVA1RUhk5l25DUCM,10248
|
|
128
|
-
pyglove/core/typing/json_schema.py,sha256=
|
|
129
|
-
pyglove/core/typing/json_schema_test.py,sha256=
|
|
128
|
+
pyglove/core/typing/json_schema.py,sha256=F7uWXN8Tk4cAydh8FE9jncaP_n7KEvgprvNHeagTvqE,17871
|
|
129
|
+
pyglove/core/typing/json_schema_test.py,sha256=ZxMO2xgKiELNDzoQ84cmXsyCtFA0Ltn1I4gZjF-3efY,26482
|
|
130
130
|
pyglove/core/typing/key_specs.py,sha256=-7xjCuUGoQgD0sMafsRFNlw3S4f1r-7t5OO4ev5bbeI,9225
|
|
131
131
|
pyglove/core/typing/key_specs_test.py,sha256=5zornpyHMAYoRaG8KDXHiQ3obu9UfRp3399lBeUNTPk,6499
|
|
132
132
|
pyglove/core/typing/pytype_support.py,sha256=lyX11WVbCwoOi5tTQ90pEOS-yvo_6iEi7Lxbp-nXu2A,2069
|
|
@@ -139,8 +139,8 @@ pyglove/core/typing/value_specs_test.py,sha256=eGXVxdduIM-oEaapJS9Kh7WSQHRUFegLI
|
|
|
139
139
|
pyglove/core/utils/__init__.py,sha256=2aw4n1kYG9xlX2tWI-H5i25cBuK1ME9Lmf-F31VlKEk,8657
|
|
140
140
|
pyglove/core/utils/common_traits.py,sha256=PWxOgPhG5H60ZwfO8xNAEGRjFUqqDZQBWQYomOfvdy8,3640
|
|
141
141
|
pyglove/core/utils/common_traits_test.py,sha256=DIuZB_1xfmeTVfWnGOguDQcDAM_iGgBOe8C-5CsIqBc,1122
|
|
142
|
-
pyglove/core/utils/contextual.py,sha256=
|
|
143
|
-
pyglove/core/utils/contextual_test.py,sha256=
|
|
142
|
+
pyglove/core/utils/contextual.py,sha256=_EO_ubCcmI81QyYyyucm3QcH1asQWSeXvT2Xa4KONs8,5355
|
|
143
|
+
pyglove/core/utils/contextual_test.py,sha256=g5P6IBf6XgQTUW83QTDC1K5JQJ9bxP6ULiAj2ko4mMo,3552
|
|
144
144
|
pyglove/core/utils/docstr_utils.py,sha256=5BY40kXozPKVGOB0eN8jy1P5_GHIzqFJ9FXAu_kzxaw,5119
|
|
145
145
|
pyglove/core/utils/docstr_utils_test.py,sha256=TBHNwvhGyyoEs7dNOv5bW7h3B_y2smDyoAR9uPDiidI,4179
|
|
146
146
|
pyglove/core/utils/error_utils.py,sha256=5if1LlouHMx9EkZt-rhlkDU53Qc-hmXgjhJHhhI125s,6392
|
|
@@ -218,8 +218,8 @@ pyglove/ext/scalars/randoms.py,sha256=LkMIIx7lOq_lvJvVS3BrgWGuWl7Pi91-lA-O8x_gZs
|
|
|
218
218
|
pyglove/ext/scalars/randoms_test.py,sha256=nEhiqarg8l_5EOucp59CYrpO2uKxS1pe0hmBdZUzRNM,2000
|
|
219
219
|
pyglove/ext/scalars/step_wise.py,sha256=IDw3tuTpv0KVh7AN44W43zqm1-E0HWPUlytWOQC9w3Y,3789
|
|
220
220
|
pyglove/ext/scalars/step_wise_test.py,sha256=TL1vJ19xVx2t5HKuyIzGoogF7N3Rm8YhLE6JF7i0iy8,2540
|
|
221
|
-
pyglove-0.5.0.
|
|
222
|
-
pyglove-0.5.0.
|
|
223
|
-
pyglove-0.5.0.
|
|
224
|
-
pyglove-0.5.0.
|
|
225
|
-
pyglove-0.5.0.
|
|
221
|
+
pyglove-0.5.0.dev202510170226.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
222
|
+
pyglove-0.5.0.dev202510170226.dist-info/METADATA,sha256=Hk2SsgTsNgeAfgxQYyDRdCRj2I9g7Tq9sUdwdQ3GuMM,7089
|
|
223
|
+
pyglove-0.5.0.dev202510170226.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
224
|
+
pyglove-0.5.0.dev202510170226.dist-info/top_level.txt,sha256=wITzJSKcj8GZUkbq-MvUQnFadkiuAv_qv5qQMw0fIow,8
|
|
225
|
+
pyglove-0.5.0.dev202510170226.dist-info/RECORD,,
|
|
File without changes
|
{pyglove-0.5.0.dev202510150810.dist-info → pyglove-0.5.0.dev202510170226.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{pyglove-0.5.0.dev202510150810.dist-info → pyglove-0.5.0.dev202510170226.dist-info}/top_level.txt
RENAMED
|
File without changes
|