pyglove 0.4.5.dev202411132359__py3-none-any.whl → 0.4.5.dev202501250807__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 (118) hide show
  1. pyglove/core/__init__.py +40 -21
  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 +312 -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 +53 -38
  14. pyglove/core/geno/base_test.py +2 -4
  15. pyglove/core/geno/categorical.py +36 -27
  16. pyglove/core/geno/custom.py +18 -15
  17. pyglove/core/geno/numerical.py +19 -16
  18. pyglove/core/geno/space.py +3 -4
  19. pyglove/core/hyper/base.py +6 -6
  20. pyglove/core/hyper/categorical.py +91 -52
  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 +3 -5
  25. pyglove/core/hyper/dynamic_evaluation.py +3 -4
  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/logging_test.py +0 -2
  31. pyglove/core/patching/object_factory.py +4 -4
  32. pyglove/core/patching/pattern_based.py +4 -4
  33. pyglove/core/patching/rule_based.py +4 -3
  34. pyglove/core/symbolic/__init__.py +4 -0
  35. pyglove/core/symbolic/base.py +200 -136
  36. pyglove/core/symbolic/base_test.py +17 -19
  37. pyglove/core/symbolic/boilerplate.py +4 -5
  38. pyglove/core/symbolic/class_wrapper.py +10 -14
  39. pyglove/core/symbolic/class_wrapper_test.py +2 -2
  40. pyglove/core/symbolic/compounding.py +2 -2
  41. pyglove/core/symbolic/compounding_test.py +2 -4
  42. pyglove/core/symbolic/contextual_object.py +288 -0
  43. pyglove/core/symbolic/contextual_object_test.py +327 -0
  44. pyglove/core/symbolic/dict.py +115 -87
  45. pyglove/core/symbolic/dict_test.py +188 -131
  46. pyglove/core/symbolic/diff.py +12 -12
  47. pyglove/core/symbolic/flags.py +1 -1
  48. pyglove/core/symbolic/functor.py +16 -15
  49. pyglove/core/symbolic/functor_test.py +2 -4
  50. pyglove/core/symbolic/inferred.py +2 -2
  51. pyglove/core/symbolic/list.py +70 -47
  52. pyglove/core/symbolic/list_test.py +117 -98
  53. pyglove/core/symbolic/object.py +59 -58
  54. pyglove/core/symbolic/object_test.py +143 -90
  55. pyglove/core/symbolic/origin.py +5 -7
  56. pyglove/core/symbolic/pure_symbolic.py +4 -3
  57. pyglove/core/symbolic/ref.py +33 -16
  58. pyglove/core/symbolic/ref_test.py +17 -0
  59. pyglove/core/tuning/local_backend.py +2 -2
  60. pyglove/core/tuning/protocols.py +3 -3
  61. pyglove/core/typing/annotation_conversion.py +8 -3
  62. pyglove/core/typing/annotation_conversion_test.py +8 -0
  63. pyglove/core/typing/callable_ext.py +11 -13
  64. pyglove/core/typing/callable_signature.py +22 -19
  65. pyglove/core/typing/callable_signature_test.py +3 -5
  66. pyglove/core/typing/class_schema.py +93 -54
  67. pyglove/core/typing/class_schema_test.py +4 -5
  68. pyglove/core/typing/custom_typing.py +5 -4
  69. pyglove/core/typing/key_specs.py +5 -7
  70. pyglove/core/typing/key_specs_test.py +4 -4
  71. pyglove/core/typing/type_conversion.py +4 -5
  72. pyglove/core/typing/type_conversion_test.py +12 -12
  73. pyglove/core/typing/typed_missing.py +6 -7
  74. pyglove/core/typing/typed_missing_test.py +7 -8
  75. pyglove/core/typing/value_specs.py +287 -144
  76. pyglove/core/typing/value_specs_test.py +148 -25
  77. pyglove/core/utils/__init__.py +172 -0
  78. pyglove/core/{object_utils → utils}/common_traits.py +2 -2
  79. pyglove/core/{object_utils → utils}/common_traits_test.py +1 -3
  80. pyglove/core/utils/contextual.py +147 -0
  81. pyglove/core/utils/contextual_test.py +88 -0
  82. pyglove/core/{object_utils → utils}/docstr_utils_test.py +1 -3
  83. pyglove/core/{object_utils → utils}/error_utils.py +3 -3
  84. pyglove/core/{object_utils → utils}/error_utils_test.py +1 -1
  85. pyglove/core/{object_utils → utils}/formatting.py +1 -1
  86. pyglove/core/{object_utils → utils}/formatting_test.py +1 -2
  87. pyglove/core/{object_utils → utils}/hierarchical.py +23 -25
  88. pyglove/core/{object_utils → utils}/hierarchical_test.py +3 -5
  89. pyglove/core/{object_utils → utils}/json_conversion.py +1 -1
  90. pyglove/core/{object_utils → utils}/json_conversion_test.py +1 -3
  91. pyglove/core/{object_utils → utils}/missing.py +2 -2
  92. pyglove/core/{object_utils → utils}/missing_test.py +2 -4
  93. pyglove/core/utils/text_color.py +128 -0
  94. pyglove/core/utils/text_color_test.py +94 -0
  95. pyglove/core/{object_utils → utils}/thread_local_test.py +1 -3
  96. pyglove/core/{object_utils → utils}/timing.py +21 -10
  97. pyglove/core/{object_utils → utils}/timing_test.py +14 -12
  98. pyglove/core/{object_utils → utils}/value_location.py +2 -2
  99. pyglove/core/{object_utils → utils}/value_location_test.py +2 -4
  100. pyglove/core/views/base.py +25 -29
  101. pyglove/core/views/html/base.py +15 -16
  102. pyglove/core/views/html/controls/base.py +46 -9
  103. pyglove/core/views/html/controls/label.py +13 -2
  104. pyglove/core/views/html/controls/label_test.py +27 -8
  105. pyglove/core/views/html/controls/progress_bar.py +3 -5
  106. pyglove/core/views/html/controls/progress_bar_test.py +2 -2
  107. pyglove/core/views/html/controls/tab.py +217 -66
  108. pyglove/core/views/html/controls/tab_test.py +46 -15
  109. pyglove/core/views/html/tree_view.py +39 -37
  110. {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/METADATA +17 -3
  111. pyglove-0.4.5.dev202501250807.dist-info/RECORD +218 -0
  112. {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/WHEEL +1 -1
  113. pyglove/core/object_utils/__init__.py +0 -164
  114. pyglove-0.4.5.dev202411132359.dist-info/RECORD +0 -203
  115. /pyglove/core/{object_utils → utils}/docstr_utils.py +0 -0
  116. /pyglove/core/{object_utils → utils}/thread_local.py +0 -0
  117. {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/LICENSE +0 -0
  118. {pyglove-0.4.5.dev202411132359.dist-info → pyglove-0.4.5.dev202501250807.dist-info}/top_level.txt +0 -0
@@ -19,9 +19,9 @@ import inspect
19
19
  import typing
20
20
  from typing import Any, Dict, Iterator, List, Optional, Sequence, Union
21
21
 
22
- from pyglove.core import logging
23
- from pyglove.core import object_utils
22
+ from pyglove.core import coding
24
23
  from pyglove.core import typing as pg_typing
24
+ from pyglove.core import utils
25
25
  from pyglove.core.symbolic import base
26
26
  from pyglove.core.symbolic import dict as pg_dict
27
27
  from pyglove.core.symbolic import flags
@@ -49,21 +49,6 @@ class ObjectMeta(abc.ABCMeta):
49
49
  """
50
50
  return f'{cls.__module__}.{cls.__qualname__}'
51
51
 
52
- def __getattr__(cls, name):
53
- # NOTE(daiyip): For backward compatibility, we allows these names to
54
- # be used as aliases to the canonical names if users do not override them.
55
- if name == 'schema':
56
- logging.warning(
57
- '`pg.Object.schema` is deprecated and will be removed in future. '
58
- 'Please use `__schema__` instead.')
59
- return cls.__schema__
60
- elif name == 'type_name':
61
- logging.warning(
62
- '`pg.Object.type_name` is deprecated and will be removed in future. '
63
- 'Please use `__type_name__` instead.')
64
- return cls.__type_name__
65
- raise AttributeError(name)
66
-
67
52
  @property
68
53
  def init_arg_list(cls) -> List[str]:
69
54
  """Gets __init__ positional argument list."""
@@ -80,7 +65,7 @@ class ObjectMeta(abc.ABCMeta):
80
65
  """
81
66
  # Formalize schema first.
82
67
  if schema is not None:
83
- schema = cls._normalize_schema(schema)
68
+ schema = cls._normalize_schema(schema) # pytype: disable=attribute-error
84
69
  setattr(cls, '__schema__', schema)
85
70
  setattr(cls, '__sym_fields', pg_typing.Dict(schema))
86
71
 
@@ -111,6 +96,7 @@ class ObjectMeta(abc.ABCMeta):
111
96
  base_schema_list=[cls.__schema__] if extend else [],
112
97
  allow_nonconst_keys=True,
113
98
  metadata=metadata,
99
+ for_cls=cls,
114
100
  )
115
101
  cls.apply_schema(schema)
116
102
 
@@ -131,7 +117,7 @@ class ObjectMeta(abc.ABCMeta):
131
117
 
132
118
  # Register class with 'type' property.
133
119
  for key in serialization_keys:
134
- object_utils.JSONConvertible.register(
120
+ utils.JSONConvertible.register(
135
121
  key, cls, flags.is_repeated_class_registration_allowed()
136
122
  )
137
123
 
@@ -164,11 +150,22 @@ class ObjectMeta(abc.ABCMeta):
164
150
  if key is None:
165
151
  continue
166
152
 
153
+ # Skip class-level attributes that are not symbolic fields.
154
+ if typing.get_origin(attr_annotation) is typing.ClassVar:
155
+ continue
156
+
167
157
  field = pg_typing.Field.from_annotation(key, attr_annotation)
168
158
  if isinstance(key, pg_typing.ConstStrKey):
169
159
  attr_value = cls.__dict__.get(attr_name, pg_typing.MISSING_VALUE)
170
160
  if attr_value != pg_typing.MISSING_VALUE:
171
161
  field.value.set_default(attr_value)
162
+
163
+ if (field.value.frozen and
164
+ field.value.default is
165
+ pg_typing.value_specs._FROZEN_VALUE_PLACEHOLDER): # pylint: disable=protected-access
166
+ raise TypeError(
167
+ f'Field {field.key!r} is marked as final but has no default value.'
168
+ )
172
169
  fields.append(field)
173
170
 
174
171
  # Trigger event so subclass could modify the fields.
@@ -197,6 +194,7 @@ class ObjectMeta(abc.ABCMeta):
197
194
  field.value.freeze(attr_value, apply_before_use=False)
198
195
  else:
199
196
  field.value.set_default(attr_value)
197
+ field.set_origin(cls)
200
198
 
201
199
 
202
200
  # Use ObjectMeta as meta class to inherit schema and type_name property.
@@ -313,7 +311,7 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
313
311
  Args:
314
312
  user_cls: The source class that calls this class method.
315
313
  """
316
- object_utils.ensure_explicit_method_override(
314
+ utils.ensure_explicit_method_override(
317
315
  cls.__init__,
318
316
  (
319
317
  '`pg.Object.__init__` is a PyGlove managed method. For setting up '
@@ -321,7 +319,8 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
321
319
  '`_on_init()`. If you do have a need to override `__init__` and '
322
320
  'know the implications, please decorate your overridden method '
323
321
  'with `@pg.explicit_method_override`.'
324
- ))
322
+ ),
323
+ )
325
324
 
326
325
  # Set `__serialization_key__` before JSONConvertible.__init_subclass__
327
326
  # is called.
@@ -347,6 +346,7 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
347
346
  base_schema_list=base_schema_list,
348
347
  allow_nonconst_keys=True,
349
348
  metadata={},
349
+ for_cls=user_cls,
350
350
  )
351
351
 
352
352
  # Freeze callable symbolic attributes if they are provided as methods.
@@ -367,11 +367,11 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
367
367
  """Normalizes the schema before applying it."""
368
368
 
369
369
  schema.set_name(cls.__type_name__)
370
- docstr = object_utils.docstr(cls)
370
+ docstr = utils.docstr(cls)
371
371
  if docstr:
372
372
  schema.set_description(docstr.description)
373
373
 
374
- def _formalize_field(path: object_utils.KeyPath, node: Any) -> bool:
374
+ def _formalize_field(path: utils.KeyPath, node: Any) -> bool:
375
375
  """Formalize field."""
376
376
  if isinstance(node, pg_typing.Field):
377
377
  field = node
@@ -389,27 +389,29 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
389
389
  if isinstance(field.value, pg_typing.Dict):
390
390
  if field.value.schema is not None:
391
391
  field.value.schema.set_name(f'{schema.name}.{path.path}')
392
- object_utils.traverse(field.value.schema.fields, _formalize_field,
393
- None, path)
392
+ utils.traverse(
393
+ field.value.schema.fields, _formalize_field, None, path
394
+ )
394
395
  elif isinstance(field.value, pg_typing.List):
395
- _formalize_field(object_utils.KeyPath(0, path), field.value.element)
396
+ _formalize_field(utils.KeyPath(0, path), field.value.element)
396
397
  elif isinstance(field.value, pg_typing.Tuple):
397
398
  for i, elem in enumerate(field.value.elements):
398
- _formalize_field(object_utils.KeyPath(i, path), elem)
399
+ _formalize_field(utils.KeyPath(i, path), elem)
399
400
  elif isinstance(field.value, pg_typing.Union):
400
401
  for i, c in enumerate(field.value.candidates):
401
402
  _formalize_field(
402
- object_utils.KeyPath(i, path),
403
- pg_typing.Field(field.key, c, 'Union sub-type.'))
403
+ utils.KeyPath(i, path),
404
+ pg_typing.Field(field.key, c, 'Union sub-type.'),
405
+ )
404
406
  return True
405
407
 
406
- object_utils.traverse(schema.fields, _formalize_field)
408
+ utils.traverse(schema.fields, _formalize_field)
407
409
  return schema
408
410
 
409
411
  @classmethod
410
412
  def _finalize_init_arg_list(cls) -> List[str]:
411
413
  """Finalizes init_arg_list based on schema."""
412
- # Update `init_arg_list`` based on the updated schema.
414
+ # Update `init_arg_list`` based on the updated schema.
413
415
  init_arg_list = cls.__schema__.metadata.get('init_arg_list', None)
414
416
  if init_arg_list is None:
415
417
  # Inherit from the first non-empty base if they have the same signature.
@@ -480,7 +482,7 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
480
482
  # Create a new `__init__` that passes through all the arguments to
481
483
  # in `pg.Object.__init__`. This is needed for each class to use different
482
484
  # signature.
483
- @object_utils.explicit_method_override
485
+ @utils.explicit_method_override
484
486
  @functools.wraps(pseudo_init)
485
487
  def _init(self, *args, **kwargs):
486
488
  # We pass through the arguments to `Object.__init__` instead of
@@ -509,7 +511,7 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
509
511
  def _create_sym_attribute(cls, attr_name, field):
510
512
  """Customizable trait: template of single symbolic attribute."""
511
513
  return property(
512
- object_utils.make_function(
514
+ coding.make_function(
513
515
  attr_name,
514
516
  ['self'],
515
517
  [f"return self.sym_inferred('{attr_name}')"],
@@ -543,8 +545,8 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
543
545
  json_value: Any,
544
546
  *,
545
547
  allow_partial: bool = False,
546
- root_path: Optional[object_utils.KeyPath] = None,
547
- **kwargs
548
+ root_path: Optional[utils.KeyPath] = None,
549
+ **kwargs,
548
550
  ) -> 'Object':
549
551
  """Class method that load an symbolic Object from a JSON value.
550
552
 
@@ -592,15 +594,16 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
592
594
  for k, v in json_value.items()
593
595
  })
594
596
 
595
- @object_utils.explicit_method_override
597
+ @utils.explicit_method_override
596
598
  def __init__(
597
599
  self,
598
600
  *args,
599
601
  allow_partial: bool = False,
600
602
  sealed: Optional[bool] = None,
601
- root_path: Optional[object_utils.KeyPath] = None,
603
+ root_path: Optional[utils.KeyPath] = None,
602
604
  explicit_init: bool = False,
603
- **kwargs):
605
+ **kwargs,
606
+ ):
604
607
  """Create an Object instance.
605
608
 
606
609
  Args:
@@ -642,8 +645,8 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
642
645
  # Fill field_args and init_args from **kwargs.
643
646
  _, unmatched_keys = self.__class__.__schema__.resolve(list(kwargs.keys()))
644
647
  if unmatched_keys:
645
- arg_phrase = object_utils.auto_plural(len(unmatched_keys), 'argument')
646
- keys_str = object_utils.comma_delimited_str(unmatched_keys)
648
+ arg_phrase = utils.auto_plural(len(unmatched_keys), 'argument')
649
+ keys_str = utils.comma_delimited_str(unmatched_keys)
647
650
  raise TypeError(
648
651
  f'{self.__class__.__name__}.__init__() got unexpected '
649
652
  f'keyword {arg_phrase}: {keys_str}')
@@ -663,8 +666,8 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
663
666
  field_args[vararg_name] = list(args[num_named_args:])
664
667
  args = args[:num_named_args]
665
668
  elif len(args) > len(init_arg_names):
666
- arg_phrase = object_utils.auto_plural(len(init_arg_names), 'argument')
667
- was_phrase = object_utils.auto_plural(len(args), 'was', 'were')
669
+ arg_phrase = utils.auto_plural(len(init_arg_names), 'argument')
670
+ was_phrase = utils.auto_plural(len(args), 'was', 'were')
668
671
  raise TypeError(
669
672
  f'{self.__class__.__name__}.__init__() takes '
670
673
  f'{len(init_arg_names)} positional {arg_phrase} but {len(args)} '
@@ -676,7 +679,7 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
676
679
 
677
680
  for k, v in kwargs.items():
678
681
  if k in field_args:
679
- values_str = object_utils.comma_delimited_str([field_args[k], v])
682
+ values_str = utils.comma_delimited_str([field_args[k], v])
680
683
  raise TypeError(
681
684
  f'{self.__class__.__name__}.__init__() got multiple values for '
682
685
  f'argument \'{k}\': {values_str}.')
@@ -691,8 +694,8 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
691
694
  and field.key not in field_args):
692
695
  missing_args.append(str(field.key))
693
696
  if missing_args:
694
- arg_phrase = object_utils.auto_plural(len(missing_args), 'argument')
695
- keys_str = object_utils.comma_delimited_str(missing_args)
697
+ arg_phrase = utils.auto_plural(len(missing_args), 'argument')
698
+ keys_str = utils.comma_delimited_str(missing_args)
696
699
  raise TypeError(
697
700
  f'{self.__class__.__name__}.__init__() missing {len(missing_args)} '
698
701
  f'required {arg_phrase}: {keys_str}.')
@@ -742,8 +745,7 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
742
745
  and during __init__.
743
746
  """
744
747
 
745
- def _on_change(self,
746
- field_updates: Dict[object_utils.KeyPath, base.FieldUpdate]):
748
+ def _on_change(self, field_updates: Dict[utils.KeyPath, base.FieldUpdate]):
747
749
  """Event that is triggered when field values in the subtree are updated.
748
750
 
749
751
  This event will be called
@@ -763,8 +765,7 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
763
765
  del field_updates
764
766
  return self._on_bound()
765
767
 
766
- def _on_path_change(
767
- self, old_path: object_utils.KeyPath, new_path: object_utils.KeyPath):
768
+ def _on_path_change(self, old_path: utils.KeyPath, new_path: utils.KeyPath):
768
769
  """Event that is triggered after the symbolic path changes."""
769
770
  del old_path, new_path
770
771
 
@@ -843,8 +844,8 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
843
844
  return self._sym_attributes.sym_getattr(key)
844
845
 
845
846
  def _sym_rebind(
846
- self, path_value_pairs: Dict[object_utils.KeyPath, Any]
847
- ) -> List[base.FieldUpdate]:
847
+ self, path_value_pairs: Dict[utils.KeyPath, Any]
848
+ ) -> List[base.FieldUpdate]:
848
849
  """Rebind current object using object-form members."""
849
850
  if base.treats_as_sealed(self):
850
851
  raise base.WritePermissionError(
@@ -883,9 +884,8 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
883
884
  return self
884
885
 
885
886
  def _update_children_paths(
886
- self,
887
- old_path: object_utils.KeyPath,
888
- new_path: object_utils.KeyPath) -> None:
887
+ self, old_path: utils.KeyPath, new_path: utils.KeyPath
888
+ ) -> None:
889
889
  """Update children paths according to root_path of current node."""
890
890
  self._sym_attributes.sym_setpath(new_path)
891
891
  self._on_path_change(old_path, new_path)
@@ -969,10 +969,10 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
969
969
  return self.sym_hash()
970
970
  return super().__hash__()
971
971
 
972
- def sym_jsonify(self, **kwargs) -> object_utils.JSONValueType:
972
+ def sym_jsonify(self, **kwargs) -> utils.JSONValueType:
973
973
  """Converts current object to a dict of plain Python objects."""
974
974
  json_dict = {
975
- object_utils.JSONConvertible.TYPE_NAME_KEY: (
975
+ utils.JSONConvertible.TYPE_NAME_KEY: (
976
976
  self.__class__.__serialization_key__
977
977
  )
978
978
  }
@@ -991,8 +991,9 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
991
991
  root_indent,
992
992
  cls_name=self.__class__.__name__,
993
993
  key_as_attribute=True,
994
- bracket_type=object_utils.BracketType.ROUND,
995
- **kwargs)
994
+ bracket_type=utils.BracketType.ROUND,
995
+ **kwargs,
996
+ )
996
997
 
997
998
 
998
999
  base.Symbolic.ObjectType = Object