pyglove 0.5.0.dev202510020810__py3-none-any.whl → 0.5.0.dev202512280810__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/geno/base.py +7 -3
- pyglove/core/io/file_system.py +452 -2
- pyglove/core/io/file_system_test.py +442 -0
- pyglove/core/monitoring.py +213 -90
- pyglove/core/monitoring_test.py +82 -29
- pyglove/core/symbolic/__init__.py +7 -0
- pyglove/core/symbolic/base.py +89 -35
- pyglove/core/symbolic/base_test.py +3 -3
- pyglove/core/symbolic/dict.py +31 -12
- pyglove/core/symbolic/dict_test.py +49 -0
- pyglove/core/symbolic/list.py +17 -3
- pyglove/core/symbolic/list_test.py +24 -2
- pyglove/core/symbolic/object.py +3 -1
- pyglove/core/symbolic/object_test.py +13 -10
- pyglove/core/symbolic/ref.py +19 -7
- pyglove/core/symbolic/ref_test.py +94 -7
- pyglove/core/symbolic/unknown_symbols.py +147 -0
- pyglove/core/symbolic/unknown_symbols_test.py +100 -0
- pyglove/core/typing/annotation_conversion.py +8 -1
- pyglove/core/typing/annotation_conversion_test.py +14 -19
- pyglove/core/typing/class_schema.py +24 -1
- pyglove/core/typing/json_schema.py +221 -8
- pyglove/core/typing/json_schema_test.py +508 -12
- pyglove/core/typing/type_conversion.py +17 -3
- pyglove/core/typing/type_conversion_test.py +7 -2
- pyglove/core/typing/value_specs.py +5 -1
- pyglove/core/typing/value_specs_test.py +5 -0
- pyglove/core/utils/__init__.py +1 -0
- pyglove/core/utils/contextual.py +9 -4
- pyglove/core/utils/contextual_test.py +10 -0
- pyglove/core/utils/json_conversion.py +360 -63
- pyglove/core/utils/json_conversion_test.py +146 -13
- pyglove/core/views/html/controls/tab.py +33 -0
- pyglove/core/views/html/controls/tab_test.py +37 -0
- pyglove/ext/evolution/base_test.py +1 -1
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/METADATA +8 -1
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/RECORD +40 -38
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/WHEEL +0 -0
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/licenses/LICENSE +0 -0
- {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/top_level.txt +0 -0
|
@@ -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()
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import calendar
|
|
17
17
|
import datetime
|
|
18
|
+
import sys
|
|
18
19
|
from typing import Any, Callable, Optional, Tuple, Type, Union
|
|
19
20
|
|
|
20
21
|
from pyglove.core import utils
|
|
@@ -135,9 +136,22 @@ def _register_builtin_converters():
|
|
|
135
136
|
register_converter(int, float, float)
|
|
136
137
|
|
|
137
138
|
# int <=> datetime.datetime.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
if sys.version_info >= (3, 11):
|
|
140
|
+
register_converter(
|
|
141
|
+
int,
|
|
142
|
+
datetime.datetime,
|
|
143
|
+
lambda x: datetime.datetime.fromtimestamp(x, datetime.UTC)
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
register_converter(
|
|
147
|
+
int, datetime.datetime, datetime.datetime.utcfromtimestamp
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
register_converter(
|
|
151
|
+
datetime.datetime,
|
|
152
|
+
int,
|
|
153
|
+
lambda x: calendar.timegm(x.timetuple())
|
|
154
|
+
)
|
|
141
155
|
|
|
142
156
|
# string <=> KeyPath.
|
|
143
157
|
register_converter(str, utils.KeyPath, utils.KeyPath.parse)
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import calendar
|
|
15
15
|
import datetime
|
|
16
|
+
import sys
|
|
16
17
|
import typing
|
|
17
18
|
import unittest
|
|
18
19
|
|
|
@@ -130,12 +131,16 @@ class BuiltInConversionsTest(unittest.TestCase):
|
|
|
130
131
|
def test_datetime_to_int(self):
|
|
131
132
|
"""Test built-in converter between int and datetime.datetime."""
|
|
132
133
|
timestamp = calendar.timegm(datetime.datetime.now().timetuple())
|
|
133
|
-
|
|
134
|
+
if sys.version_info >= (3, 11):
|
|
135
|
+
now = datetime.datetime.fromtimestamp(timestamp, datetime.UTC)
|
|
136
|
+
else:
|
|
137
|
+
now = datetime.datetime.utcfromtimestamp(timestamp)
|
|
134
138
|
self.assertEqual(vs.Object(datetime.datetime).apply(timestamp), now)
|
|
135
139
|
self.assertEqual(vs.Int().apply(now), timestamp)
|
|
136
140
|
self.assertEqual(
|
|
137
141
|
type_conversion.get_json_value_converter(datetime.datetime)(now),
|
|
138
|
-
timestamp
|
|
142
|
+
timestamp
|
|
143
|
+
)
|
|
139
144
|
|
|
140
145
|
def test_keypath_to_str(self):
|
|
141
146
|
"""Test built-in converter between string and KeyPath."""
|