pyglove 0.5.0.dev202510140809__tar.gz → 0.5.0.dev202511161718__tar.gz

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 (231) hide show
  1. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/PKG-INFO +1 -1
  2. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/base.py +7 -3
  3. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/io/file_system.py +105 -2
  4. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/io/file_system_test.py +174 -0
  5. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/base.py +70 -31
  6. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/base_test.py +3 -3
  7. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/dict.py +31 -12
  8. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/dict_test.py +49 -0
  9. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/list.py +17 -3
  10. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/list_test.py +24 -2
  11. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/object.py +1 -0
  12. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/ref.py +19 -7
  13. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/ref_test.py +94 -7
  14. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/annotation_conversion.py +8 -1
  15. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/annotation_conversion_test.py +14 -19
  16. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/class_schema.py +24 -1
  17. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/json_schema.py +221 -8
  18. pyglove-0.5.0.dev202511161718/pyglove/core/typing/json_schema_test.py +973 -0
  19. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/type_conversion.py +17 -3
  20. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/type_conversion_test.py +7 -2
  21. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/__init__.py +1 -0
  22. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/contextual.py +9 -4
  23. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/contextual_test.py +10 -0
  24. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/json_conversion.py +255 -16
  25. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/json_conversion_test.py +63 -4
  26. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/controls/tab.py +33 -0
  27. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/controls/tab_test.py +37 -0
  28. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/base_test.py +1 -1
  29. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove.egg-info/PKG-INFO +1 -1
  30. pyglove-0.5.0.dev202510140809/pyglove/core/typing/json_schema_test.py +0 -477
  31. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/LICENSE +0 -0
  32. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/README.md +0 -0
  33. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/__init__.py +0 -0
  34. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/__init__.py +0 -0
  35. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/coding/__init__.py +0 -0
  36. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/coding/errors.py +0 -0
  37. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/coding/errors_test.py +0 -0
  38. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/coding/execution.py +0 -0
  39. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/coding/execution_test.py +0 -0
  40. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/coding/function_generation.py +0 -0
  41. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/coding/function_generation_test.py +0 -0
  42. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/coding/parsing.py +0 -0
  43. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/coding/parsing_test.py +0 -0
  44. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/coding/permissions.py +0 -0
  45. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/coding/permissions_test.py +0 -0
  46. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/detouring/__init__.py +0 -0
  47. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/detouring/class_detour.py +0 -0
  48. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/detouring/class_detour_test.py +0 -0
  49. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/__init__.py +0 -0
  50. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/base_test.py +0 -0
  51. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/categorical.py +0 -0
  52. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/categorical_test.py +0 -0
  53. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/custom.py +0 -0
  54. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/custom_test.py +0 -0
  55. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/deduping.py +0 -0
  56. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/deduping_test.py +0 -0
  57. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/dna_generator.py +0 -0
  58. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/dna_generator_test.py +0 -0
  59. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/numerical.py +0 -0
  60. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/numerical_test.py +0 -0
  61. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/random.py +0 -0
  62. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/random_test.py +0 -0
  63. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/space.py +0 -0
  64. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/space_test.py +0 -0
  65. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/sweeping.py +0 -0
  66. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/geno/sweeping_test.py +0 -0
  67. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/__init__.py +0 -0
  68. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/base.py +0 -0
  69. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/categorical.py +0 -0
  70. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/categorical_test.py +0 -0
  71. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/custom.py +0 -0
  72. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/custom_test.py +0 -0
  73. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/derived.py +0 -0
  74. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/derived_test.py +0 -0
  75. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/dynamic_evaluation.py +0 -0
  76. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/dynamic_evaluation_test.py +0 -0
  77. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/evolvable.py +0 -0
  78. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/evolvable_test.py +0 -0
  79. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/iter.py +0 -0
  80. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/iter_test.py +0 -0
  81. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/numerical.py +0 -0
  82. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/numerical_test.py +0 -0
  83. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/object_template.py +0 -0
  84. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/hyper/object_template_test.py +0 -0
  85. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/io/__init__.py +0 -0
  86. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/io/sequence.py +0 -0
  87. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/io/sequence_test.py +0 -0
  88. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/logging.py +0 -0
  89. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/logging_test.py +0 -0
  90. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/monitoring.py +0 -0
  91. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/monitoring_test.py +0 -0
  92. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/patching/__init__.py +0 -0
  93. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/patching/object_factory.py +0 -0
  94. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/patching/object_factory_test.py +0 -0
  95. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/patching/pattern_based.py +0 -0
  96. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/patching/pattern_based_test.py +0 -0
  97. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/patching/rule_based.py +0 -0
  98. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/patching/rule_based_test.py +0 -0
  99. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/__init__.py +0 -0
  100. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/boilerplate.py +0 -0
  101. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/boilerplate_test.py +0 -0
  102. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/class_wrapper.py +0 -0
  103. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/class_wrapper_test.py +0 -0
  104. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/compounding.py +0 -0
  105. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/compounding_test.py +0 -0
  106. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/contextual_object.py +0 -0
  107. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/contextual_object_test.py +0 -0
  108. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/diff.py +0 -0
  109. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/diff_test.py +0 -0
  110. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/error_info.py +0 -0
  111. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/error_info_test.py +0 -0
  112. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/flags.py +0 -0
  113. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/flags_test.py +0 -0
  114. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/functor.py +0 -0
  115. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/functor_test.py +0 -0
  116. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/inferred.py +0 -0
  117. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/inferred_test.py +0 -0
  118. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/object_test.py +0 -0
  119. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/origin.py +0 -0
  120. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/origin_test.py +0 -0
  121. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/pure_symbolic.py +0 -0
  122. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/symbolize.py +0 -0
  123. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/symbolic/symbolize_test.py +0 -0
  124. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/tuning/__init__.py +0 -0
  125. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/tuning/backend.py +0 -0
  126. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/tuning/backend_test.py +0 -0
  127. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/tuning/early_stopping.py +0 -0
  128. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/tuning/local_backend.py +0 -0
  129. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/tuning/protocols.py +0 -0
  130. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/tuning/protocols_test.py +0 -0
  131. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/tuning/sample.py +0 -0
  132. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/tuning/sample_test.py +0 -0
  133. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/__init__.py +0 -0
  134. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/annotated.py +0 -0
  135. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/annotated_test.py +0 -0
  136. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/annotation_future_test.py +0 -0
  137. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/callable_ext.py +0 -0
  138. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/callable_ext_test.py +0 -0
  139. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/callable_signature.py +0 -0
  140. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/callable_signature_test.py +0 -0
  141. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/class_schema_test.py +0 -0
  142. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/custom_typing.py +0 -0
  143. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/inspect.py +0 -0
  144. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/inspect_test.py +0 -0
  145. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/key_specs.py +0 -0
  146. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/key_specs_test.py +0 -0
  147. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/pytype_support.py +0 -0
  148. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/typed_missing.py +0 -0
  149. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/typed_missing_test.py +0 -0
  150. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/value_specs.py +0 -0
  151. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/typing/value_specs_test.py +0 -0
  152. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/common_traits.py +0 -0
  153. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/common_traits_test.py +0 -0
  154. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/docstr_utils.py +0 -0
  155. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/docstr_utils_test.py +0 -0
  156. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/error_utils.py +0 -0
  157. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/error_utils_test.py +0 -0
  158. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/formatting.py +0 -0
  159. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/formatting_test.py +0 -0
  160. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/hierarchical.py +0 -0
  161. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/hierarchical_test.py +0 -0
  162. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/missing.py +0 -0
  163. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/missing_test.py +0 -0
  164. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/text_color.py +0 -0
  165. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/text_color_test.py +0 -0
  166. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/thread_local.py +0 -0
  167. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/thread_local_test.py +0 -0
  168. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/timing.py +0 -0
  169. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/timing_test.py +0 -0
  170. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/value_location.py +0 -0
  171. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/utils/value_location_test.py +0 -0
  172. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/__init__.py +0 -0
  173. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/base.py +0 -0
  174. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/base_test.py +0 -0
  175. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/__init__.py +0 -0
  176. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/base.py +0 -0
  177. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/base_test.py +0 -0
  178. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/controls/__init__.py +0 -0
  179. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/controls/base.py +0 -0
  180. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/controls/label.py +0 -0
  181. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/controls/label_test.py +0 -0
  182. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/controls/progress_bar.py +0 -0
  183. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/controls/progress_bar_test.py +0 -0
  184. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/controls/tooltip.py +0 -0
  185. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/controls/tooltip_test.py +0 -0
  186. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/tree_view.py +0 -0
  187. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/core/views/html/tree_view_test.py +0 -0
  188. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/__init__.py +0 -0
  189. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/early_stopping/__init__.py +0 -0
  190. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/early_stopping/base.py +0 -0
  191. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/early_stopping/base_test.py +0 -0
  192. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/early_stopping/step_wise.py +0 -0
  193. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/early_stopping/step_wise_test.py +0 -0
  194. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/__init__.py +0 -0
  195. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/base.py +0 -0
  196. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/hill_climb.py +0 -0
  197. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/hill_climb_test.py +0 -0
  198. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/mutators.py +0 -0
  199. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/mutators_test.py +0 -0
  200. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/neat.py +0 -0
  201. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/neat_test.py +0 -0
  202. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/nsga2.py +0 -0
  203. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/nsga2_test.py +0 -0
  204. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/recombinators.py +0 -0
  205. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/recombinators_test.py +0 -0
  206. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/regularized_evolution.py +0 -0
  207. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/regularized_evolution_test.py +0 -0
  208. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/selectors.py +0 -0
  209. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/selectors_test.py +0 -0
  210. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/where.py +0 -0
  211. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/evolution/where_test.py +0 -0
  212. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/mutfun/__init__.py +0 -0
  213. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/mutfun/base.py +0 -0
  214. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/mutfun/base_test.py +0 -0
  215. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/mutfun/basic_ops.py +0 -0
  216. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/mutfun/basic_ops_test.py +0 -0
  217. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/scalars/__init__.py +0 -0
  218. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/scalars/base.py +0 -0
  219. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/scalars/base_test.py +0 -0
  220. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/scalars/maths.py +0 -0
  221. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/scalars/maths_test.py +0 -0
  222. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/scalars/randoms.py +0 -0
  223. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/scalars/randoms_test.py +0 -0
  224. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/scalars/step_wise.py +0 -0
  225. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove/ext/scalars/step_wise_test.py +0 -0
  226. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove.egg-info/SOURCES.txt +0 -0
  227. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove.egg-info/dependency_links.txt +0 -0
  228. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove.egg-info/requires.txt +0 -0
  229. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/pyglove.egg-info/top_level.txt +0 -0
  230. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/setup.cfg +0 -0
  231. {pyglove-0.5.0.dev202510140809 → pyglove-0.5.0.dev202511161718}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyglove
3
- Version: 0.5.0.dev202510140809
3
+ Version: 0.5.0.dev202511161718
4
4
  Summary: PyGlove: A library for manipulating Python objects.
5
5
  Home-page: https://github.com/google/pyglove
6
6
  Author: PyGlove Authors
@@ -1447,6 +1447,7 @@ class DNA(symbolic.Object):
1447
1447
  *,
1448
1448
  allow_partial: bool = False,
1449
1449
  root_path: Optional[utils.KeyPath] = None,
1450
+ **kwargs,
1450
1451
  ) -> 'DNA':
1451
1452
  """Class method that load a DNA from a JSON value.
1452
1453
 
@@ -1454,6 +1455,7 @@ class DNA(symbolic.Object):
1454
1455
  json_value: Input JSON value, only JSON dict is acceptable.
1455
1456
  allow_partial: Whether to allow elements of the list to be partial.
1456
1457
  root_path: KeyPath of loaded object in its object tree.
1458
+ **kwargs: Keyword arguments that will be passed to symbolic.from_json.
1457
1459
 
1458
1460
  Returns:
1459
1461
  A DNA object.
@@ -1463,16 +1465,18 @@ class DNA(symbolic.Object):
1463
1465
  # NOTE(daiyip): DNA.parse will validate the input. Therefore, we can
1464
1466
  # disable runtime type check during constructing the DNA objects.
1465
1467
  with symbolic.enable_type_check(False):
1466
- dna = DNA.parse(symbolic.from_json(json_value.get('value')))
1468
+ dna = DNA.parse(symbolic.from_json(json_value.get('value'), **kwargs))
1467
1469
  if 'metadata' in json_value:
1468
1470
  dna.rebind(
1469
- metadata=symbolic.from_json(json_value.get('metadata')),
1471
+ metadata=symbolic.from_json(json_value.get('metadata'), **kwargs),
1470
1472
  raise_on_no_change=False, skip_notification=True)
1471
1473
  else:
1472
1474
  dna = super(DNA, cls).from_json(
1473
1475
  json_value,
1474
1476
  allow_partial=allow_partial,
1475
- root_path=root_path) # pytype: disable=bad-return-type
1477
+ root_path=root_path,
1478
+ **kwargs,
1479
+ ) # pytype: disable=bad-return-type
1476
1480
  assert isinstance(dna, DNA)
1477
1481
  if cloneable_metadata_keys:
1478
1482
  dna._cloneable_metadata_keys = set(cloneable_metadata_keys) # pylint: disable=protected-access
@@ -14,8 +14,12 @@
14
14
  """Pluggable file system."""
15
15
 
16
16
  import abc
17
+ import fnmatch
18
+ import glob as std_glob
17
19
  import io
18
20
  import os
21
+ import re
22
+ import time
19
23
  from typing import Any, Literal, Optional, Union
20
24
 
21
25
 
@@ -84,6 +88,10 @@ class FileSystem(metaclass=abc.ABCMeta):
84
88
  def exists(self, path: Union[str, os.PathLike[str]]) -> bool:
85
89
  """Returns True if a path exists."""
86
90
 
91
+ @abc.abstractmethod
92
+ def glob(self, pattern: Union[str, os.PathLike[str]]) -> list[str]:
93
+ """Lists all files or sub-directories."""
94
+
87
95
  @abc.abstractmethod
88
96
  def listdir(self, path: Union[str, os.PathLike[str]]) -> list[str]:
89
97
  """Lists all files or sub-directories."""
@@ -111,6 +119,14 @@ class FileSystem(metaclass=abc.ABCMeta):
111
119
  def rm(self, path: Union[str, os.PathLike[str]]) -> None:
112
120
  """Removes a file based on a path."""
113
121
 
122
+ @abc.abstractmethod
123
+ def rename(
124
+ self,
125
+ oldpath: Union[str, os.PathLike[str]],
126
+ newpath: Union[str, os.PathLike[str]],
127
+ ) -> None:
128
+ """Renames a file based on a path."""
129
+
114
130
  @abc.abstractmethod
115
131
  def rmdir(self, path: Union[str, os.PathLike[str]]) -> bool:
116
132
  """Removes a directory based on a path."""
@@ -137,9 +153,10 @@ def resolve_path(path: Union[str, os.PathLike[str]]) -> str:
137
153
  class StdFile(File):
138
154
  """The standard file."""
139
155
 
140
- def __init__(self, file_object) -> None:
156
+ def __init__(self, file_object, path: Union[str, os.PathLike[str]]) -> None:
141
157
  super().__init__()
142
158
  self._file_object = file_object
159
+ self._path = path
143
160
 
144
161
  def read(self, size: Optional[int] = None) -> Union[str, bytes]:
145
162
  return self._file_object.read(size)
@@ -162,6 +179,14 @@ class StdFile(File):
162
179
  def close(self) -> None:
163
180
  self._file_object.close()
164
181
 
182
+ # For some file systems, the file might not be immediately available
183
+ # after writing. We retry for a few times to ensure the file is
184
+ # world-readable.
185
+ while True:
186
+ if os.path.exists(self._path):
187
+ break
188
+ time.sleep(0.1)
189
+
165
190
 
166
191
  class StdFileSystem(FileSystem):
167
192
  """The standard file system."""
@@ -169,7 +194,7 @@ class StdFileSystem(FileSystem):
169
194
  def open(
170
195
  self, path: Union[str, os.PathLike[str]], mode: str = 'r', **kwargs
171
196
  ) -> File:
172
- return StdFile(io.open(path, mode, **kwargs))
197
+ return StdFile(io.open(path, mode, **kwargs), path)
173
198
 
174
199
  def chmod(self, path: Union[str, os.PathLike[str]], mode: int) -> None:
175
200
  os.chmod(path, mode)
@@ -177,6 +202,9 @@ class StdFileSystem(FileSystem):
177
202
  def exists(self, path: Union[str, os.PathLike[str]]) -> bool:
178
203
  return os.path.exists(path)
179
204
 
205
+ def glob(self, pattern: Union[str, os.PathLike[str]]) -> list[str]:
206
+ return std_glob.glob(pattern)
207
+
180
208
  def listdir(self, path: Union[str, os.PathLike[str]]) -> list[str]:
181
209
  return os.listdir(path)
182
210
 
@@ -196,6 +224,13 @@ class StdFileSystem(FileSystem):
196
224
  ) -> None:
197
225
  os.makedirs(path, mode, exist_ok)
198
226
 
227
+ def rename(
228
+ self,
229
+ oldpath: Union[str, os.PathLike[str]],
230
+ newpath: Union[str, os.PathLike[str]],
231
+ ) -> None:
232
+ os.rename(oldpath, newpath)
233
+
199
234
  def rm(self, path: Union[str, os.PathLike[str]]) -> None:
200
235
  os.remove(path)
201
236
 
@@ -286,6 +321,21 @@ class MemoryFileSystem(FileSystem):
286
321
  def exists(self, path: Union[str, os.PathLike[str]]) -> bool:
287
322
  return self._locate(path) is not None
288
323
 
324
+ def glob(self, pattern: Union[str, os.PathLike[str]]) -> list[str]:
325
+ pattern = resolve_path(pattern)
326
+ regex = re.compile(fnmatch.translate(pattern))
327
+
328
+ results = []
329
+ def _traverse(node, prefix_parts):
330
+ for k, v in node.items():
331
+ path = self._prefix + '/'.join(prefix_parts + [k])
332
+ if regex.match(path):
333
+ results.append(path)
334
+ if isinstance(v, dict):
335
+ _traverse(v, prefix_parts + [k])
336
+ _traverse(self._root, [])
337
+ return results
338
+
289
339
  def listdir(self, path: Union[str, os.PathLike[str]]) -> list[str]:
290
340
  d = self._locate(path)
291
341
  if not isinstance(d, dict):
@@ -338,6 +388,46 @@ class MemoryFileSystem(FileSystem):
338
388
  raise NotADirectoryError(path)
339
389
  current = entry
340
390
 
391
+ def rename(
392
+ self,
393
+ oldpath: Union[str, os.PathLike[str]],
394
+ newpath: Union[str, os.PathLike[str]],
395
+ ) -> None:
396
+ oldpath_str = resolve_path(oldpath)
397
+ newpath_str = resolve_path(newpath)
398
+
399
+ old_parent_dir, old_name = self._parent_and_name(oldpath_str)
400
+ entry = old_parent_dir.get(old_name)
401
+
402
+ if entry is None:
403
+ raise FileNotFoundError(oldpath_str)
404
+
405
+ new_entry = self._locate(newpath_str)
406
+ if new_entry is not None:
407
+ if isinstance(entry, dict): # oldpath is dir
408
+ if not isinstance(new_entry, dict):
409
+ raise NotADirectoryError(
410
+ f'Cannot rename directory {oldpath_str!r} '
411
+ f'to non-directory {newpath_str!r}')
412
+ elif new_entry:
413
+ raise OSError(f'Directory not empty: {newpath_str!r}')
414
+ else:
415
+ self.rmdir(newpath_str)
416
+ else: # oldpath is file
417
+ if isinstance(new_entry, dict):
418
+ raise IsADirectoryError(
419
+ f'Cannot rename non-directory {oldpath_str!r} '
420
+ f'to directory {newpath_str!r}')
421
+ else:
422
+ self.rm(newpath_str)
423
+ elif isinstance(entry, dict) and newpath_str.startswith(oldpath_str + '/'):
424
+ raise OSError(f'Cannot move directory {oldpath_str!r} '
425
+ f'to a subdirectory of itself {newpath_str!r}')
426
+
427
+ new_parent_dir, new_name = self._parent_and_name(newpath_str)
428
+ new_parent_dir[new_name] = entry
429
+ del old_parent_dir[old_name]
430
+
341
431
  def rm(self, path: Union[str, os.PathLike[str]]) -> None:
342
432
  parent_dir, name = self._parent_and_name(path)
343
433
  entry = parent_dir.get(name)
@@ -458,6 +548,14 @@ def writefile(
458
548
  chmod(path, perms)
459
549
 
460
550
 
551
+ def rename(
552
+ oldpath: Union[str, os.PathLike[str]],
553
+ newpath: Union[str, os.PathLike[str]],
554
+ ) -> None:
555
+ """Renames a file."""
556
+ _fs.get(oldpath).rename(oldpath, newpath)
557
+
558
+
461
559
  def rm(path: Union[str, os.PathLike[str]]) -> None:
462
560
  """Removes a file."""
463
561
  _fs.get(path).rm(path)
@@ -468,6 +566,11 @@ def path_exists(path: Union[str, os.PathLike[str]]) -> bool:
468
566
  return _fs.get(path).exists(path)
469
567
 
470
568
 
569
+ def glob(pattern: Union[str, os.PathLike[str]]) -> list[str]:
570
+ """Lists all files or sub-directories."""
571
+ return _fs.get(pattern).glob(pattern)
572
+
573
+
471
574
  def listdir(
472
575
  path: Union[str, os.PathLike[str]], fullpath: bool = False
473
576
  ) -> list[str]: # pylint: disable=redefined-builtin
@@ -82,6 +82,75 @@ class StdFileSystemTest(unittest.TestCase):
82
82
  fs.rmdirs(os.path.join(dir_a, 'b/c'))
83
83
  self.assertEqual(sorted(fs.listdir(dir_a)), ['file1']) # pylint: disable=g-generic-assert
84
84
 
85
+ def test_rename(self):
86
+ tmp_dir = tempfile.mkdtemp()
87
+ fs = file_system.StdFileSystem()
88
+
89
+ _ = fs.mkdirs(os.path.join(tmp_dir, 'a/b'))
90
+ file_foo = os.path.join(tmp_dir, 'a/foo.txt')
91
+ file_bar = os.path.join(tmp_dir, 'a/bar.txt')
92
+
93
+ with fs.open(file_foo, 'w') as f:
94
+ f.write('foo')
95
+ with fs.open(file_bar, 'w') as f:
96
+ f.write('bar')
97
+
98
+ # Rename file to a new name.
99
+ file_foo_new = os.path.join(tmp_dir, 'a/foo-new.txt')
100
+ fs.rename(file_foo, file_foo_new)
101
+ self.assertFalse(fs.exists(file_foo))
102
+ self.assertTrue(fs.exists(file_foo_new))
103
+
104
+ # Rename file to an existing file name.
105
+ fs.rename(file_foo_new, file_bar)
106
+ self.assertFalse(fs.exists(file_foo_new))
107
+ with fs.open(file_bar, 'r') as f:
108
+ self.assertEqual(f.read(), 'foo')
109
+
110
+ # Rename directory to a new name.
111
+ dir_b = os.path.join(tmp_dir, 'a/b')
112
+ dir_c = os.path.join(tmp_dir, 'a/c')
113
+ fs.rename(dir_b, dir_c)
114
+ self.assertFalse(fs.exists(dir_b))
115
+ self.assertTrue(fs.exists(dir_c))
116
+ self.assertTrue(fs.isdir(dir_c))
117
+
118
+ # Rename directory to an existing empty directory.
119
+ dir_d = os.path.join(tmp_dir, 'a/d')
120
+ fs.mkdirs(dir_d)
121
+ fs.rename(dir_c, dir_d)
122
+ self.assertFalse(fs.exists(dir_c))
123
+ self.assertTrue(fs.exists(dir_d))
124
+
125
+ # Rename directory to a non-empty directory.
126
+ dir_x = os.path.join(tmp_dir, 'x')
127
+ dir_a = os.path.join(tmp_dir, 'a')
128
+ fs.mkdirs(os.path.join(dir_x, 'y'))
129
+ with self.assertRaises(OSError):
130
+ fs.rename(dir_a, dir_x)
131
+ self.assertTrue(fs.exists(dir_a))
132
+ self.assertTrue(fs.exists(os.path.join(dir_x, 'y')))
133
+
134
+ # Errors
135
+ dir_u = os.path.join(tmp_dir, 'u')
136
+ dir_u_v = os.path.join(dir_u, 'v')
137
+ file_u_a = os.path.join(dir_u, 'a.txt')
138
+ fs.mkdirs(dir_u_v)
139
+ with fs.open(file_u_a, 'w') as f:
140
+ f.write('a')
141
+
142
+ with self.assertRaises((OSError, NotADirectoryError)):
143
+ fs.rename(dir_u, file_u_a)
144
+
145
+ with self.assertRaises(IsADirectoryError):
146
+ fs.rename(file_u_a, dir_u_v)
147
+
148
+ with self.assertRaises(FileNotFoundError):
149
+ fs.rename(
150
+ os.path.join(tmp_dir, 'non-existent'),
151
+ os.path.join(tmp_dir, 'y')
152
+ )
153
+
85
154
 
86
155
  class MemoryFileSystemTest(unittest.TestCase):
87
156
 
@@ -180,6 +249,94 @@ class MemoryFileSystemTest(unittest.TestCase):
180
249
  fs.rmdirs(os.path.join(dir_a, 'b/c'))
181
250
  self.assertEqual(fs.listdir(dir_a), ['file1']) # pylint: disable=g-generic-assert
182
251
 
252
+ def test_glob(self):
253
+ fs = file_system.MemoryFileSystem()
254
+ fs.mkdirs('/mem/a/b/c')
255
+ with fs.open('/mem/a/foo.txt', 'w') as f:
256
+ f.write('foo')
257
+ with fs.open('/mem/a/bar.json', 'w') as f:
258
+ f.write('bar')
259
+ with fs.open('/mem/a/b/baz.txt', 'w') as f:
260
+ f.write('baz')
261
+
262
+ self.assertEqual(
263
+ sorted(fs.glob('/mem/a/*')),
264
+ ['/mem/a/b', '/mem/a/b/baz.txt', '/mem/a/b/c',
265
+ '/mem/a/bar.json', '/mem/a/foo.txt'])
266
+ self.assertEqual(
267
+ sorted(fs.glob('/mem/a/*.txt')),
268
+ ['/mem/a/b/baz.txt', '/mem/a/foo.txt'])
269
+ self.assertEqual(
270
+ sorted(fs.glob('/mem/a/b/*')),
271
+ ['/mem/a/b/baz.txt', '/mem/a/b/c'])
272
+ self.assertEqual(fs.glob('/mem/a/b/*.txt'), ['/mem/a/b/baz.txt'])
273
+ self.assertEqual(fs.glob('/mem/a/b/c/*'), [])
274
+ self.assertEqual(fs.glob('/mem/a/???.txt'), ['/mem/a/foo.txt'])
275
+ self.assertEqual(fs.glob('/mem/a/bar.*'), ['/mem/a/bar.json'])
276
+ self.assertEqual(
277
+ sorted(fs.glob('/mem/a/*.*')),
278
+ ['/mem/a/b/baz.txt', '/mem/a/bar.json', '/mem/a/foo.txt'])
279
+
280
+ def test_rename(self):
281
+ fs = file_system.MemoryFileSystem()
282
+ fs.mkdirs('/mem/a/b')
283
+ with fs.open('/mem/a/foo.txt', 'w') as f:
284
+ f.write('foo')
285
+ with fs.open('/mem/a/bar.txt', 'w') as f:
286
+ f.write('bar')
287
+
288
+ # Rename file to a new name.
289
+ fs.rename('/mem/a/foo.txt', '/mem/a/foo-new.txt')
290
+ self.assertFalse(fs.exists('/mem/a/foo.txt'))
291
+ self.assertTrue(fs.exists('/mem/a/foo-new.txt'))
292
+
293
+ # Rename file to an existing file name.
294
+ fs.rename('/mem/a/foo-new.txt', '/mem/a/bar.txt')
295
+ self.assertFalse(fs.exists('/mem/a/foo-new.txt'))
296
+ with fs.open('/mem/a/bar.txt', 'r') as f:
297
+ self.assertEqual(f.read(), 'foo')
298
+
299
+ # Rename directory to a new name.
300
+ fs.rename('/mem/a/b', '/mem/a/c')
301
+ self.assertFalse(fs.exists('/mem/a/b'))
302
+ self.assertTrue(fs.exists('/mem/a/c'))
303
+ self.assertTrue(fs.isdir('/mem/a/c'))
304
+
305
+ # Rename directory to an existing empty directory.
306
+ fs.mkdirs('/mem/a/d')
307
+ fs.rename('/mem/a/c', '/mem/a/d')
308
+ self.assertFalse(fs.exists('/mem/a/c'))
309
+ self.assertTrue(fs.exists('/mem/a/d'))
310
+
311
+ # Rename directory to a non-empty directory.
312
+ fs.mkdirs('/mem/x/y')
313
+ with self.assertRaisesRegex(OSError, "Directory not empty: '/mem/x'"):
314
+ fs.rename('/mem/a', '/mem/x')
315
+ self.assertTrue(fs.exists('/mem/a'))
316
+ self.assertTrue(fs.exists('/mem/x/y'))
317
+
318
+ # Errors
319
+ fs.mkdirs('/mem/u/v')
320
+ with fs.open('/mem/u/a.txt', 'w') as f:
321
+ f.write('a')
322
+
323
+ with self.assertRaisesRegex(
324
+ OSError, "Cannot move directory '/mem/u' to a subdirectory of itself"):
325
+ fs.rename('/mem/u', '/mem/u/v/w')
326
+
327
+ with self.assertRaisesRegex(
328
+ NotADirectoryError,
329
+ "Cannot rename directory '/mem/u' to non-directory '/mem/u/a.txt'"):
330
+ fs.rename('/mem/u', '/mem/u/a.txt')
331
+
332
+ with self.assertRaisesRegex(
333
+ IsADirectoryError,
334
+ "Cannot rename non-directory '/mem/u/a.txt' to directory '/mem/u/v'"):
335
+ fs.rename('/mem/u/a.txt', '/mem/u/v')
336
+
337
+ with self.assertRaises(FileNotFoundError):
338
+ fs.rename('/mem/non-existent', '/mem/y')
339
+
183
340
 
184
341
  class FileIoApiTest(unittest.TestCase):
185
342
 
@@ -217,6 +374,14 @@ class FileIoApiTest(unittest.TestCase):
217
374
  file_system.rm(file2)
218
375
  self.assertFalse(file_system.path_exists(file2))
219
376
 
377
+ # Test glob with standard file system.
378
+ glob_dir = os.path.join(tempfile.mkdtemp(), 'glob')
379
+ file_system.mkdirs(os.path.join(glob_dir, 'a/b'))
380
+ file_system.writefile(os.path.join(glob_dir, 'a/foo.txt'), 'foo')
381
+ self.assertEqual(
382
+ sorted(file_system.glob(os.path.join(glob_dir, 'a/*'))),
383
+ [os.path.join(glob_dir, 'a/b'), os.path.join(glob_dir, 'a/foo.txt')])
384
+
220
385
  def test_memory_filesystem(self):
221
386
  file1 = pathlib.Path('/mem/file1')
222
387
  with self.assertRaises(FileNotFoundError):
@@ -248,6 +413,15 @@ class FileIoApiTest(unittest.TestCase):
248
413
  file_system.rm(file2)
249
414
  self.assertFalse(file_system.path_exists(file2))
250
415
 
416
+ # Test glob with memory file system.
417
+ file_system.mkdirs('/mem/g/a/b')
418
+ file_system.writefile('/mem/g/a/foo.txt', 'foo')
419
+ file_system.rename('/mem/g/a/foo.txt', '/mem/g/a/foo2.txt')
420
+ file_system.writefile('/mem/g/a/b/bar.txt', 'bar')
421
+ self.assertEqual(
422
+ sorted(file_system.glob('/mem/g/a/*')),
423
+ ['/mem/g/a/b', '/mem/g/a/b/bar.txt', '/mem/g/a/foo2.txt'])
424
+
251
425
 
252
426
  if __name__ == '__main__':
253
427
  unittest.main()
@@ -503,10 +503,12 @@ class Symbolic(
503
503
  return default
504
504
 
505
505
  def _sym_inferred(self, key: Union[str, int], **kwargs) -> Any:
506
- v = self.sym_getattr(key)
507
- if isinstance(v, Inferential):
508
- v = v.infer(**kwargs)
509
- return v
506
+ return self._infer_if_applicable(self.sym_getattr(key), **kwargs)
507
+
508
+ def _infer_if_applicable(self, value: Any, **kwargs) -> Any:
509
+ if isinstance(value, Inferential):
510
+ return value.infer(**kwargs)
511
+ return value
510
512
 
511
513
  @abc.abstractmethod
512
514
  def sym_keys(self) -> Iterator[Union[str, int]]:
@@ -944,7 +946,7 @@ class Symbolic(
944
946
 
945
947
  def to_json(self, **kwargs) -> utils.JSONValueType:
946
948
  """Alias for `sym_jsonify`."""
947
- return to_json(self, **kwargs)
949
+ return utils.to_json(self, **kwargs)
948
950
 
949
951
  def to_json_str(self, json_indent: Optional[int] = None, **kwargs) -> str:
950
952
  """Serializes current object into a JSON string."""
@@ -1983,10 +1985,12 @@ def is_abstract(x: Any) -> bool:
1983
1985
  def contains(
1984
1986
  x: Any,
1985
1987
  value: Any = None,
1986
- type: Optional[Union[ # pylint: disable=redefined-builtin
1988
+ type: Union[ # pylint: disable=redefined-builtin
1987
1989
  Type[Any],
1988
- Tuple[Type[Any]]]]=None
1989
- ) -> bool:
1990
+ Tuple[Type[Any], ...],
1991
+ None,
1992
+ ]=None,
1993
+ ) -> bool:
1990
1994
  """Returns if a value contains values of specific type.
1991
1995
 
1992
1996
  Example::
@@ -2035,10 +2039,12 @@ def contains(
2035
2039
  def from_json(
2036
2040
  json_value: Any,
2037
2041
  *,
2038
- allow_partial: bool = False,
2039
- root_path: Optional[utils.KeyPath] = None,
2042
+ context: Optional[utils.JSONConversionContext] = None,
2043
+ auto_symbolic: bool = True,
2040
2044
  auto_import: bool = True,
2041
2045
  auto_dict: bool = False,
2046
+ allow_partial: bool = False,
2047
+ root_path: Optional[utils.KeyPath] = None,
2042
2048
  value_spec: Optional[pg_typing.ValueSpec] = None,
2043
2049
  **kwargs,
2044
2050
  ) -> Any:
@@ -2059,14 +2065,18 @@ def from_json(
2059
2065
 
2060
2066
  Args:
2061
2067
  json_value: Input JSON value.
2062
- allow_partial: Whether to allow elements of the list to be partial.
2063
- root_path: KeyPath of loaded object in its object tree.
2068
+ context: JSON conversion context.
2069
+ auto_symbolic: If True, list and dict will be automatically converted to
2070
+ `pg.List` and `pg.Dict`. Otherwise, they will be plain lists
2071
+ and dicts.
2064
2072
  auto_import: If True, when a '_type' is not registered, PyGlove will
2065
2073
  identify its parent module and automatically import it. For example,
2066
2074
  if the type is 'foo.bar.A', PyGlove will try to import 'foo.bar' and
2067
2075
  find the class 'A' within the imported module.
2068
2076
  auto_dict: If True, dict with '_type' that cannot be loaded will remain
2069
2077
  as dict, with '_type' renamed to 'type_name'.
2078
+ allow_partial: Whether to allow elements of the list to be partial.
2079
+ root_path: KeyPath of loaded object in its object tree.
2070
2080
  value_spec: The value spec for the symbolic list or dict.
2071
2081
  **kwargs: Allow passing through keyword arguments to from_json of specific
2072
2082
  types.
@@ -2082,10 +2092,18 @@ def from_json(
2082
2092
  if isinstance(json_value, Symbolic):
2083
2093
  return json_value
2084
2094
 
2095
+ if context is None:
2096
+ if (isinstance(json_value, dict) and (
2097
+ context_node := json_value.get(utils.JSONConvertible.CONTEXT_KEY))):
2098
+ context = utils.JSONConversionContext.from_json(context_node, **kwargs)
2099
+ json_value = json_value[utils.JSONConvertible.ROOT_VALUE_KEY]
2100
+ else:
2101
+ context = utils.JSONConversionContext()
2102
+
2085
2103
  typename_resolved = kwargs.pop('_typename_resolved', False)
2086
2104
  if not typename_resolved:
2087
2105
  json_value = utils.json_conversion.resolve_typenames(
2088
- json_value, auto_import=auto_import, auto_dict=auto_dict
2106
+ json_value, auto_import, auto_dict
2089
2107
  )
2090
2108
 
2091
2109
  def _load_child(k, v):
@@ -2094,6 +2112,7 @@ def from_json(
2094
2112
  root_path=utils.KeyPath(k, root_path),
2095
2113
  _typename_resolved=True,
2096
2114
  allow_partial=allow_partial,
2115
+ context=context,
2097
2116
  **kwargs,
2098
2117
  )
2099
2118
 
@@ -2109,24 +2128,42 @@ def from_json(
2109
2128
  )
2110
2129
  )
2111
2130
  return tuple(_load_child(i, v) for i, v in enumerate(json_value[1:]))
2112
- return Symbolic.ListType.from_json( # pytype: disable=attribute-error
2131
+ if json_value and json_value[0] == utils.JSONConvertible.SYMBOLIC_MARKER:
2132
+ auto_symbolic = True
2133
+ if auto_symbolic:
2134
+ from_json_fn = Symbolic.ListType.from_json # pytype: disable=attribute-error
2135
+ else:
2136
+ from_json_fn = utils.from_json
2137
+ return from_json_fn(
2113
2138
  json_value,
2139
+ context=context,
2114
2140
  value_spec=value_spec,
2115
2141
  root_path=root_path,
2116
2142
  allow_partial=allow_partial,
2117
2143
  **kwargs,
2118
2144
  )
2119
2145
  elif isinstance(json_value, dict):
2146
+ if utils.JSONConvertible.REF_KEY in json_value:
2147
+ x = context.get_shared(
2148
+ json_value[utils.JSONConvertible.REF_KEY]
2149
+ ).value
2150
+ return x
2120
2151
  if utils.JSONConvertible.TYPE_NAME_KEY not in json_value:
2121
- return Symbolic.DictType.from_json( # pytype: disable=attribute-error
2122
- json_value,
2123
- value_spec=value_spec,
2124
- root_path=root_path,
2125
- allow_partial=allow_partial,
2126
- **kwargs,
2152
+ auto_symbolic = json_value.get(
2153
+ utils.JSONConvertible.SYMBOLIC_MARKER, auto_symbolic
2127
2154
  )
2155
+ if auto_symbolic:
2156
+ return Symbolic.DictType.from_json( # pytype: disable=attribute-error
2157
+ json_value,
2158
+ context=context,
2159
+ value_spec=value_spec,
2160
+ root_path=root_path,
2161
+ allow_partial=allow_partial,
2162
+ **kwargs,
2163
+ )
2128
2164
  return utils.from_json(
2129
2165
  json_value,
2166
+ context=context,
2130
2167
  _typename_resolved=True,
2131
2168
  root_path=root_path,
2132
2169
  allow_partial=allow_partial,
@@ -2138,10 +2175,12 @@ def from_json(
2138
2175
  def from_json_str(
2139
2176
  json_str: str,
2140
2177
  *,
2141
- allow_partial: bool = False,
2142
- root_path: Optional[utils.KeyPath] = None,
2178
+ context: Optional[utils.JSONConversionContext] = None,
2143
2179
  auto_import: bool = True,
2144
2180
  auto_dict: bool = False,
2181
+ allow_partial: bool = False,
2182
+ root_path: Optional[utils.KeyPath] = None,
2183
+ value_spec: Optional[pg_typing.ValueSpec] = None,
2145
2184
  **kwargs,
2146
2185
  ) -> Any:
2147
2186
  """Deserialize (maybe) symbolic object from JSON string.
@@ -2161,15 +2200,17 @@ def from_json_str(
2161
2200
 
2162
2201
  Args:
2163
2202
  json_str: JSON string.
2164
- allow_partial: If True, allow a partial symbolic object to be created.
2165
- Otherwise error will be raised on partial value.
2166
- root_path: The symbolic path used for the deserialized root object.
2203
+ context: JSON conversion context.
2167
2204
  auto_import: If True, when a '_type' is not registered, PyGlove will
2168
2205
  identify its parent module and automatically import it. For example,
2169
2206
  if the type is 'foo.bar.A', PyGlove will try to import 'foo.bar' and
2170
2207
  find the class 'A' within the imported module.
2171
2208
  auto_dict: If True, dict with '_type' that cannot be loaded will remain
2172
2209
  as dict, with '_type' renamed to 'type_name'.
2210
+ allow_partial: If True, allow a partial symbolic object to be created.
2211
+ Otherwise error will be raised on partial value.
2212
+ root_path: The symbolic path used for the deserialized root object.
2213
+ value_spec: The value spec for the symbolic list or dict.
2173
2214
  **kwargs: Additional keyword arguments that will be passed to
2174
2215
  ``pg.from_json``.
2175
2216
 
@@ -2193,10 +2234,12 @@ def from_json_str(
2193
2234
 
2194
2235
  return from_json(
2195
2236
  _decode_int_keys(json.loads(json_str)),
2196
- allow_partial=allow_partial,
2197
- root_path=root_path,
2237
+ context=context,
2198
2238
  auto_import=auto_import,
2199
2239
  auto_dict=auto_dict,
2240
+ allow_partial=allow_partial,
2241
+ root_path=root_path,
2242
+ value_spec=value_spec,
2200
2243
  **kwargs
2201
2244
  )
2202
2245
 
@@ -2232,10 +2275,6 @@ def to_json(value: Any, **kwargs) -> Any:
2232
2275
  Returns:
2233
2276
  JSON value.
2234
2277
  """
2235
- # NOTE(daiyip): special handling `sym_jsonify` since symbolized
2236
- # classes may have conflicting `to_json` method in their existing classes.
2237
- if isinstance(value, Symbolic):
2238
- return value.sym_jsonify(**kwargs)
2239
2278
  return utils.to_json(value, **kwargs)
2240
2279
 
2241
2280
 
@@ -20,9 +20,9 @@ from pyglove.core import typing as pg_typing
20
20
  from pyglove.core import utils
21
21
  from pyglove.core import views
22
22
  from pyglove.core.symbolic import base
23
- from pyglove.core.symbolic.dict import Dict
24
- from pyglove.core.symbolic.inferred import ValueFromParentChain
25
- from pyglove.core.symbolic.object import Object
23
+ from pyglove.core.symbolic.dict import Dict # pylint: disable=g-importing-member
24
+ from pyglove.core.symbolic.inferred import ValueFromParentChain # pylint: disable=g-importing-member
25
+ from pyglove.core.symbolic.object import Object # pylint: disable=g-importing-member
26
26
 
27
27
 
28
28
  class FieldUpdateTest(unittest.TestCase):