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,16 +11,14 @@
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.Dict."""
15
-
16
14
  import copy
17
15
  import inspect
18
16
  import io
19
17
  import pickle
20
18
  import unittest
21
19
 
22
- from pyglove.core import object_utils
23
20
  from pyglove.core import typing as pg_typing
21
+ from pyglove.core import utils
24
22
  from pyglove.core.symbolic import base
25
23
  from pyglove.core.symbolic import flags
26
24
  from pyglove.core.symbolic import inferred
@@ -31,7 +29,7 @@ from pyglove.core.symbolic.pure_symbolic import NonDeterministic
31
29
  from pyglove.core.symbolic.pure_symbolic import PureSymbolic
32
30
 
33
31
 
34
- MISSING_VALUE = object_utils.MISSING_VALUE
32
+ MISSING_VALUE = utils.MISSING_VALUE
35
33
 
36
34
 
37
35
  class DictTest(unittest.TestCase):
@@ -44,14 +42,14 @@ class DictTest(unittest.TestCase):
44
42
  self.assertEqual(len(sd), 0)
45
43
 
46
44
  # Schemaless dict created from a regular dict.
47
- sd = Dict({'a': 1})
45
+ sd = Dict({'a': 1, 1: 1})
48
46
  self.assertIsNone(sd.value_spec)
49
- self.assertEqual(sd, dict(a=1))
47
+ self.assertEqual(sd, {'a': 1, 1: 1})
50
48
 
51
49
  # Schemaless dict created from key value pairs.
52
- sd = Dict((('a', 1),))
50
+ sd = Dict((('a', 1), (1, 1)))
53
51
  self.assertIsNone(sd.value_spec)
54
- self.assertEqual(sd, dict(a=1))
52
+ self.assertEqual(sd, {'a': 1, 1: 1})
55
53
 
56
54
  # Schemaless dict created from keyword args.
57
55
  sd = Dict(a=1)
@@ -59,9 +57,9 @@ class DictTest(unittest.TestCase):
59
57
  self.assertEqual(sd, dict(a=1))
60
58
 
61
59
  # Schemaless dict created from both a regular dict and keyword args.
62
- sd = Dict({'a': 1}, a=2)
60
+ sd = Dict({'a': 1, 1: 1}, a=2)
63
61
  self.assertIsNone(sd.value_spec)
64
- self.assertEqual(sd, dict(a=2))
62
+ self.assertEqual(sd, {'a': 2, 1: 1})
65
63
 
66
64
  # Schematized dict.
67
65
  vs = pg_typing.Dict([('a', pg_typing.Int())])
@@ -305,8 +303,8 @@ class DictTest(unittest.TestCase):
305
303
  with self.assertRaisesRegex(
306
304
  base.WritePermissionError, 'Cannot modify field of a sealed Dict.'):
307
305
  sd['b'] = 1
308
- with self.assertRaisesRegex(KeyError, 'Key must be string type'):
309
- sd[0] = 1
306
+ with self.assertRaisesRegex(KeyError, 'Key must be string or int type'):
307
+ sd[0.5] = 1
310
308
 
311
309
  # Set item in a schematized dict.
312
310
  sd = Dict(value_spec=pg_typing.Dict([('a', pg_typing.Int(default=0))]))
@@ -786,35 +784,45 @@ class DictTest(unittest.TestCase):
786
784
  sd.missing_values(flatten=False), {'a': MISSING_VALUE})
787
785
 
788
786
  # A non-schematized dict has a schematized child.
789
- sd = Dict(x=sd)
787
+ sd = Dict({'x': sd, 1: sd})
790
788
  self.assertIsNone(sd.value_spec)
791
- self.assertEqual(sd.missing_values(), {'x.a': MISSING_VALUE})
789
+ self.assertEqual(
790
+ sd.missing_values(), {'x.a': MISSING_VALUE, '[1].a': MISSING_VALUE}
791
+ )
792
792
 
793
793
  def test_sym_has(self):
794
- sd = Dict(x=1, y=Dict(z=2))
794
+ sd = Dict({'x': 1, 1: dict(a=3), 'y': Dict({'z': 2, 2: 3})})
795
795
  self.assertTrue(sd.sym_has('x'))
796
+ self.assertTrue(sd.sym_has(1))
796
797
  self.assertTrue(sd.sym_has('y.z'))
797
- self.assertTrue(sd.sym_has(object_utils.KeyPath.parse('y.z')))
798
+ self.assertTrue(sd.sym_has('[1].a'))
799
+ self.assertTrue(sd.sym_has('y[2]'))
800
+ self.assertTrue(sd.sym_has(utils.KeyPath.parse('y.z')))
798
801
  self.assertFalse(sd.sym_has('x.z'))
799
802
 
800
803
  def test_sym_get(self):
801
- sd = Dict(x=1, y=Dict(z=2))
804
+ sd = Dict({'x': 1, 1: dict(a=3), 'y': Dict({'z': 2, 2: 3})})
802
805
  self.assertEqual(sd.sym_get('x'), 1)
806
+ self.assertEqual(sd.sym_get(1), dict(a=3))
803
807
  self.assertEqual(sd.sym_get('y.z'), 2)
808
+ self.assertEqual(sd.sym_get('[1].a'), 3)
809
+ self.assertEqual(sd.sym_get('y[2]'), 3)
804
810
  self.assertIsNone(sd.sym_get('x.z', None))
805
811
  with self.assertRaisesRegex(
806
812
  KeyError, 'Cannot query sub-key \'z\' of object.'):
807
813
  sd.sym_get('x.z')
808
814
 
809
815
  def test_sym_hasattr(self):
810
- sd = Dict(x=1, y=Dict(z=2))
816
+ sd = Dict({'x': 1, 1: dict(a=3), 'y': Dict({'z': 2, 2: 3})})
811
817
  self.assertTrue(sd.sym_hasattr('x'))
818
+ self.assertTrue(sd.sym_hasattr(1))
812
819
  self.assertFalse(sd.sym_hasattr('y.z'))
813
820
  self.assertFalse(sd.sym_hasattr('a'))
814
821
 
815
822
  def test_sym_getattr(self):
816
- sd = Dict(x=1, y=Dict(z=2))
823
+ sd = Dict({'x': 1, 1: dict(a=3), 'y': Dict({'z': 2, 2: 3})})
817
824
  self.assertEqual(sd.sym_getattr('x'), 1)
825
+ self.assertEqual(sd.sym_getattr(1), dict(a=3))
818
826
  self.assertIsNone(sd.sym_getattr('a', None))
819
827
  with self.assertRaisesRegex(
820
828
  AttributeError,
@@ -832,8 +840,9 @@ class DictTest(unittest.TestCase):
832
840
  with self.assertRaisesRegex(AttributeError, 'z'):
833
841
  _ = sd.sym_inferred('z')
834
842
 
835
- sd = Dict(y=1, x=Dict(x=Dict(y=inferred.ValueFromParentChain())))
843
+ sd = Dict(y=1, x={'x': Dict(y=inferred.ValueFromParentChain()), 1: 2})
836
844
  self.assertEqual(sd.x.x.y, 1)
845
+ self.assertEqual(sd.x[1], 2)
837
846
 
838
847
  def test_sym_field(self):
839
848
  sd = Dict(x=1, y=Dict(z=2))
@@ -859,9 +868,9 @@ class DictTest(unittest.TestCase):
859
868
  self.assertIs(sd.sym_attr_field('x'), spec.schema.get_field('x'))
860
869
 
861
870
  def test_sym_keys(self):
862
- sd = Dict(x=1, y=2)
871
+ sd = Dict({'x': 1, 'y': 2, 1: 3})
863
872
  self.assertEqual(next(sd.sym_keys()), 'x')
864
- self.assertEqual(list(sd.sym_keys()), ['x', 'y'])
873
+ self.assertEqual(list(sd.sym_keys()), ['x', 'y', 1])
865
874
 
866
875
  sd = Dict(x=1, z=3, y=2, value_spec=pg_typing.Dict([
867
876
  (pg_typing.StrKey(), pg_typing.Int())
@@ -874,9 +883,9 @@ class DictTest(unittest.TestCase):
874
883
  self.assertEqual(list(sd.sym_keys()), ['x', 'y'])
875
884
 
876
885
  def test_sym_values(self):
877
- sd = Dict(x=1, y=2)
886
+ sd = Dict({'x': 1, 'y': 2, 1: 3})
878
887
  self.assertEqual(next(sd.sym_values()), 1)
879
- self.assertEqual(list(sd.sym_values()), [1, 2])
888
+ self.assertEqual(list(sd.sym_values()), [1, 2, 3])
880
889
 
881
890
  sd = Dict(x=1, z=3, y=2, value_spec=pg_typing.Dict([
882
891
  (pg_typing.StrKey(), pg_typing.Int())
@@ -891,9 +900,9 @@ class DictTest(unittest.TestCase):
891
900
  )
892
901
 
893
902
  def test_sym_items(self):
894
- sd = Dict(x=1, y=2)
903
+ sd = Dict({'x': 1, 'y': 2, 1: 3})
895
904
  self.assertEqual(next(sd.sym_items()), ('x', 1))
896
- self.assertEqual(list(sd.sym_items()), [('x', 1), ('y', 2)])
905
+ self.assertEqual(list(sd.sym_items()), [('x', 1), ('y', 2), (1, 3)])
897
906
 
898
907
  sd = Dict(x=1, z=3, y=2, value_spec=pg_typing.Dict([
899
908
  (pg_typing.StrKey(), pg_typing.Int())
@@ -917,9 +926,9 @@ class DictTest(unittest.TestCase):
917
926
 
918
927
  def test_sym_rebind(self):
919
928
  # Refer to RebindTest for more detailed tests.
920
- sd = Dict(x=1, y=2)
921
- sd.sym_rebind(x=2)
922
- self.assertEqual(sd, dict(x=2, y=2))
929
+ sd = Dict({'x': 1, 'y': 2, 1: 3})
930
+ sd.sym_rebind({1: 4}, x=2)
931
+ self.assertEqual(sd, {'x': 2, 'y': 2, 1: 4})
923
932
 
924
933
  def test_sym_clone(self):
925
934
  class A:
@@ -997,6 +1006,12 @@ class DictTest(unittest.TestCase):
997
1006
  y: int = 1
998
1007
  use_symbolic_comparison = True
999
1008
 
1009
+ sd = Dict({'x': A(2), 1: B(2)})
1010
+ self.assertEqual(sd.sym_nondefault(), {'x.x': 2, '[1].y': 2})
1011
+
1012
+ sd = Dict({'x': A(1), 1: B(1)})
1013
+ self.assertEqual(sd.sym_nondefault(), {'x.x': 1})
1014
+
1000
1015
  sd = Dict(x=1, y=dict(a1=A(1)), value_spec=pg_typing.Dict([
1001
1016
  ('x', pg_typing.Int(default=0)),
1002
1017
  ('y', pg_typing.Dict([
@@ -1080,7 +1095,7 @@ class DictTest(unittest.TestCase):
1080
1095
  self.assertTrue(Dict().sym_eq(Dict()))
1081
1096
  self.assertTrue(base.eq(Dict(), Dict()))
1082
1097
 
1083
- self.assertEqual(Dict(a=1), Dict(a=1))
1098
+ self.assertEqual(Dict({'a': 1, 1: 2}), Dict({'a': 1, 1: 2}))
1084
1099
  self.assertTrue(Dict(a=1).sym_eq(Dict(a=1)))
1085
1100
  self.assertTrue(base.eq(Dict(a=1), Dict(a=1)))
1086
1101
  self.assertTrue(
@@ -1130,6 +1145,8 @@ class DictTest(unittest.TestCase):
1130
1145
  self.assertTrue(base.ne(Dict(), 1))
1131
1146
  self.assertNotEqual(Dict(), Dict(a=1))
1132
1147
  self.assertTrue(base.ne(Dict(), Dict(a=1)))
1148
+ self.assertNotEqual(Dict({1: 1}), Dict({'1': 1}))
1149
+ self.assertTrue(base.ne(Dict({1: 1}), Dict({'1': 1})))
1133
1150
  self.assertNotEqual(Dict(a=0), Dict(a=1))
1134
1151
  self.assertTrue(base.ne(Dict(a=0), Dict(a=1)))
1135
1152
 
@@ -1192,6 +1209,7 @@ class DictTest(unittest.TestCase):
1192
1209
  self.assertEqual(hash(Dict(a=1)), hash(Dict(a=1)))
1193
1210
  self.assertEqual(hash(Dict(a=dict(x=1))), hash(Dict(a=dict(x=1))))
1194
1211
  self.assertNotEqual(hash(Dict()), hash(Dict(a=1)))
1212
+ self.assertNotEqual(hash(Dict({'1': 1})), hash(Dict({1: 1})))
1195
1213
  self.assertNotEqual(hash(Dict(a=1)), hash(Dict(a=2)))
1196
1214
 
1197
1215
  class A:
@@ -1247,7 +1265,7 @@ class DictTest(unittest.TestCase):
1247
1265
  self.assertEqual(sd.x.a.sym_path, 'x.a')
1248
1266
  self.assertEqual(sd.y[0].b.sym_path, 'y[0].b')
1249
1267
 
1250
- sd.sym_setpath(object_utils.KeyPath('a'))
1268
+ sd.sym_setpath(utils.KeyPath('a'))
1251
1269
  self.assertEqual(sd.sym_path, 'a')
1252
1270
  self.assertEqual(sd.x.sym_path, 'a.x')
1253
1271
  self.assertEqual(sd.x.a.sym_path, 'a.x.a')
@@ -1704,96 +1722,112 @@ class RebindTest(unittest.TestCase):
1704
1722
  'd': 'foo', # Unchanged.
1705
1723
  'e': 'bar'
1706
1724
  })
1707
- self.assertEqual(updates, [
1708
- { # Notification to `sd.c[0]`.
1709
- 'p': base.FieldUpdate(
1710
- object_utils.KeyPath.parse('c[0].p'),
1711
- target=sd.c[0],
1712
- field=None,
1713
- old_value=1,
1714
- new_value=MISSING_VALUE),
1715
- 'q': base.FieldUpdate(
1716
- object_utils.KeyPath.parse('c[0].q'),
1717
- target=sd.c[0],
1718
- field=None,
1719
- old_value=MISSING_VALUE,
1720
- new_value=2),
1721
- },
1722
- { # Notification to `sd.c`.
1723
- '[0].p': base.FieldUpdate(
1724
- object_utils.KeyPath.parse('c[0].p'),
1725
- target=sd.c[0],
1726
- field=None,
1727
- old_value=1,
1728
- new_value=MISSING_VALUE),
1729
- '[0].q': base.FieldUpdate(
1730
- object_utils.KeyPath.parse('c[0].q'),
1731
- target=sd.c[0],
1732
- field=None,
1733
- old_value=MISSING_VALUE,
1734
- new_value=2),
1735
- },
1736
- { # Notification to `sd.b.y`.
1737
- 'z': base.FieldUpdate(
1738
- object_utils.KeyPath.parse('b.y.z'),
1739
- target=sd.b.y,
1740
- field=None,
1741
- old_value=MISSING_VALUE,
1742
- new_value=1),
1743
- },
1744
- { # Notification to `sd.b`.
1745
- 'x': base.FieldUpdate(
1746
- object_utils.KeyPath.parse('b.x'),
1747
- target=sd.b,
1748
- field=None,
1749
- old_value=1,
1750
- new_value=2),
1751
- 'y.z': base.FieldUpdate(
1752
- object_utils.KeyPath.parse('b.y.z'),
1753
- target=sd.b.y,
1754
- field=None,
1755
- old_value=MISSING_VALUE,
1756
- new_value=1),
1757
- },
1758
- { # Notification to `sd`.
1759
- 'a': base.FieldUpdate(
1760
- object_utils.KeyPath.parse('a'),
1761
- target=sd,
1762
- field=None,
1763
- old_value=1,
1764
- new_value=2),
1765
- 'b.x': base.FieldUpdate(
1766
- object_utils.KeyPath.parse('b.x'),
1767
- target=sd.b,
1768
- field=None,
1769
- old_value=1,
1770
- new_value=2),
1771
- 'b.y.z': base.FieldUpdate(
1772
- object_utils.KeyPath.parse('b.y.z'),
1773
- target=sd.b.y,
1774
- field=None,
1775
- old_value=MISSING_VALUE,
1776
- new_value=1),
1777
- 'c[0].p': base.FieldUpdate(
1778
- object_utils.KeyPath.parse('c[0].p'),
1779
- target=sd.c[0],
1780
- field=None,
1781
- old_value=1,
1782
- new_value=MISSING_VALUE),
1783
- 'c[0].q': base.FieldUpdate(
1784
- object_utils.KeyPath.parse('c[0].q'),
1785
- target=sd.c[0],
1786
- field=None,
1787
- old_value=MISSING_VALUE,
1788
- new_value=2),
1789
- 'e': base.FieldUpdate(
1790
- object_utils.KeyPath.parse('e'),
1791
- target=sd,
1792
- field=None,
1793
- old_value=MISSING_VALUE,
1794
- new_value='bar')
1795
- }
1796
- ])
1725
+ self.assertEqual(
1726
+ updates,
1727
+ [
1728
+ { # Notification to `sd.c[0]`.
1729
+ 'p': base.FieldUpdate(
1730
+ utils.KeyPath.parse('c[0].p'),
1731
+ target=sd.c[0],
1732
+ field=None,
1733
+ old_value=1,
1734
+ new_value=MISSING_VALUE,
1735
+ ),
1736
+ 'q': base.FieldUpdate(
1737
+ utils.KeyPath.parse('c[0].q'),
1738
+ target=sd.c[0],
1739
+ field=None,
1740
+ old_value=MISSING_VALUE,
1741
+ new_value=2,
1742
+ ),
1743
+ },
1744
+ { # Notification to `sd.c`.
1745
+ '[0].p': base.FieldUpdate(
1746
+ utils.KeyPath.parse('c[0].p'),
1747
+ target=sd.c[0],
1748
+ field=None,
1749
+ old_value=1,
1750
+ new_value=MISSING_VALUE,
1751
+ ),
1752
+ '[0].q': base.FieldUpdate(
1753
+ utils.KeyPath.parse('c[0].q'),
1754
+ target=sd.c[0],
1755
+ field=None,
1756
+ old_value=MISSING_VALUE,
1757
+ new_value=2,
1758
+ ),
1759
+ },
1760
+ { # Notification to `sd.b.y`.
1761
+ 'z': base.FieldUpdate(
1762
+ utils.KeyPath.parse('b.y.z'),
1763
+ target=sd.b.y,
1764
+ field=None,
1765
+ old_value=MISSING_VALUE,
1766
+ new_value=1,
1767
+ ),
1768
+ },
1769
+ { # Notification to `sd.b`.
1770
+ 'x': base.FieldUpdate(
1771
+ utils.KeyPath.parse('b.x'),
1772
+ target=sd.b,
1773
+ field=None,
1774
+ old_value=1,
1775
+ new_value=2,
1776
+ ),
1777
+ 'y.z': base.FieldUpdate(
1778
+ utils.KeyPath.parse('b.y.z'),
1779
+ target=sd.b.y,
1780
+ field=None,
1781
+ old_value=MISSING_VALUE,
1782
+ new_value=1,
1783
+ ),
1784
+ },
1785
+ { # Notification to `sd`.
1786
+ 'a': base.FieldUpdate(
1787
+ utils.KeyPath.parse('a'),
1788
+ target=sd,
1789
+ field=None,
1790
+ old_value=1,
1791
+ new_value=2,
1792
+ ),
1793
+ 'b.x': base.FieldUpdate(
1794
+ utils.KeyPath.parse('b.x'),
1795
+ target=sd.b,
1796
+ field=None,
1797
+ old_value=1,
1798
+ new_value=2,
1799
+ ),
1800
+ 'b.y.z': base.FieldUpdate(
1801
+ utils.KeyPath.parse('b.y.z'),
1802
+ target=sd.b.y,
1803
+ field=None,
1804
+ old_value=MISSING_VALUE,
1805
+ new_value=1,
1806
+ ),
1807
+ 'c[0].p': base.FieldUpdate(
1808
+ utils.KeyPath.parse('c[0].p'),
1809
+ target=sd.c[0],
1810
+ field=None,
1811
+ old_value=1,
1812
+ new_value=MISSING_VALUE,
1813
+ ),
1814
+ 'c[0].q': base.FieldUpdate(
1815
+ utils.KeyPath.parse('c[0].q'),
1816
+ target=sd.c[0],
1817
+ field=None,
1818
+ old_value=MISSING_VALUE,
1819
+ new_value=2,
1820
+ ),
1821
+ 'e': base.FieldUpdate(
1822
+ utils.KeyPath.parse('e'),
1823
+ target=sd,
1824
+ field=None,
1825
+ old_value=MISSING_VALUE,
1826
+ new_value='bar',
1827
+ ),
1828
+ },
1829
+ ],
1830
+ )
1797
1831
 
1798
1832
  def test_rebind_with_fn(self):
1799
1833
  sd = Dict(a=1, b=dict(x=2, y='foo', z=[0, 1, 2]))
@@ -1845,10 +1879,6 @@ class RebindTest(unittest.TestCase):
1845
1879
  ValueError, 'There are no values to rebind.'):
1846
1880
  Dict().rebind({})
1847
1881
 
1848
- with self.assertRaisesRegex(
1849
- KeyError, 'Key must be string type. Encountered 1'):
1850
- Dict().rebind({1: 1})
1851
-
1852
1882
  with self.assertRaisesRegex(
1853
1883
  ValueError, 'Required value is not specified.'):
1854
1884
  Dict(a=1, value_spec=pg_typing.Dict([('a', pg_typing.Int())])).rebind({
@@ -1859,13 +1889,17 @@ class SerializationTest(unittest.TestCase):
1859
1889
  """Dedicated tests for `pg.Dict` serialization."""
1860
1890
 
1861
1891
  def test_schemaless(self):
1862
- sd = Dict()
1892
+ sd = Dict({1: 2})
1863
1893
  sd.b = 0
1864
1894
  sd.c = None
1865
1895
  sd.a = 'foo'
1866
1896
 
1867
1897
  # Key order is preserved.
1868
- self.assertEqual(sd.to_json_str(), '{"b": 0, "c": null, "a": "foo"}')
1898
+ self.assertEqual(
1899
+ sd.to_json_str(),
1900
+ '{"n_:1": 2, "b": 0, "c": null, "a": "foo"}'
1901
+ )
1902
+ self.assertEqual(base.from_json_str(sd.to_json_str()), sd)
1869
1903
 
1870
1904
  def test_schematized(self):
1871
1905
  sd = Dict.partial(
@@ -1907,6 +1941,36 @@ class SerializationTest(unittest.TestCase):
1907
1941
  self.assertEqual(sd.to_json_str(), '{"x": 1, "y": 2.0}')
1908
1942
  self.assertEqual(base.from_json_str(sd.to_json_str(), value_spec=spec), sd)
1909
1943
 
1944
+ def test_hide_frozen(self):
1945
+
1946
+ class A(pg_object.Object):
1947
+ x: pg_typing.Int().freeze(1)
1948
+
1949
+ sd = Dict.partial(
1950
+ a=A(),
1951
+ value_spec=pg_typing.Dict([
1952
+ ('a', pg_typing.Object(A)),
1953
+ ('b', pg_typing.Bool(True).freeze()),
1954
+ ]))
1955
+ self.assertEqual(
1956
+ sd.to_json(),
1957
+ {
1958
+ 'a': {
1959
+ '_type': A.__type_name__
1960
+ },
1961
+ }
1962
+ )
1963
+ self.assertEqual(
1964
+ sd.to_json(hide_frozen=False),
1965
+ {
1966
+ 'a': {
1967
+ '_type': A.__type_name__,
1968
+ 'x': 1,
1969
+ },
1970
+ 'b': True
1971
+ }
1972
+ )
1973
+
1910
1974
  def test_hide_default_values(self):
1911
1975
 
1912
1976
  class A(pg_object.Object):
@@ -2046,7 +2110,9 @@ class FormatTest(unittest.TestCase):
2046
2110
 
2047
2111
  def test_compact_python_format(self):
2048
2112
  self.assertEqual(
2049
- self._dict.format(compact=True, python_format=True, markdown=True),
2113
+ utils.format(
2114
+ self._dict, compact=True, python_format=True, markdown=True
2115
+ ),
2050
2116
  "`{'a1': 1, 'a2': {'b1': {'c1': [{'d1': MISSING_VALUE, "
2051
2117
  "'d2': True, 'd3': A(x=2, y=MISSING_VALUE, z={'p': [None, True], "
2052
2118
  "'q': 'foo', 't': 'foo'})}]}}}`",
@@ -2054,8 +2120,12 @@ class FormatTest(unittest.TestCase):
2054
2120
 
2055
2121
  def test_noncompact_python_format(self):
2056
2122
  self.assertEqual(
2057
- self._dict.format(
2058
- compact=False, verbose=False, python_format=True, markdown=True
2123
+ utils.format(
2124
+ self._dict,
2125
+ compact=False,
2126
+ verbose=False,
2127
+ python_format=True,
2128
+ markdown=True,
2059
2129
  ),
2060
2130
  inspect.cleandoc("""
2061
2131
  ```
@@ -2155,6 +2225,7 @@ class FormatTest(unittest.TestCase):
2155
2225
  }"""))
2156
2226
 
2157
2227
  def test_noncompact_verbose_with_extra_blankline_for_field_docstr(self):
2228
+ self.maxDiff = None
2158
2229
  self.assertEqual(
2159
2230
  self._dict.format(
2160
2231
  compact=False, verbose=True, extra_blankline_for_field_docstr=True),
@@ -2255,6 +2326,43 @@ class FormatTest(unittest.TestCase):
2255
2326
  """),
2256
2327
  )
2257
2328
 
2329
+ def test_hide_frozen(self):
2330
+ d = Dict(x=1, value_spec=pg_typing.Dict([
2331
+ ('x', pg_typing.Int()),
2332
+ ('y', pg_typing.Bool(True).freeze()),
2333
+ ('z', pg_typing.Dict([
2334
+ ('v', pg_typing.Int(1)),
2335
+ ('w', pg_typing.Bool().freeze(True)),
2336
+ ]))
2337
+ ]))
2338
+ self.assertEqual(
2339
+ d.format(compact=True),
2340
+ '{x=1, z={v=1}}'
2341
+ )
2342
+ self.assertEqual(
2343
+ d.format(compact=False, hide_frozen=False),
2344
+ '{\n x = 1,\n y = True,\n z = {\n v = 1,\n w = True\n }\n}'
2345
+ )
2346
+
2347
+ def test_compact_int_key(self):
2348
+ d = Dict({1: 1, '1': 2})
2349
+ self.assertEqual(d.format(compact=True), '{[1]=1, 1=2}')
2350
+ self.assertEqual(
2351
+ d.format(compact=True, python_format=True),
2352
+ '{1: 1, \'1\': 2}'
2353
+ )
2354
+
2355
+ def test_non_compact_int_key(self):
2356
+ d = Dict({1: 1, '1': 2})
2357
+ self.assertEqual(
2358
+ d.format(compact=False, verbose=False),
2359
+ '{\n [1] = 1,\n 1 = 2\n}'
2360
+ )
2361
+ self.assertEqual(
2362
+ d.format(compact=False, python_format=True),
2363
+ '{\n 1: 1,\n \'1\': 2\n}'
2364
+ )
2365
+
2258
2366
 
2259
2367
  def _on_change_callback(updates):
2260
2368
  del updates