pyglove 0.5.0.dev202511161718__tar.gz → 0.5.0.dev202511250811__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 (233) hide show
  1. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/PKG-INFO +8 -1
  2. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/io/file_system.py +199 -9
  3. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/io/file_system_test.py +117 -0
  4. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/__init__.py +7 -0
  5. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/base.py +24 -9
  6. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/object.py +2 -1
  7. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/object_test.py +13 -10
  8. pyglove-0.5.0.dev202511250811/pyglove/core/symbolic/unknown_symbols.py +147 -0
  9. pyglove-0.5.0.dev202511250811/pyglove/core/symbolic/unknown_symbols_test.py +100 -0
  10. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/value_specs.py +5 -1
  11. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/value_specs_test.py +5 -0
  12. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/json_conversion.py +107 -49
  13. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/json_conversion_test.py +83 -9
  14. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove.egg-info/PKG-INFO +8 -1
  15. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove.egg-info/SOURCES.txt +2 -0
  16. pyglove-0.5.0.dev202511250811/pyglove.egg-info/requires.txt +10 -0
  17. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/setup.py +36 -14
  18. pyglove-0.5.0.dev202511161718/pyglove.egg-info/requires.txt +0 -2
  19. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/LICENSE +0 -0
  20. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/README.md +0 -0
  21. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/__init__.py +0 -0
  22. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/__init__.py +0 -0
  23. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/coding/__init__.py +0 -0
  24. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/coding/errors.py +0 -0
  25. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/coding/errors_test.py +0 -0
  26. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/coding/execution.py +0 -0
  27. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/coding/execution_test.py +0 -0
  28. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/coding/function_generation.py +0 -0
  29. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/coding/function_generation_test.py +0 -0
  30. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/coding/parsing.py +0 -0
  31. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/coding/parsing_test.py +0 -0
  32. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/coding/permissions.py +0 -0
  33. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/coding/permissions_test.py +0 -0
  34. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/detouring/__init__.py +0 -0
  35. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/detouring/class_detour.py +0 -0
  36. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/detouring/class_detour_test.py +0 -0
  37. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/__init__.py +0 -0
  38. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/base.py +0 -0
  39. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/base_test.py +0 -0
  40. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/categorical.py +0 -0
  41. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/categorical_test.py +0 -0
  42. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/custom.py +0 -0
  43. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/custom_test.py +0 -0
  44. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/deduping.py +0 -0
  45. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/deduping_test.py +0 -0
  46. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/dna_generator.py +0 -0
  47. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/dna_generator_test.py +0 -0
  48. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/numerical.py +0 -0
  49. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/numerical_test.py +0 -0
  50. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/random.py +0 -0
  51. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/random_test.py +0 -0
  52. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/space.py +0 -0
  53. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/space_test.py +0 -0
  54. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/sweeping.py +0 -0
  55. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/geno/sweeping_test.py +0 -0
  56. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/__init__.py +0 -0
  57. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/base.py +0 -0
  58. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/categorical.py +0 -0
  59. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/categorical_test.py +0 -0
  60. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/custom.py +0 -0
  61. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/custom_test.py +0 -0
  62. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/derived.py +0 -0
  63. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/derived_test.py +0 -0
  64. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/dynamic_evaluation.py +0 -0
  65. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/dynamic_evaluation_test.py +0 -0
  66. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/evolvable.py +0 -0
  67. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/evolvable_test.py +0 -0
  68. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/iter.py +0 -0
  69. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/iter_test.py +0 -0
  70. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/numerical.py +0 -0
  71. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/numerical_test.py +0 -0
  72. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/object_template.py +0 -0
  73. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/hyper/object_template_test.py +0 -0
  74. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/io/__init__.py +0 -0
  75. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/io/sequence.py +0 -0
  76. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/io/sequence_test.py +0 -0
  77. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/logging.py +0 -0
  78. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/logging_test.py +0 -0
  79. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/monitoring.py +0 -0
  80. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/monitoring_test.py +0 -0
  81. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/patching/__init__.py +0 -0
  82. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/patching/object_factory.py +0 -0
  83. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/patching/object_factory_test.py +0 -0
  84. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/patching/pattern_based.py +0 -0
  85. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/patching/pattern_based_test.py +0 -0
  86. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/patching/rule_based.py +0 -0
  87. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/patching/rule_based_test.py +0 -0
  88. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/base_test.py +0 -0
  89. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/boilerplate.py +0 -0
  90. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/boilerplate_test.py +0 -0
  91. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/class_wrapper.py +0 -0
  92. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/class_wrapper_test.py +0 -0
  93. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/compounding.py +0 -0
  94. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/compounding_test.py +0 -0
  95. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/contextual_object.py +0 -0
  96. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/contextual_object_test.py +0 -0
  97. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/dict.py +0 -0
  98. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/dict_test.py +0 -0
  99. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/diff.py +0 -0
  100. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/diff_test.py +0 -0
  101. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/error_info.py +0 -0
  102. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/error_info_test.py +0 -0
  103. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/flags.py +0 -0
  104. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/flags_test.py +0 -0
  105. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/functor.py +0 -0
  106. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/functor_test.py +0 -0
  107. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/inferred.py +0 -0
  108. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/inferred_test.py +0 -0
  109. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/list.py +0 -0
  110. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/list_test.py +0 -0
  111. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/origin.py +0 -0
  112. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/origin_test.py +0 -0
  113. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/pure_symbolic.py +0 -0
  114. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/ref.py +0 -0
  115. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/ref_test.py +0 -0
  116. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/symbolize.py +0 -0
  117. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/symbolic/symbolize_test.py +0 -0
  118. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/tuning/__init__.py +0 -0
  119. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/tuning/backend.py +0 -0
  120. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/tuning/backend_test.py +0 -0
  121. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/tuning/early_stopping.py +0 -0
  122. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/tuning/local_backend.py +0 -0
  123. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/tuning/protocols.py +0 -0
  124. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/tuning/protocols_test.py +0 -0
  125. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/tuning/sample.py +0 -0
  126. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/tuning/sample_test.py +0 -0
  127. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/__init__.py +0 -0
  128. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/annotated.py +0 -0
  129. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/annotated_test.py +0 -0
  130. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/annotation_conversion.py +0 -0
  131. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/annotation_conversion_test.py +0 -0
  132. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/annotation_future_test.py +0 -0
  133. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/callable_ext.py +0 -0
  134. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/callable_ext_test.py +0 -0
  135. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/callable_signature.py +0 -0
  136. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/callable_signature_test.py +0 -0
  137. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/class_schema.py +0 -0
  138. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/class_schema_test.py +0 -0
  139. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/custom_typing.py +0 -0
  140. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/inspect.py +0 -0
  141. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/inspect_test.py +0 -0
  142. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/json_schema.py +0 -0
  143. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/json_schema_test.py +0 -0
  144. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/key_specs.py +0 -0
  145. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/key_specs_test.py +0 -0
  146. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/pytype_support.py +0 -0
  147. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/type_conversion.py +0 -0
  148. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/type_conversion_test.py +0 -0
  149. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/typed_missing.py +0 -0
  150. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/typing/typed_missing_test.py +0 -0
  151. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/__init__.py +0 -0
  152. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/common_traits.py +0 -0
  153. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/common_traits_test.py +0 -0
  154. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/contextual.py +0 -0
  155. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/contextual_test.py +0 -0
  156. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/docstr_utils.py +0 -0
  157. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/docstr_utils_test.py +0 -0
  158. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/error_utils.py +0 -0
  159. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/error_utils_test.py +0 -0
  160. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/formatting.py +0 -0
  161. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/formatting_test.py +0 -0
  162. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/hierarchical.py +0 -0
  163. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/hierarchical_test.py +0 -0
  164. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/missing.py +0 -0
  165. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/missing_test.py +0 -0
  166. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/text_color.py +0 -0
  167. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/text_color_test.py +0 -0
  168. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/thread_local.py +0 -0
  169. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/thread_local_test.py +0 -0
  170. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/timing.py +0 -0
  171. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/timing_test.py +0 -0
  172. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/value_location.py +0 -0
  173. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/utils/value_location_test.py +0 -0
  174. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/__init__.py +0 -0
  175. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/base.py +0 -0
  176. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/base_test.py +0 -0
  177. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/__init__.py +0 -0
  178. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/base.py +0 -0
  179. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/base_test.py +0 -0
  180. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/controls/__init__.py +0 -0
  181. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/controls/base.py +0 -0
  182. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/controls/label.py +0 -0
  183. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/controls/label_test.py +0 -0
  184. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/controls/progress_bar.py +0 -0
  185. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/controls/progress_bar_test.py +0 -0
  186. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/controls/tab.py +0 -0
  187. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/controls/tab_test.py +0 -0
  188. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/controls/tooltip.py +0 -0
  189. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/controls/tooltip_test.py +0 -0
  190. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/tree_view.py +0 -0
  191. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/core/views/html/tree_view_test.py +0 -0
  192. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/__init__.py +0 -0
  193. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/early_stopping/__init__.py +0 -0
  194. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/early_stopping/base.py +0 -0
  195. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/early_stopping/base_test.py +0 -0
  196. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/early_stopping/step_wise.py +0 -0
  197. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/early_stopping/step_wise_test.py +0 -0
  198. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/__init__.py +0 -0
  199. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/base.py +0 -0
  200. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/base_test.py +0 -0
  201. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/hill_climb.py +0 -0
  202. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/hill_climb_test.py +0 -0
  203. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/mutators.py +0 -0
  204. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/mutators_test.py +0 -0
  205. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/neat.py +0 -0
  206. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/neat_test.py +0 -0
  207. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/nsga2.py +0 -0
  208. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/nsga2_test.py +0 -0
  209. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/recombinators.py +0 -0
  210. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/recombinators_test.py +0 -0
  211. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/regularized_evolution.py +0 -0
  212. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/regularized_evolution_test.py +0 -0
  213. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/selectors.py +0 -0
  214. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/selectors_test.py +0 -0
  215. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/where.py +0 -0
  216. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/evolution/where_test.py +0 -0
  217. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/mutfun/__init__.py +0 -0
  218. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/mutfun/base.py +0 -0
  219. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/mutfun/base_test.py +0 -0
  220. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/mutfun/basic_ops.py +0 -0
  221. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/mutfun/basic_ops_test.py +0 -0
  222. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/scalars/__init__.py +0 -0
  223. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/scalars/base.py +0 -0
  224. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/scalars/base_test.py +0 -0
  225. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/scalars/maths.py +0 -0
  226. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/scalars/maths_test.py +0 -0
  227. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/scalars/randoms.py +0 -0
  228. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/scalars/randoms_test.py +0 -0
  229. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/scalars/step_wise.py +0 -0
  230. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove/ext/scalars/step_wise_test.py +0 -0
  231. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove.egg-info/dependency_links.txt +0 -0
  232. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/pyglove.egg-info/top_level.txt +0 -0
  233. {pyglove-0.5.0.dev202511161718 → pyglove-0.5.0.dev202511250811}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyglove
3
- Version: 0.5.0.dev202511161718
3
+ Version: 0.5.0.dev202511250811
4
4
  Summary: PyGlove: A library for manipulating Python objects.
5
5
  Home-page: https://github.com/google/pyglove
6
6
  Author: PyGlove Authors
@@ -23,6 +23,12 @@ Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: docstring-parser>=0.12
25
25
  Requires-Dist: termcolor>=1.1.0
26
+ Provides-Extra: all
27
+ Requires-Dist: docstring-parser>=0.12; extra == "all"
28
+ Requires-Dist: termcolor>=1.1.0; extra == "all"
29
+ Requires-Dist: fsspec>=2023.3.0; extra == "all"
30
+ Provides-Extra: io
31
+ Requires-Dist: fsspec>=2023.3.0; extra == "io"
26
32
  Dynamic: author
27
33
  Dynamic: author-email
28
34
  Dynamic: classifier
@@ -32,6 +38,7 @@ Dynamic: home-page
32
38
  Dynamic: keywords
33
39
  Dynamic: license
34
40
  Dynamic: license-file
41
+ Dynamic: provides-extra
35
42
  Dynamic: requires-dist
36
43
  Dynamic: summary
37
44
 
@@ -19,7 +19,6 @@ import glob as std_glob
19
19
  import io
20
20
  import os
21
21
  import re
22
- import time
23
22
  from typing import Any, Literal, Optional, Union
24
23
 
25
24
 
@@ -179,14 +178,6 @@ class StdFile(File):
179
178
  def close(self) -> None:
180
179
  self._file_object.close()
181
180
 
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
-
190
181
 
191
182
  class StdFileSystem(FileSystem):
192
183
  """The standard file system."""
@@ -502,6 +493,205 @@ def add_file_system(prefix: str, fs: FileSystem) -> None:
502
493
  add_file_system('/mem/', MemoryFileSystem('/mem/'))
503
494
 
504
495
 
496
+ try:
497
+ # pylint: disable=g-import-not-at-top
498
+ # pytype: disable=import-error
499
+ import fsspec
500
+ # pytype: enable=import-error
501
+ # pylint: enable=g-import-not-at-top
502
+ except ImportError:
503
+ fsspec = None
504
+
505
+
506
+ class FsspecFile(File):
507
+ """File object based on fsspec."""
508
+
509
+ def __init__(self, f):
510
+ self._f = f
511
+
512
+ def read(self, size: Optional[int] = None) -> Union[str, bytes]:
513
+ return self._f.read(size)
514
+
515
+ def readline(self) -> Union[str, bytes]:
516
+ return self._f.readline()
517
+
518
+ def write(self, content: Union[str, bytes]) -> None:
519
+ self._f.write(content)
520
+
521
+ def seek(self, offset: int, whence: Literal[0, 1, 2] = 0) -> int:
522
+ return self._f.seek(offset, whence)
523
+
524
+ def tell(self) -> int:
525
+ return self._f.tell()
526
+
527
+ def flush(self) -> None:
528
+ self._f.flush()
529
+
530
+ def close(self) -> None:
531
+ self._f.close()
532
+
533
+
534
+ class FsspecFileSystem(FileSystem):
535
+ """File system based on fsspec."""
536
+
537
+ def open(
538
+ self, path: Union[str, os.PathLike[str]], mode: str = 'r', **kwargs
539
+ ) -> File:
540
+ assert fsspec is not None, '`fsspec` is not installed.'
541
+ return FsspecFile(fsspec.open(path, mode, **kwargs).open())
542
+
543
+ def chmod(self, path: Union[str, os.PathLike[str]], mode: int) -> None:
544
+ assert fsspec is not None, '`fsspec` is not installed.'
545
+ fs, path = fsspec.core.url_to_fs(path)
546
+ if hasattr(fs, 'chmod'):
547
+ fs.chmod(path, mode)
548
+
549
+ def exists(self, path: Union[str, os.PathLike[str]]) -> bool:
550
+ assert fsspec is not None, '`fsspec` is not installed.'
551
+ fs, path = fsspec.core.url_to_fs(path)
552
+ return fs.exists(path)
553
+
554
+ def glob(self, pattern: Union[str, os.PathLike[str]]) -> list[str]:
555
+ assert fsspec is not None, '`fsspec` is not installed.'
556
+ fs, path = fsspec.core.url_to_fs(pattern)
557
+ protocol = fsspec.utils.get_protocol(pattern)
558
+ return [f'{protocol}:///{r.lstrip("/")}' for r in fs.glob(path)]
559
+
560
+ def listdir(self, path: Union[str, os.PathLike[str]]) -> list[str]:
561
+ assert fsspec is not None, '`fsspec` is not installed.'
562
+ fs, path = fsspec.core.url_to_fs(path)
563
+ return [os.path.basename(f) for f in fs.ls(path, detail=False)]
564
+
565
+ def isdir(self, path: Union[str, os.PathLike[str]]) -> bool:
566
+ assert fsspec is not None, '`fsspec` is not installed.'
567
+ fs, path = fsspec.core.url_to_fs(path)
568
+ return fs.isdir(path)
569
+
570
+ def mkdir(
571
+ self,
572
+ path: Union[str, os.PathLike[str]], mode: int = 0o777
573
+ ) -> None:
574
+ assert fsspec is not None, '`fsspec` is not installed.'
575
+ fs, path = fsspec.core.url_to_fs(path)
576
+ fs.mkdir(path)
577
+
578
+ def mkdirs(
579
+ self,
580
+ path: Union[str, os.PathLike[str]],
581
+ mode: int = 0o777,
582
+ exist_ok: bool = True,
583
+ ) -> None:
584
+ assert fsspec is not None, '`fsspec` is not installed.'
585
+ fs, path = fsspec.core.url_to_fs(path)
586
+ fs.makedirs(path, exist_ok=exist_ok)
587
+
588
+ def rm(self, path: Union[str, os.PathLike[str]]) -> None:
589
+ assert fsspec is not None, '`fsspec` is not installed.'
590
+ fs, path = fsspec.core.url_to_fs(path)
591
+ fs.rm(path)
592
+
593
+ def rename(
594
+ self,
595
+ oldpath: Union[str, os.PathLike[str]],
596
+ newpath: Union[str, os.PathLike[str]],
597
+ ) -> None:
598
+ assert fsspec is not None, '`fsspec` is not installed.'
599
+ fs, old_path = fsspec.core.url_to_fs(oldpath)
600
+ fs2, new_path = fsspec.core.url_to_fs(newpath)
601
+ if fs.__class__ != fs2.__class__:
602
+ raise ValueError(
603
+ f'Rename across different filesystems is not supported: '
604
+ f'{type(fs)} vs {type(fs2)}'
605
+ )
606
+ fs.rename(old_path, new_path)
607
+
608
+ def rmdir(self, path: Union[str, os.PathLike[str]]) -> None: # pytype: disable=signature-mismatch
609
+ assert fsspec is not None, '`fsspec` is not installed.'
610
+ fs, path = fsspec.core.url_to_fs(path)
611
+ fs.rmdir(path)
612
+
613
+ def rmdirs(self, path: Union[str, os.PathLike[str]]) -> None:
614
+ assert fsspec is not None, '`fsspec` is not installed.'
615
+ fs, path = fsspec.core.url_to_fs(path)
616
+ fs.rm(path, recursive=True)
617
+
618
+
619
+ class _FsspecUriCatcher(FileSystem):
620
+ """File system to catch URI paths and redirect to FsspecFileSystem."""
621
+
622
+ # Catch all paths that contains '://' but not registered by
623
+ # available_protocols.
624
+ _URI_PATTERN = re.compile(r'^[a-zA-Z][a-zA-Z0-9+-.]*://.*')
625
+
626
+ def __init__(self):
627
+ super().__init__()
628
+ self._std_fs = StdFileSystem()
629
+ self._fsspec_fs = FsspecFileSystem()
630
+
631
+ def get_fs(self, path: Union[str, os.PathLike[str]]) -> FileSystem:
632
+ if self._URI_PATTERN.match(resolve_path(path)):
633
+ return self._fsspec_fs
634
+ return self._std_fs
635
+
636
+ def open(
637
+ self, path: Union[str, os.PathLike[str]], mode: str = 'r', **kwargs
638
+ ) -> File:
639
+ return self.get_fs(path).open(path, mode, **kwargs)
640
+
641
+ def chmod(self, path: Union[str, os.PathLike[str]], mode: int) -> None:
642
+ self.get_fs(path).chmod(path, mode)
643
+
644
+ def exists(self, path: Union[str, os.PathLike[str]]) -> bool:
645
+ return self.get_fs(path).exists(path)
646
+
647
+ def glob(self, pattern: Union[str, os.PathLike[str]]) -> list[str]:
648
+ return self.get_fs(pattern).glob(pattern)
649
+
650
+ def listdir(self, path: Union[str, os.PathLike[str]]) -> list[str]:
651
+ return self.get_fs(path).listdir(path)
652
+
653
+ def isdir(self, path: Union[str, os.PathLike[str]]) -> bool:
654
+ return self.get_fs(path).isdir(path)
655
+
656
+ def mkdir(
657
+ self,
658
+ path: Union[str, os.PathLike[str]],
659
+ mode: int = 0o777
660
+ ) -> None:
661
+ self.get_fs(path).mkdir(path, mode)
662
+
663
+ def mkdirs(
664
+ self,
665
+ path: Union[str, os.PathLike[str]],
666
+ mode: int = 0o777,
667
+ exist_ok: bool = True,
668
+ ) -> None:
669
+ self.get_fs(path).mkdirs(path, mode, exist_ok)
670
+
671
+ def rm(self, path: Union[str, os.PathLike[str]]) -> None:
672
+ self.get_fs(path).rm(path)
673
+
674
+ def rename(
675
+ self,
676
+ oldpath: Union[str, os.PathLike[str]],
677
+ newpath: Union[str, os.PathLike[str]],
678
+ ) -> None:
679
+ self.get_fs(oldpath).rename(oldpath, newpath)
680
+
681
+ def rmdir(self, path: Union[str, os.PathLike[str]]) -> None: # pytype: disable=signature-mismatch
682
+ self.get_fs(path).rmdir(path)
683
+
684
+ def rmdirs(self, path: Union[str, os.PathLike[str]]) -> None:
685
+ self.get_fs(path).rmdirs(path)
686
+
687
+
688
+ if fsspec is not None:
689
+ fsspec_fs = FsspecFileSystem()
690
+ for p in fsspec.available_protocols():
691
+ add_file_system(p + '://', fsspec_fs)
692
+ add_file_system('', _FsspecUriCatcher())
693
+
694
+
505
695
  #
506
696
  # APIs for file IO.
507
697
  #
@@ -16,6 +16,8 @@ import os
16
16
  import pathlib
17
17
  import tempfile
18
18
  import unittest
19
+ from unittest import mock
20
+ import fsspec
19
21
  from pyglove.core.io import file_system
20
22
 
21
23
 
@@ -423,5 +425,120 @@ class FileIoApiTest(unittest.TestCase):
423
425
  ['/mem/g/a/b', '/mem/g/a/b/bar.txt', '/mem/g/a/foo2.txt'])
424
426
 
425
427
 
428
+ class FsspecFileSystemTest(unittest.TestCase):
429
+
430
+ def setUp(self):
431
+ super().setUp()
432
+ self.fs = fsspec.filesystem('memory')
433
+ self.fs.pipe('memory:///a/b/c', b'abc')
434
+ self.fs.pipe('memory:///a/b/d', b'abd')
435
+ self.fs.mkdir('memory:///a/e')
436
+
437
+ def tearDown(self):
438
+ super().tearDown()
439
+ fsspec.filesystem('memory').rm('/', recursive=True)
440
+
441
+ def test_read_file(self):
442
+ self.assertEqual(file_system.readfile('memory:///a/b/c', mode='rb'), b'abc')
443
+ with file_system.open('memory:///a/b/d', 'rb') as f:
444
+ self.assertEqual(f.read(), b'abd')
445
+
446
+ def test_fsspec_file_ops(self):
447
+ file_system.writefile('memory:///f', b'hello\nworld\n', mode='wb')
448
+ with file_system.open('memory:///f', 'rb') as f:
449
+ self.assertIsInstance(f, file_system.FsspecFile)
450
+ self.assertEqual(f.readline(), b'hello\n')
451
+ self.assertEqual(f.tell(), 6)
452
+ self.assertEqual(f.seek(8), 8)
453
+ self.assertEqual(f.read(), b'rld\n')
454
+ f.flush()
455
+
456
+ def test_write_file(self):
457
+ file_system.writefile('memory:///a/b/e', b'abe', mode='wb')
458
+ self.assertTrue(self.fs.exists('memory:///a/b/e'))
459
+ self.assertEqual(self.fs.cat('memory:///a/b/e'), b'abe')
460
+
461
+ def test_exists(self):
462
+ self.assertTrue(file_system.path_exists('memory:///a/b/c'))
463
+ self.assertFalse(file_system.path_exists('memory:///a/b/nonexist'))
464
+
465
+ def test_isdir(self):
466
+ self.assertTrue(file_system.isdir('memory:///a/b'))
467
+ self.assertTrue(file_system.isdir('memory:///a/e'))
468
+ self.assertFalse(file_system.isdir('memory:///a/b/c'))
469
+
470
+ def test_listdir(self):
471
+ self.assertCountEqual(file_system.listdir('memory:///a'), ['b', 'e'])
472
+ self.assertCountEqual(file_system.listdir('memory:///a/b'), ['c', 'd'])
473
+
474
+ def test_glob(self):
475
+ self.assertCountEqual(
476
+ file_system.glob('memory:///a/b/*'),
477
+ ['memory:///a/b/c', 'memory:///a/b/d']
478
+ )
479
+
480
+ def test_mkdir(self):
481
+ file_system.mkdir('memory:///a/f')
482
+ self.assertTrue(self.fs.isdir('memory:///a/f'))
483
+
484
+ def test_mkdirs(self):
485
+ file_system.mkdirs('memory:///g/h/i')
486
+ self.assertTrue(self.fs.isdir('memory:///g/h/i'))
487
+
488
+ def test_rm(self):
489
+ file_system.rm('memory:///a/b/c')
490
+ self.assertFalse(self.fs.exists('memory:///a/b/c'))
491
+
492
+ def test_rename(self):
493
+ file_system.rename('memory:///a/b/c', 'memory:///a/b/c_new')
494
+ self.assertFalse(self.fs.exists('memory:///a/b/c'))
495
+ self.assertTrue(self.fs.exists('memory:///a/b/c_new'))
496
+ with self.assertRaisesRegex(ValueError, 'Rename across different'):
497
+ file_system.rename('memory:///a/b/c_new', 'file:///a/b/c_d')
498
+
499
+ def test_chmod(self):
500
+ mock_fs = mock.Mock()
501
+ mock_fs.chmod = mock.Mock()
502
+ with mock.patch('fsspec.core.url_to_fs', return_value=(mock_fs, 'path')):
503
+ file_system.chmod('protocol:///path', 0o777)
504
+ mock_fs.chmod.assert_called_once_with('path', 0o777)
505
+
506
+ def test_rmdir(self):
507
+ file_system.rmdir('memory:///a/e')
508
+ self.assertFalse(self.fs.exists('memory:///a/e'))
509
+
510
+ def test_rmdirs(self):
511
+ file_system.mkdirs('memory:///x/y/z')
512
+ self.assertTrue(file_system.isdir('memory:///x/y/z'))
513
+ file_system.rmdirs('memory:///x')
514
+ self.assertFalse(file_system.path_exists('memory:///x'))
515
+
516
+ def test_fsspec_uri_catcher(self):
517
+ with mock.patch.object(
518
+ file_system.FsspecFileSystem, 'exists', return_value=True
519
+ ) as mock_fsspec_exists:
520
+ # We use a protocol that is not registered in
521
+ # fsspec.available_protocols() to make sure _FsspecUriCatcher is used.
522
+ self.assertTrue(file_system.path_exists('some-proto://foo'))
523
+ mock_fsspec_exists.assert_called_once_with('some-proto://foo')
524
+
525
+ # For full coverage of _FsspecUriCatcher.get_fs returning StdFileSystem,
526
+ # we need to test a non-URI path that doesn't match other prefixes.
527
+ # We mock StdFileSystem.exists to check if it's called.
528
+ with mock.patch.object(
529
+ file_system.StdFileSystem, 'exists', return_value=True
530
+ ) as mock_std_exists:
531
+ self.assertTrue(file_system.path_exists('/foo/bar/baz'))
532
+ mock_std_exists.assert_called_once_with('/foo/bar/baz')
533
+
534
+ with mock.patch.object(
535
+ file_system.FsspecFileSystem, 'rename', return_value=None
536
+ ) as mock_fsspec_rename:
537
+ file_system.rename('some-proto://foo', 'some-proto://bar')
538
+ mock_fsspec_rename.assert_called_once_with(
539
+ 'some-proto://foo', 'some-proto://bar'
540
+ )
541
+
542
+
426
543
  if __name__ == '__main__':
427
544
  unittest.main()
@@ -147,4 +147,11 @@ from pyglove.core.symbolic.list import mark_as_insertion
147
147
  from pyglove.core.symbolic.base import WritePermissionError
148
148
  from pyglove.core.symbolic.error_info import ErrorInfo
149
149
 
150
+ # Unknown symbols.
151
+ from pyglove.core.symbolic.unknown_symbols import UnknownSymbol
152
+ from pyglove.core.symbolic.unknown_symbols import UnknownType
153
+ from pyglove.core.symbolic.unknown_symbols import UnknownFunction
154
+ from pyglove.core.symbolic.unknown_symbols import UnknownMethod
155
+ from pyglove.core.symbolic.unknown_symbols import UnknownTypedObject
156
+
150
157
  # pylint: enable=g-bad-import-order
@@ -2042,7 +2042,7 @@ def from_json(
2042
2042
  context: Optional[utils.JSONConversionContext] = None,
2043
2043
  auto_symbolic: bool = True,
2044
2044
  auto_import: bool = True,
2045
- auto_dict: bool = False,
2045
+ convert_unknown: bool = False,
2046
2046
  allow_partial: bool = False,
2047
2047
  root_path: Optional[utils.KeyPath] = None,
2048
2048
  value_spec: Optional[pg_typing.ValueSpec] = None,
@@ -2073,8 +2073,13 @@ def from_json(
2073
2073
  identify its parent module and automatically import it. For example,
2074
2074
  if the type is 'foo.bar.A', PyGlove will try to import 'foo.bar' and
2075
2075
  find the class 'A' within the imported module.
2076
- auto_dict: If True, dict with '_type' that cannot be loaded will remain
2077
- as dict, with '_type' renamed to 'type_name'.
2076
+ convert_unknown: If True, when a '_type' is not registered and cannot
2077
+ be imported, PyGlove will create objects of:
2078
+ - `pg.symbolic.UnknownType` for unknown types;
2079
+ - `pg.symbolic.UnknownTypedObject` for objects of unknown types;
2080
+ - `pg.symbolic.UnknownFunction` for unknown functions;
2081
+ - `pg.symbolic.UnknownMethod` for unknown methods.
2082
+ If False, TypeError will be raised.
2078
2083
  allow_partial: Whether to allow elements of the list to be partial.
2079
2084
  root_path: KeyPath of loaded object in its object tree.
2080
2085
  value_spec: The value spec for the symbolic list or dict.
@@ -2095,7 +2100,12 @@ def from_json(
2095
2100
  if context is None:
2096
2101
  if (isinstance(json_value, dict) and (
2097
2102
  context_node := json_value.get(utils.JSONConvertible.CONTEXT_KEY))):
2098
- context = utils.JSONConversionContext.from_json(context_node, **kwargs)
2103
+ context = utils.JSONConversionContext.from_json(
2104
+ context_node,
2105
+ auto_import=auto_import,
2106
+ convert_unknown=convert_unknown,
2107
+ **kwargs
2108
+ )
2099
2109
  json_value = json_value[utils.JSONConvertible.ROOT_VALUE_KEY]
2100
2110
  else:
2101
2111
  context = utils.JSONConversionContext()
@@ -2103,7 +2113,7 @@ def from_json(
2103
2113
  typename_resolved = kwargs.pop('_typename_resolved', False)
2104
2114
  if not typename_resolved:
2105
2115
  json_value = utils.json_conversion.resolve_typenames(
2106
- json_value, auto_import, auto_dict
2116
+ json_value, auto_import, convert_unknown
2107
2117
  )
2108
2118
 
2109
2119
  def _load_child(k, v):
@@ -2177,7 +2187,7 @@ def from_json_str(
2177
2187
  *,
2178
2188
  context: Optional[utils.JSONConversionContext] = None,
2179
2189
  auto_import: bool = True,
2180
- auto_dict: bool = False,
2190
+ convert_unknown: bool = False,
2181
2191
  allow_partial: bool = False,
2182
2192
  root_path: Optional[utils.KeyPath] = None,
2183
2193
  value_spec: Optional[pg_typing.ValueSpec] = None,
@@ -2205,8 +2215,13 @@ def from_json_str(
2205
2215
  identify its parent module and automatically import it. For example,
2206
2216
  if the type is 'foo.bar.A', PyGlove will try to import 'foo.bar' and
2207
2217
  find the class 'A' within the imported module.
2208
- auto_dict: If True, dict with '_type' that cannot be loaded will remain
2209
- as dict, with '_type' renamed to 'type_name'.
2218
+ convert_unknown: If True, when a '_type' is not registered and cannot
2219
+ be imported, PyGlove will create objects of:
2220
+ - `pg.symbolic.UnknownType` for unknown types;
2221
+ - `pg.symbolic.UnknownTypedObject` for objects of unknown types;
2222
+ - `pg.symbolic.UnknownFunction` for unknown functions;
2223
+ - `pg.symbolic.UnknownMethod` for unknown methods.
2224
+ If False, TypeError will be raised.
2210
2225
  allow_partial: If True, allow a partial symbolic object to be created.
2211
2226
  Otherwise error will be raised on partial value.
2212
2227
  root_path: The symbolic path used for the deserialized root object.
@@ -2236,7 +2251,7 @@ def from_json_str(
2236
2251
  _decode_int_keys(json.loads(json_str)),
2237
2252
  context=context,
2238
2253
  auto_import=auto_import,
2239
- auto_dict=auto_dict,
2254
+ convert_unknown=convert_unknown,
2240
2255
  allow_partial=allow_partial,
2241
2256
  root_path=root_path,
2242
2257
  value_spec=value_spec,
@@ -339,7 +339,8 @@ class Object(base.Symbolic, metaclass=ObjectMeta):
339
339
 
340
340
  # Set `__serialization_key__` before JSONConvertible.__init_subclass__
341
341
  # is called.
342
- setattr(cls, '__serialization_key__', cls.__type_name__)
342
+ if '__serialization_key__' not in cls.__dict__:
343
+ setattr(cls, '__serialization_key__', cls.__type_name__)
343
344
 
344
345
  super().__init_subclass__()
345
346
 
@@ -38,6 +38,7 @@ from pyglove.core.symbolic.object import use_init_args as pg_use_init_args
38
38
  from pyglove.core.symbolic.origin import Origin
39
39
  from pyglove.core.symbolic.pure_symbolic import NonDeterministic
40
40
  from pyglove.core.symbolic.pure_symbolic import PureSymbolic
41
+ from pyglove.core.symbolic.unknown_symbols import UnknownTypedObject
41
42
  from pyglove.core.views.html import tree_view # pylint: disable=unused-import
42
43
 
43
44
 
@@ -3158,7 +3159,7 @@ class SerializationTest(unittest.TestCase):
3158
3159
  Q.partial(P.partial()).to_json_str(), allow_partial=True),
3159
3160
  Q.partial(P.partial()))
3160
3161
 
3161
- def test_serialization_with_auto_dict(self):
3162
+ def test_serialization_with_convert_unknown(self):
3162
3163
 
3163
3164
  class P(Object):
3164
3165
  auto_register = False
@@ -3181,15 +3182,17 @@ class SerializationTest(unittest.TestCase):
3181
3182
  }
3182
3183
  )
3183
3184
  self.assertEqual(
3184
- base.from_json_str(Q(P(1), y='foo').to_json_str(), auto_dict=True),
3185
- {
3186
- 'p': {
3187
- 'type_name': P.__type_name__,
3188
- 'x': 1
3189
- },
3190
- 'y': 'foo',
3191
- 'type_name': Q.__type_name__,
3192
- }
3185
+ base.from_json_str(
3186
+ Q(P(1), y='foo').to_json_str(), convert_unknown=True
3187
+ ),
3188
+ UnknownTypedObject(
3189
+ type_name=Q.__type_name__,
3190
+ p=UnknownTypedObject(
3191
+ type_name=P.__type_name__,
3192
+ x=1
3193
+ ),
3194
+ y='foo'
3195
+ )
3193
3196
  )
3194
3197
 
3195
3198
  def test_serialization_with_converter(self):
@@ -0,0 +1,147 @@
1
+ # Copyright 2021 The PyGlove Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Symbolic types for reprenting unknown types and objects."""
15
+
16
+ from typing import Annotated, Any, ClassVar, Literal
17
+ from pyglove.core import typing as pg_typing
18
+ from pyglove.core import utils
19
+ from pyglove.core.symbolic import list as pg_list # pylint: disable=unused-import
20
+ from pyglove.core.symbolic import object as pg_object
21
+
22
+
23
+ class UnknownSymbol(pg_object.Object, pg_typing.CustomTyping):
24
+ """Interface for symbolic representation of unknown symbols."""
25
+ auto_register = False
26
+
27
+ def custom_apply(self, *args, **kwargs) -> tuple[bool, Any]:
28
+ """Bypass PyGlove type check."""
29
+ return (False, self)
30
+
31
+
32
+ class UnknownType(UnknownSymbol):
33
+ """Symbolic object for representing unknown types."""
34
+
35
+ auto_register = True
36
+ __serialization_key__ = 'unknown_type'
37
+
38
+ # TODO(daiyip): Revisit the design on how `pg.typing.Object()` handles
39
+ # UnknownType. This hacky solution should be removed in the future.
40
+ __no_type_check__ = True
41
+
42
+ name: str
43
+ args: list[Any] = []
44
+
45
+ def sym_jsonify(self, **kwargs) -> utils.JSONValueType:
46
+ json_dict = {'_type': 'type', 'name': self.name}
47
+ if self.args:
48
+ json_dict['args'] = utils.to_json(self.args, **kwargs)
49
+ return json_dict
50
+
51
+ def format(
52
+ self,
53
+ compact: bool = False,
54
+ verbose: bool = True,
55
+ root_indent: int = 0,
56
+ **kwargs
57
+ ) -> str:
58
+ s = f'<unknown-type {self.name}>'
59
+ if self.args:
60
+ s += f'[{", ".join(repr(x) for x in self.args)}]'
61
+ return s
62
+
63
+ def __call__(self, **kwargs):
64
+ return UnknownTypedObject(
65
+ type_name=self.name, **kwargs
66
+ )
67
+
68
+
69
+ class UnknownCallable(UnknownSymbol):
70
+ """Symbolic object for representing unknown callables."""
71
+
72
+ auto_register = False
73
+ name: str
74
+ CALLABLE_TYPE: ClassVar[Literal['function', 'method']]
75
+
76
+ def sym_jsonify(self, **kwargs) -> utils.JSONValueType:
77
+ return {'_type': self.CALLABLE_TYPE, 'name': self.name}
78
+
79
+ def format(
80
+ self,
81
+ compact: bool = False,
82
+ verbose: bool = True,
83
+ root_indent: int = 0,
84
+ **kwargs
85
+ ) -> str:
86
+ return f'<unknown-{self.CALLABLE_TYPE} {self.name}>'
87
+
88
+
89
+ class UnknownFunction(UnknownCallable):
90
+ """Symbolic objject for representing unknown functions."""
91
+
92
+ auto_register = True
93
+ __serialization_key__ = 'unknown_function'
94
+ CALLABLE_TYPE = 'function'
95
+
96
+
97
+ class UnknownMethod(UnknownCallable):
98
+ """Symbolic object for representing unknown methods."""
99
+
100
+ auto_register = True
101
+ __serialization_key__ = 'unknown_method'
102
+ CALLABLE_TYPE = 'method'
103
+
104
+
105
+ class UnknownTypedObject(UnknownSymbol):
106
+ """Symbolic object for representing objects of unknown-type."""
107
+
108
+ auto_register = True
109
+ __serialization_key__ = 'unknown_object'
110
+
111
+ type_name: str
112
+ __kwargs__: Annotated[
113
+ Any,
114
+ (
115
+ 'Fields of the original object will be kept as symbolic attributes '
116
+ 'of this object so they can be accessed through `__getattr__`.'
117
+ )
118
+ ]
119
+
120
+ def sym_jsonify(self, **kwargs) -> utils.JSONValueType:
121
+ """Converts current object to a dict of plain Python objects."""
122
+ json_dict = self._sym_attributes.to_json(
123
+ exclude_keys=set(['type_name']), **kwargs
124
+ )
125
+ assert isinstance(json_dict, dict)
126
+ json_dict[utils.JSONConvertible.TYPE_NAME_KEY] = self.type_name
127
+ return json_dict
128
+
129
+ def format(
130
+ self,
131
+ compact: bool = False,
132
+ verbose: bool = True,
133
+ root_indent: int = 0,
134
+ **kwargs
135
+ ) -> str:
136
+ exclude_keys = kwargs.pop('exclude_keys', set())
137
+ exclude_keys.add('type_name')
138
+ kwargs['exclude_keys'] = exclude_keys
139
+ return self._sym_attributes.format(
140
+ compact,
141
+ verbose,
142
+ root_indent,
143
+ cls_name=f'<unknown-type {self.type_name}>',
144
+ key_as_attribute=True,
145
+ bracket_type=utils.BracketType.ROUND,
146
+ **kwargs,
147
+ )