pyglove 0.5.0.dev202508250811__py3-none-any.whl → 0.5.0.dev202511300809__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 (44) hide show
  1. pyglove/core/__init__.py +8 -1
  2. pyglove/core/geno/base.py +7 -3
  3. pyglove/core/io/file_system.py +295 -2
  4. pyglove/core/io/file_system_test.py +291 -0
  5. pyglove/core/logging.py +45 -1
  6. pyglove/core/logging_test.py +12 -21
  7. pyglove/core/monitoring.py +657 -0
  8. pyglove/core/monitoring_test.py +289 -0
  9. pyglove/core/symbolic/__init__.py +7 -0
  10. pyglove/core/symbolic/base.py +89 -35
  11. pyglove/core/symbolic/base_test.py +3 -3
  12. pyglove/core/symbolic/dict.py +31 -12
  13. pyglove/core/symbolic/dict_test.py +49 -0
  14. pyglove/core/symbolic/list.py +17 -3
  15. pyglove/core/symbolic/list_test.py +24 -2
  16. pyglove/core/symbolic/object.py +3 -1
  17. pyglove/core/symbolic/object_test.py +13 -10
  18. pyglove/core/symbolic/ref.py +19 -7
  19. pyglove/core/symbolic/ref_test.py +94 -7
  20. pyglove/core/symbolic/unknown_symbols.py +147 -0
  21. pyglove/core/symbolic/unknown_symbols_test.py +100 -0
  22. pyglove/core/typing/annotation_conversion.py +8 -1
  23. pyglove/core/typing/annotation_conversion_test.py +14 -19
  24. pyglove/core/typing/class_schema.py +24 -1
  25. pyglove/core/typing/json_schema.py +221 -8
  26. pyglove/core/typing/json_schema_test.py +508 -12
  27. pyglove/core/typing/type_conversion.py +17 -3
  28. pyglove/core/typing/type_conversion_test.py +7 -2
  29. pyglove/core/typing/value_specs.py +5 -1
  30. pyglove/core/typing/value_specs_test.py +5 -0
  31. pyglove/core/utils/__init__.py +2 -0
  32. pyglove/core/utils/contextual.py +9 -4
  33. pyglove/core/utils/contextual_test.py +10 -0
  34. pyglove/core/utils/error_utils.py +59 -25
  35. pyglove/core/utils/json_conversion.py +360 -63
  36. pyglove/core/utils/json_conversion_test.py +146 -13
  37. pyglove/core/views/html/controls/tab.py +33 -0
  38. pyglove/core/views/html/controls/tab_test.py +37 -0
  39. pyglove/ext/evolution/base_test.py +1 -1
  40. {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/METADATA +8 -1
  41. {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/RECORD +44 -40
  42. {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/WHEEL +0 -0
  43. {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.dist-info}/licenses/LICENSE +0 -0
  44. {pyglove-0.5.0.dev202508250811.dist-info → pyglove-0.5.0.dev202511300809.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 JsonSchemaTest(unittest.TestCase):
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
- self.assert_json_schema(vs.Object(A), {
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': 'A',
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 test_pg_object_nessted(self):
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', 'z'],
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', 'z'],
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
- register_converter(int, datetime.datetime, datetime.datetime.utcfromtimestamp)
139
- register_converter(datetime.datetime, int,
140
- lambda x: calendar.timegm(x.timetuple()))
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
- now = datetime.datetime.utcfromtimestamp(timestamp)
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."""