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.

Files changed (40) hide show
  1. pyglove/core/geno/base.py +7 -3
  2. pyglove/core/io/file_system.py +452 -2
  3. pyglove/core/io/file_system_test.py +442 -0
  4. pyglove/core/monitoring.py +213 -90
  5. pyglove/core/monitoring_test.py +82 -29
  6. pyglove/core/symbolic/__init__.py +7 -0
  7. pyglove/core/symbolic/base.py +89 -35
  8. pyglove/core/symbolic/base_test.py +3 -3
  9. pyglove/core/symbolic/dict.py +31 -12
  10. pyglove/core/symbolic/dict_test.py +49 -0
  11. pyglove/core/symbolic/list.py +17 -3
  12. pyglove/core/symbolic/list_test.py +24 -2
  13. pyglove/core/symbolic/object.py +3 -1
  14. pyglove/core/symbolic/object_test.py +13 -10
  15. pyglove/core/symbolic/ref.py +19 -7
  16. pyglove/core/symbolic/ref_test.py +94 -7
  17. pyglove/core/symbolic/unknown_symbols.py +147 -0
  18. pyglove/core/symbolic/unknown_symbols_test.py +100 -0
  19. pyglove/core/typing/annotation_conversion.py +8 -1
  20. pyglove/core/typing/annotation_conversion_test.py +14 -19
  21. pyglove/core/typing/class_schema.py +24 -1
  22. pyglove/core/typing/json_schema.py +221 -8
  23. pyglove/core/typing/json_schema_test.py +508 -12
  24. pyglove/core/typing/type_conversion.py +17 -3
  25. pyglove/core/typing/type_conversion_test.py +7 -2
  26. pyglove/core/typing/value_specs.py +5 -1
  27. pyglove/core/typing/value_specs_test.py +5 -0
  28. pyglove/core/utils/__init__.py +1 -0
  29. pyglove/core/utils/contextual.py +9 -4
  30. pyglove/core/utils/contextual_test.py +10 -0
  31. pyglove/core/utils/json_conversion.py +360 -63
  32. pyglove/core/utils/json_conversion_test.py +146 -13
  33. pyglove/core/views/html/controls/tab.py +33 -0
  34. pyglove/core/views/html/controls/tab_test.py +37 -0
  35. pyglove/ext/evolution/base_test.py +1 -1
  36. {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/METADATA +8 -1
  37. {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/RECORD +40 -38
  38. {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/WHEEL +0 -0
  39. {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/licenses/LICENSE +0 -0
  40. {pyglove-0.5.0.dev202510020810.dist-info → pyglove-0.5.0.dev202512280810.dist-info}/top_level.txt +0 -0
@@ -15,7 +15,7 @@
15
15
 
16
16
  import dataclasses
17
17
  import inspect
18
- from typing import Any, Dict, List, Optional, Tuple, Union
18
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union
19
19
 
20
20
  from pyglove.core import utils
21
21
  from pyglove.core.typing import callable_signature
@@ -53,8 +53,8 @@ def _json_schema_from_schema(
53
53
  required = []
54
54
 
55
55
  if type_name and include_type_name:
56
- properties['_type'] = {'const': type_name}
57
- required.append('_type')
56
+ properties[utils.JSONConvertible.TYPE_NAME_KEY] = {'const': type_name}
57
+ required.append(utils.JSONConvertible.TYPE_NAME_KEY)
58
58
 
59
59
  for key, field in schema.items():
60
60
  if isinstance(key, ks.ConstStrKey):
@@ -145,7 +145,6 @@ def _json_schema_from_value_spec(
145
145
  }
146
146
  if not isinstance(value_spec.element.value, vs.Any):
147
147
  definition['items'] = _child_json_schema(value_spec.element.value)
148
- return definition
149
148
  elif isinstance(value_spec, vs.Dict):
150
149
  if value_spec.schema is None:
151
150
  definition = {
@@ -168,9 +167,7 @@ def _json_schema_from_value_spec(
168
167
  include_type_name=include_type_name,
169
168
  include_subclasses=include_subclasses,
170
169
  )
171
- definitions = [
172
- _json_schema_from_cls(value_spec.cls)
173
- ]
170
+ definitions = [_json_schema_from_cls(value_spec.cls)]
174
171
 
175
172
  if include_subclasses:
176
173
  for subclass in value_spec.cls.__subclasses__():
@@ -206,12 +203,26 @@ def _json_schema_from_value_spec(
206
203
  f'Value spec {value_spec!r} cannot be converted to JSON schema.'
207
204
  )
208
205
 
206
+ if (value_spec.has_default
207
+ and value_spec.default is not None
208
+ and not isinstance(value_spec, vs.Dict)):
209
+ default = utils.to_json(value_spec.default)
210
+ if not include_type_name:
211
+ def _remove_type_name(_, v: Dict[str, Any]):
212
+ if isinstance(v, dict):
213
+ v.pop(utils.JSONConvertible.TYPE_NAME_KEY, None)
214
+ return v
215
+ default = utils.transform(default, _remove_type_name)
216
+ definition['default'] = default
217
+
209
218
  if not ignore_nonable and value_spec.is_noneable:
210
219
  nullable = {'type': 'null'}
211
220
  if 'anyOf' in definition:
212
221
  definition['anyOf'].append(nullable)
213
222
  else:
214
223
  definition = {'anyOf': [definition, nullable]}
224
+ if value_spec.default is None:
225
+ definition['default'] = None
215
226
  return definition
216
227
 
217
228
 
@@ -294,6 +305,8 @@ def _canonicalize_schema(
294
305
  }
295
306
  canonical_form = {'$defs': referenced_defs} if referenced_defs else {}
296
307
  canonical_form.update(new_root)
308
+ if 'default' in root:
309
+ canonical_form['default'] = root['default']
297
310
  return canonical_form
298
311
 
299
312
  #
@@ -336,5 +349,205 @@ def _schema_to_json_schema(
336
349
  **kwargs
337
350
  )
338
351
 
339
- vs.ValueSpec.to_json_schema = _value_spec_to_json_schema
352
+ class_schema.ValueSpec.to_json_schema = _value_spec_to_json_schema
340
353
  class_schema.Schema.to_json_schema = _schema_to_json_schema
354
+
355
+ #
356
+ # from JSON schema to PyGlove value spec.
357
+ #
358
+
359
+
360
+ def _json_schema_to_value_spec(
361
+ json_schema: Dict[str, Any],
362
+ defs: Dict[str, Type[Any]],
363
+ class_fn: Optional[Callable[[str, class_schema.Schema], Type[Any]]],
364
+ add_json_schema_as_metadata: bool,
365
+ ) -> class_schema.ValueSpec:
366
+ """Converts a JSON schema to a value spec."""
367
+ # Generate code to convert JSON schema to value spec.
368
+ def _value_spec(value_schema: Dict[str, Any]):
369
+ return _json_schema_to_value_spec(
370
+ value_schema, defs, class_fn, add_json_schema_as_metadata
371
+ )
372
+
373
+ if '$ref' in json_schema:
374
+ # TODO(daiyip): Support circular references.
375
+ ref_key = json_schema['$ref'].split('/')[-1]
376
+ type_ref = defs.get(ref_key)
377
+ if type_ref is None:
378
+ raise ValueError(
379
+ f'Reference {ref_key!r} not defined in defs. '
380
+ 'Please make sure classes being referenced are defined '
381
+ 'before the referencing classes. '
382
+ )
383
+ return type_ref
384
+ type_str = json_schema.get('type')
385
+ default = json_schema.get('default', utils.MISSING_VALUE)
386
+ if type_str is None:
387
+ if 'enum' in json_schema:
388
+ for v in json_schema['enum']:
389
+ if not isinstance(v, (str, int, float, bool)):
390
+ raise ValueError(
391
+ f'Enum candidate {v!r} is not supported for JSON schema '
392
+ 'conversion.'
393
+ )
394
+ return vs.Enum(
395
+ default,
396
+ [v for v in json_schema['enum'] if v is not None]
397
+ )
398
+ elif 'anyOf' in json_schema:
399
+ candidates = []
400
+ accepts_none = False
401
+ for v in json_schema['anyOf']:
402
+ candidate = _value_spec(v)
403
+ if candidate.frozen and candidate.default is None:
404
+ accepts_none = True
405
+ continue
406
+ candidates.append(candidate)
407
+
408
+ if len(candidates) == 1:
409
+ spec = candidates[0]
410
+ else:
411
+ spec = vs.Union(candidates)
412
+ if accepts_none:
413
+ spec = spec.noneable()
414
+ return spec
415
+ elif type_str == 'null':
416
+ return vs.Any().freeze(None)
417
+ elif type_str == 'boolean':
418
+ return vs.Bool(default=default)
419
+ elif type_str == 'integer':
420
+ minimum = json_schema.get('minimum')
421
+ maximum = json_schema.get('maximum')
422
+ return vs.Int(min_value=minimum, max_value=maximum, default=default)
423
+ elif type_str == 'number':
424
+ minimum = json_schema.get('minimum')
425
+ maximum = json_schema.get('maximum')
426
+ return vs.Float(min_value=minimum, max_value=maximum, default=default)
427
+ elif type_str == 'string':
428
+ pattern = json_schema.get('pattern')
429
+ return vs.Str(regex=pattern, default=default)
430
+ elif type_str == 'array':
431
+ items = json_schema.get('items')
432
+ return vs.List(_value_spec(items) if items else vs.Any(), default=default)
433
+ elif type_str == 'object':
434
+ schema = _json_schema_to_schema(
435
+ json_schema, defs, class_fn, add_json_schema_as_metadata
436
+ )
437
+ if class_fn is not None and 'title' in json_schema:
438
+ return vs.Object(class_fn(json_schema['title'], schema))
439
+ return vs.Dict(schema=schema if schema.fields else None)
440
+ raise ValueError(f'Unsupported type {type_str!r} in JSON schema.')
441
+
442
+
443
+ def _json_schema_to_schema(
444
+ json_schema: Dict[str, Any],
445
+ defs: Dict[str, Type[Any]],
446
+ class_fn: Optional[Callable[[str, class_schema.Schema], Type[Any]]],
447
+ add_json_schema_as_metadata: bool,
448
+ ) -> class_schema.Schema:
449
+ """Converts a JSON schema to a schema."""
450
+ title = json_schema.get('title')
451
+ properties = json_schema.get('properties', {})
452
+ fields = []
453
+ required = set(json_schema.get('required', []))
454
+ for name, property_schema in properties.items():
455
+ value_spec = _json_schema_to_value_spec(
456
+ property_schema, defs, class_fn, add_json_schema_as_metadata
457
+ )
458
+ if name not in required and not value_spec.has_default:
459
+ value_spec = value_spec.noneable()
460
+ fields.append(
461
+ class_schema.Field(
462
+ name,
463
+ value_spec,
464
+ description=property_schema.get('description'),
465
+ metadata=(
466
+ dict(json_schema=property_schema)
467
+ if add_json_schema_as_metadata else None
468
+ )
469
+ )
470
+ )
471
+ additional_properties = json_schema.get('additionalProperties')
472
+ if additional_properties:
473
+ if isinstance(additional_properties, dict):
474
+ value_spec = _json_schema_to_value_spec(
475
+ additional_properties, defs, class_fn, add_json_schema_as_metadata
476
+ )
477
+ else:
478
+ value_spec = vs.Any()
479
+ fields.append(class_schema.Field(ks.StrKey(), value_spec))
480
+ return class_schema.Schema(
481
+ name=title,
482
+ description=json_schema.get('description'),
483
+ fields=fields,
484
+ allow_nonconst_keys=True,
485
+ )
486
+
487
+
488
+ @classmethod
489
+ def _value_spec_from_json_schema(
490
+ cls,
491
+ json_schema: Dict[str, Any],
492
+ class_fn: Optional[Callable[[str, class_schema.Schema], Type[Any]]] = None,
493
+ add_json_schema_as_metadata: bool = False,
494
+ ) -> class_schema.ValueSpec:
495
+ """Creates a PyGlove value spec from a JSON schema.
496
+
497
+ Args:
498
+ json_schema: The JSON schema for a value spec.
499
+ class_fn: A function that creates a PyGlove class from a class name and a
500
+ schema. If None, all "object" type properties will be converted to
501
+ `pg.typing.Dict`. Otherwise, "object" type properties will be converted to
502
+ a class.
503
+ add_json_schema_as_metadata: Whether to add the JSON schema as field
504
+ metadata.
505
+
506
+ Returns:
507
+ A PyGlove value spec.
508
+ """
509
+ del cls
510
+ defs = {}
511
+ if '$defs' in json_schema:
512
+ for key, def_entry in json_schema['$defs'].items():
513
+ defs[key] = _json_schema_to_value_spec(
514
+ def_entry, defs, class_fn, add_json_schema_as_metadata
515
+ )
516
+ return _json_schema_to_value_spec(
517
+ json_schema, defs, class_fn, add_json_schema_as_metadata
518
+ )
519
+
520
+
521
+ @classmethod
522
+ def _schema_from_json_schema(
523
+ cls,
524
+ json_schema: Dict[str, Any],
525
+ class_fn: Optional[Callable[[str, class_schema.Schema], Type[Any]]] = None,
526
+ add_json_schema_as_metadata: bool = False,
527
+ ) -> class_schema.Schema:
528
+ """Creates a PyGlove schema from a JSON schema.
529
+
530
+ Args:
531
+ json_schema: The JSON schema to convert.
532
+ class_fn: A function that creates a PyGlove class from a class name and a
533
+ schema. If None, all "object" type properties will be converted to
534
+ `pg.typing.Dict`. Otherwise, "object" type properties will be converted to
535
+ a class.
536
+ add_json_schema_as_metadata: Whether to add the JSON schema as field
537
+ metadata.
538
+
539
+ Returns:
540
+ A PyGlove schema.
541
+ """
542
+ del cls
543
+ if json_schema.get('type') != 'object':
544
+ raise ValueError(
545
+ f'JSON schema is not an object type: {json_schema!r}'
546
+ )
547
+ return _json_schema_to_schema(
548
+ json_schema, {}, class_fn, add_json_schema_as_metadata
549
+ )
550
+
551
+
552
+ class_schema.ValueSpec.from_json_schema = _value_spec_from_json_schema
553
+ class_schema.Schema.from_json_schema = _schema_from_json_schema