params-proto 3.1.2__tar.gz → 3.2.0__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 (130) hide show
  1. {params_proto-3.1.2 → params_proto-3.2.0}/.idea/workspace.xml +15 -6
  2. {params_proto-3.1.2 → params_proto-3.2.0}/PKG-INFO +1 -1
  3. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/union_types.md +99 -25
  4. {params_proto-3.1.2 → params_proto-3.2.0}/docs/release_notes.md +35 -0
  5. {params_proto-3.1.2 → params_proto-3.2.0}/pyproject.toml +1 -1
  6. {params_proto-3.1.2 → params_proto-3.2.0}/skills/params-proto/SKILL.md +2 -2
  7. {params_proto-3.1.2 → params_proto-3.2.0}/skills/params-proto/references/cli-and-types.md +32 -2
  8. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/cli/cli_parse.py +28 -0
  9. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/hyper/sweep.py +71 -0
  10. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/proto.py +28 -16
  11. params_proto-3.2.0/tests/test_v3/conftest.py +56 -0
  12. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_cli_parsing.py +0 -55
  13. params_proto-3.2.0/tests/test_v3/test_nested_cli.py +818 -0
  14. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_piter.py +121 -0
  15. {params_proto-3.1.2 → params_proto-3.2.0}/.claude-plugin/marketplace.json +0 -0
  16. {params_proto-3.1.2 → params_proto-3.2.0}/.claude-plugin/plugin.json +0 -0
  17. {params_proto-3.1.2 → params_proto-3.2.0}/.editorconfig +0 -0
  18. {params_proto-3.1.2 → params_proto-3.2.0}/.gitignore +0 -0
  19. {params_proto-3.1.2 → params_proto-3.2.0}/.idea/.gitignore +0 -0
  20. {params_proto-3.1.2 → params_proto-3.2.0}/.idea/codeStyles/codeStyleConfig.xml +0 -0
  21. {params_proto-3.1.2 → params_proto-3.2.0}/.idea/inspectionProfiles/Project_Default.xml +0 -0
  22. {params_proto-3.1.2 → params_proto-3.2.0}/.idea/inspectionProfiles/profiles_settings.xml +0 -0
  23. {params_proto-3.1.2 → params_proto-3.2.0}/.idea/markdown.xml +0 -0
  24. {params_proto-3.1.2 → params_proto-3.2.0}/.idea/misc.xml +0 -0
  25. {params_proto-3.1.2 → params_proto-3.2.0}/.idea/modules.xml +0 -0
  26. {params_proto-3.1.2 → params_proto-3.2.0}/.idea/params-proto.iml +0 -0
  27. {params_proto-3.1.2 → params_proto-3.2.0}/.idea/ruff.xml +0 -0
  28. {params_proto-3.1.2 → params_proto-3.2.0}/.idea/vcs.xml +0 -0
  29. {params_proto-3.1.2 → params_proto-3.2.0}/.readthedocs.yaml +0 -0
  30. {params_proto-3.1.2 → params_proto-3.2.0}/.run/pytest for test_neo_proto_cli.run.xml +0 -0
  31. {params_proto-3.1.2 → params_proto-3.2.0}/.run/pytest in test_params_proto.run.xml +0 -0
  32. {params_proto-3.1.2 → params_proto-3.2.0}/ANSI_HELP_CONSIDERATIONS.md +0 -0
  33. {params_proto-3.1.2 → params_proto-3.2.0}/CLAUDE.md +0 -0
  34. {params_proto-3.1.2 → params_proto-3.2.0}/LICENSE.md +0 -0
  35. {params_proto-3.1.2 → params_proto-3.2.0}/Makefile +0 -0
  36. {params_proto-3.1.2 → params_proto-3.2.0}/README +0 -0
  37. {params_proto-3.1.2 → params_proto-3.2.0}/README.md +0 -0
  38. {params_proto-3.1.2 → params_proto-3.2.0}/docs/Makefile +0 -0
  39. {params_proto-3.1.2 → params_proto-3.2.0}/docs/_archive_v2/api/hyper.md +0 -0
  40. {params_proto-3.1.2 → params_proto-3.2.0}/docs/_archive_v2/api/proto.md +0 -0
  41. {params_proto-3.1.2 → params_proto-3.2.0}/docs/_archive_v2/api/utils.md +0 -0
  42. {params_proto-3.1.2 → params_proto-3.2.0}/docs/_archive_v2/examples/advanced_features.md +0 -0
  43. {params_proto-3.1.2 → params_proto-3.2.0}/docs/_archive_v2/examples/basic_usage.md +0 -0
  44. {params_proto-3.1.2 → params_proto-3.2.0}/docs/_archive_v2/examples/environment_variables.md +0 -0
  45. {params_proto-3.1.2 → params_proto-3.2.0}/docs/_archive_v2/examples/hyperparameter_sweeps.md +0 -0
  46. {params_proto-3.1.2 → params_proto-3.2.0}/docs/_archive_v2/examples/index.md +0 -0
  47. {params_proto-3.1.2 → params_proto-3.2.0}/docs/_archive_v2/examples/nested_configs.md +0 -0
  48. {params_proto-3.1.2 → params_proto-3.2.0}/docs/_archive_v2/quick_start.md +0 -0
  49. {params_proto-3.1.2 → params_proto-3.2.0}/docs/_static/ansi.css +0 -0
  50. {params_proto-3.1.2 → params_proto-3.2.0}/docs/_static/custom.css +0 -0
  51. {params_proto-3.1.2 → params_proto-3.2.0}/docs/api/index.md +0 -0
  52. {params_proto-3.1.2 → params_proto-3.2.0}/docs/api/proto.md +0 -0
  53. {params_proto-3.1.2 → params_proto-3.2.0}/docs/conf.py +0 -0
  54. {params_proto-3.1.2 → params_proto-3.2.0}/docs/examples/basic_usage.md +0 -0
  55. {params_proto-3.1.2 → params_proto-3.2.0}/docs/examples/cli_applications.md +0 -0
  56. {params_proto-3.1.2 → params_proto-3.2.0}/docs/examples/ml_training.md +0 -0
  57. {params_proto-3.1.2 → params_proto-3.2.0}/docs/examples/rl_agent.md +0 -0
  58. {params_proto-3.1.2 → params_proto-3.2.0}/docs/index.md +0 -0
  59. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/advanced_patterns.md +0 -0
  60. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/ansi_formatting.md +0 -0
  61. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/cli-fundamentals.md +0 -0
  62. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/cli-patterns.md +0 -0
  63. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/configuration-patterns.md +0 -0
  64. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/core-concepts.md +0 -0
  65. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/environment_variables.md +0 -0
  66. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/help-generation.md +0 -0
  67. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/hyperparameter_sweeps.md +0 -0
  68. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/naming-conventions.md +0 -0
  69. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/parameter-iteration.md +0 -0
  70. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/parameter-overrides.md +0 -0
  71. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/type-system.md +0 -0
  72. {params_proto-3.1.2 → params_proto-3.2.0}/docs/key_concepts/welcome.md +0 -0
  73. {params_proto-3.1.2 → params_proto-3.2.0}/docs/migration.md +0 -0
  74. {params_proto-3.1.2 → params_proto-3.2.0}/docs/quick_start.md +0 -0
  75. {params_proto-3.1.2 → params_proto-3.2.0}/docs/requirements.txt +0 -0
  76. {params_proto-3.1.2 → params_proto-3.2.0}/examples/union_subcommands.py +0 -0
  77. {params_proto-3.1.2 → params_proto-3.2.0}/figures/man-page.png +0 -0
  78. {params_proto-3.1.2 → params_proto-3.2.0}/figures/params-proto-autocompletion.gif +0 -0
  79. {params_proto-3.1.2 → params_proto-3.2.0}/figures/spec_files.png +0 -0
  80. {params_proto-3.1.2 → params_proto-3.2.0}/scratch/demo_params_proto.py +0 -0
  81. {params_proto-3.1.2 → params_proto-3.2.0}/scratch/demo_v3.py +0 -0
  82. {params_proto-3.1.2 → params_proto-3.2.0}/scratch/proto_DAT_scratch.py +0 -0
  83. {params_proto-3.1.2 → params_proto-3.2.0}/scratch/proto_dependency_tree_pattern.py +0 -0
  84. {params_proto-3.1.2 → params_proto-3.2.0}/skills/params-proto/references/environment-vars.md +0 -0
  85. {params_proto-3.1.2 → params_proto-3.2.0}/skills/params-proto/references/patterns.md +0 -0
  86. {params_proto-3.1.2 → params_proto-3.2.0}/skills/params-proto/references/sweeps.md +0 -0
  87. {params_proto-3.1.2 → params_proto-3.2.0}/skills/params-proto.skill +0 -0
  88. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/__init__.py +0 -0
  89. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/app.py +0 -0
  90. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/cli/__init__.py +0 -0
  91. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/cli/ansi_help.py +0 -0
  92. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/cli/help_gen.py +0 -0
  93. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/documentation.py +0 -0
  94. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/envvar.py +0 -0
  95. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/hyper/__init__.py +0 -0
  96. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/hyper/proxies.py +0 -0
  97. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/parse_env_template.py +0 -0
  98. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/type_utils.py +0 -0
  99. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/v1/__init__.py +0 -0
  100. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/v1/hyper.py +0 -0
  101. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/v1/params_proto.py +0 -0
  102. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/v2/__init__.py +0 -0
  103. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/v2/hyper.py +0 -0
  104. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/v2/partial.py +0 -0
  105. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/v2/proto.py +0 -0
  106. {params_proto-3.1.2 → params_proto-3.2.0}/src/params_proto/v2/utils.py +0 -0
  107. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v1/__init__.py +0 -0
  108. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v1/test_hyper.py +0 -0
  109. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v1/test_params_proto.py +0 -0
  110. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v2/test_Eval.py +0 -0
  111. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v2/test_neo_hyper.py +0 -0
  112. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v2/test_neo_proto.py +0 -0
  113. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v2/test_neo_proto_cli.py +0 -0
  114. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v2/test_neo_proto_partial.py +0 -0
  115. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v2/test_utils.py +0 -0
  116. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/samples/train.py +0 -0
  117. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_advanced_types.py +0 -0
  118. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_class_level_methods.py +0 -0
  119. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_help_strings.py +0 -0
  120. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_method_self_param.py +0 -0
  121. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_parse_env_template.py +0 -0
  122. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_positional_example.sh +0 -0
  123. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_proto_comments.py +0 -0
  124. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_proto_core.py +0 -0
  125. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_proto_envvar.py +0 -0
  126. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_proto_linebreaking.py +0 -0
  127. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_proto_partial.py +0 -0
  128. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_proto_required.py +0 -0
  129. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_strings.py +0 -0
  130. {params_proto-3.1.2 → params_proto-3.2.0}/tests/test_v3/test_sweep.py +0 -0
@@ -4,7 +4,13 @@
4
4
  <option name="autoReloadType" value="SELECTIVE" />
5
5
  </component>
6
6
  <component name="ChangeListManager">
7
- <list default="true" id="7a053ece-f497-4c97-ac58-a86c807155ac" name="Changes" comment="add design specs" />
7
+ <list default="true" id="7a053ece-f497-4c97-ac58-a86c807155ac" name="Changes" comment="add design specs">
8
+ <change beforePath="$PROJECT_DIR$/src/params_proto/cli/cli_parse.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/params_proto/cli/cli_parse.py" afterDir="false" />
9
+ <change beforePath="$PROJECT_DIR$/src/params_proto/hyper/sweep.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/params_proto/hyper/sweep.py" afterDir="false" />
10
+ <change beforePath="$PROJECT_DIR$/src/params_proto/proto.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/params_proto/proto.py" afterDir="false" />
11
+ <change beforePath="$PROJECT_DIR$/tests/test_v3/test_cli_parsing.py" beforeDir="false" afterPath="$PROJECT_DIR$/tests/test_v3/test_cli_parsing.py" afterDir="false" />
12
+ <change beforePath="$PROJECT_DIR$/tests/test_v3/test_piter.py" beforeDir="false" afterPath="$PROJECT_DIR$/tests/test_v3/test_piter.py" afterDir="false" />
13
+ </list>
8
14
  <option name="SHOW_DIALOG" value="false" />
9
15
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
10
16
  <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -33,6 +39,7 @@
33
39
  <component name="HighlightingSettingsPerFile">
34
40
  <setting file="file://$USER_HOME$/Library/Caches/JetBrains/PyCharm2025.2/python_stubs/-1979844046/_typing.py" root0="SKIP_INSPECTION" />
35
41
  <setting file="file://$PROJECT_DIR$/pyproject.toml" root0="SKIP_INSPECTION" />
42
+ <setting file="file://$PROJECT_DIR$/src/params_proto/envvar.py" root0="SKIP_INSPECTION" />
36
43
  </component>
37
44
  <component name="KubernetesApiPersistence">{}</component>
38
45
  <component name="KubernetesApiProvider">{
@@ -193,10 +200,10 @@
193
200
  <option name="ADD_CONTENT_ROOTS" value="true" />
194
201
  <option name="ADD_SOURCE_ROOTS" value="true" />
195
202
  <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
196
- <option name="RUN_TOOL" value="" />
203
+ <option name="RUN_TOOL" value="true" />
197
204
  <option name="_new_keywords" value="&quot;&quot;" />
198
205
  <option name="_new_parameters" value="&quot;&quot;" />
199
- <option name="_new_additionalArguments" value="&quot;&quot;" />
206
+ <option name="_new_additionalArguments" value="&quot;-n auto&quot;" />
200
207
  <option name="_new_target" value="&quot;$PROJECT_DIR$/tests&quot;" />
201
208
  <option name="_new_targetType" value="&quot;PATH&quot;" />
202
209
  <method v="2" />
@@ -223,8 +230,8 @@
223
230
  <component name="SharedIndexes">
224
231
  <attachedChunks>
225
232
  <set>
226
- <option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-PY-253.29346.308" />
227
- <option value="bundled-python-sdk-ca5e2b39c7df-6e1f45a539f7-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.29346.308" />
233
+ <option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-PY-253.30387.127" />
234
+ <option value="bundled-python-sdk-3944b0c99280-6d6dccd035ac-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.30387.127" />
228
235
  </set>
229
236
  </attachedChunks>
230
237
  </component>
@@ -255,6 +262,8 @@
255
262
  <workItem from="1768126847510" duration="6729000" />
256
263
  <workItem from="1768382679094" duration="3000" />
257
264
  <workItem from="1769135684123" duration="4247000" />
265
+ <workItem from="1769569263076" duration="1863000" />
266
+ <workItem from="1769933004821" duration="1704000" />
258
267
  </task>
259
268
  <task id="LOCAL-00001" summary="add design specs">
260
269
  <option name="closed" value="true" />
@@ -295,7 +304,7 @@
295
304
  <SUITE FILE_PATH="coverage/params_proto$scratch.coverage" NAME="scratch Coverage Results" MODIFIED="1762422882594" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
296
305
  <SUITE FILE_PATH="coverage/params_proto$demo_params_proto.coverage" NAME="demo_params_proto Coverage Results" MODIFIED="1762230413342" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/scratch" />
297
306
  <SUITE FILE_PATH="coverage/params_proto$pytest_for_tests_test_v3_test_proto_cli_test_proto_cli.coverage" NAME="pytest for tests.test_v3.test_proto_cli.test_proto_cli Coverage Results" MODIFIED="1762386112096" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
298
- <SUITE FILE_PATH="coverage/params_proto$pytest_in_tests.coverage" NAME="pytest in tests Coverage Results" MODIFIED="1767991865711" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" />
307
+ <SUITE FILE_PATH="coverage/params_proto$pytest_in_tests.coverage" NAME="pytest in tests Coverage Results" MODIFIED="1769934711414" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" />
299
308
  <SUITE FILE_PATH="coverage/params_proto$pytest_in_test_parse_env_template_py.coverage" NAME="pytest in test_parse_env_template.py Coverage Results" MODIFIED="1762242493577" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
300
309
  <SUITE FILE_PATH="coverage/params_proto$pytest_in_test_v2.coverage" NAME="pytest in test_v2 Coverage Results" MODIFIED="1762232660046" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests/test_v2" />
301
310
  <SUITE FILE_PATH="coverage/params_proto$pytest_in_test_neo_proto_py.coverage" NAME="pytest in test_neo_proto.py Coverage Results" MODIFIED="1762233469671" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: params-proto
3
- Version: 3.1.2
3
+ Version: 3.2.0
4
4
  Summary: Modern Hyper Parameter Management for Machine Learning
5
5
  Project-URL: Homepage, https://github.com/geyang/params-proto
6
6
  Project-URL: Documentation, https://params-proto.readthedocs.io
@@ -36,8 +36,13 @@ class SGD:
36
36
  def train(optimizer: Adam | SGD, epochs: int = 10):
37
37
  pass
38
38
 
39
- # CLI: python train.py --optimizer:Adam --optimizer.lr 0.01
40
- # CLI: python train.py adam --epochs 50
39
+ # CLI: python train.py adam --lr 0.01 --epochs 50
40
+ # CLI: python train.py sgd --momentum 0.95
41
+ ```
42
+
43
+ ```{note}
44
+ **Version 3.2.0+**: Subcommand attributes are **unprefixed by default**.
45
+ Use `--lr` instead of `--optimizer.lr`. The prefixed syntax still works for backwards compatibility.
41
46
  ```
42
47
 
43
48
  ```python
@@ -51,8 +56,8 @@ class Model:
51
56
  def main(model: Model):
52
57
  pass
53
58
 
54
- # CLI: python main.py --model:Model --model.hidden-size 512
55
- # CLI: python main.py model
59
+ # CLI: python main.py model --hidden-size 512
60
+ # CLI: python main.py model --name vit
56
61
  ```
57
62
 
58
63
  ```python
@@ -75,17 +80,17 @@ Use Union types when you need to **select which class to instantiate**. This cre
75
80
  First, let's see how users interact with this from the command line:
76
81
 
77
82
  ```bash
78
- # Positional selection (simplest)
79
- python render.py perspective-camera --output scene.png
83
+ # Positional selection with unprefixed attrs (v3.2.0+)
84
+ python render.py perspective-camera --fov 45 --aspect 1.77
80
85
 
81
- # Named selection (explicit)
82
- python render.py --camera:perspective-camera
86
+ # Alternative class
87
+ python render.py orthographic-camera --scale 2.0
83
88
 
84
- # With attribute overrides
85
- python render.py perspective-camera --camera.fov 45 --camera.aspect 1.77
89
+ # With shared parameters
90
+ python render.py perspective-camera --output scene.png
86
91
 
87
- # Alternative class
88
- python render.py --camera:OrthographicCamera --camera.scale 2.0
92
+ # Prefixed syntax still works (backwards compatible)
93
+ python render.py --camera:perspective-camera --camera.fov 45
89
94
  ```
90
95
 
91
96
  Now the implementation:
@@ -121,7 +126,7 @@ if __name__ == "__main__":
121
126
  **How it works:**
122
127
  - `camera` is a **required parameter** with Union type
123
128
  - params-proto instantiates the selected class
124
- - Each class has its own parameter namespace (`--camera.fov`, `--camera.aspect`, etc.)
129
+ - Attributes are unprefixed by default (`--fov`, `--aspect`)
125
130
  - Use `isinstance()` to dispatch on the selected type
126
131
 
127
132
  ## Optional[T]: Simple Optional Parameters
@@ -180,10 +185,10 @@ def train(checkpoint: Optional[str] = None, epochs: int = 10):
180
185
  --camera:PerspectiveCamera # Exact match
181
186
  --camera:perspective-camera # kebab-case
182
187
  --camera:perspectivecamera # lowercase
183
- perspective-camera # Positional
188
+ perspective-camera # Positional (recommended)
184
189
  ```
185
190
 
186
- **Attribute overrides** (kebab-case conversion):
191
+ **Attribute overrides** (v3.2.0+: unprefixed by default):
187
192
 
188
193
  ```python
189
194
  @dataclass
@@ -191,11 +196,37 @@ class Config:
191
196
  batch_size: int = 32 # Python: snake_case
192
197
  learning_rate: float = 0.001
193
198
 
194
- # CLI uses kebab-case
199
+ # CLI uses kebab-case (unprefixed by default)
200
+ --batch-size 64
201
+ --learning-rate 0.01
202
+
203
+ # Prefixed syntax still works
195
204
  --config.batch-size 64
196
205
  --config.learning-rate 0.01
197
206
  ```
198
207
 
208
+ ### Using `@proto.prefix` for Required Prefixes
209
+
210
+ If you want to **require** prefixed syntax for a class (e.g., for disambiguation), decorate it with `@proto.prefix`:
211
+
212
+ ```python
213
+ @proto.prefix # Now requires --config.batch-size syntax
214
+ @dataclass
215
+ class Config:
216
+ batch_size: int = 32
217
+
218
+ @dataclass
219
+ class Model:
220
+ batch_size: int = 16 # Same attr name, no conflict
221
+
222
+ @proto.cli
223
+ def train(config: Config, model: Model):
224
+ pass
225
+
226
+ # CLI: Config requires prefix, Model doesn't
227
+ # python train.py config model --config.batch-size 64 --batch-size 32
228
+ ```
229
+
199
230
  ## Examples
200
231
 
201
232
  ### Example 1: Optimizer Selection
@@ -203,9 +234,11 @@ class Config:
203
234
  Command-line usage:
204
235
 
205
236
  ```bash
206
- # Choose optimizer and override defaults
207
- python train.py adam --optimizer.lr 0.01
208
- python train.py sgd --optimizer.momentum 0.95
237
+ # Choose optimizer and override defaults (unprefixed)
238
+ python train.py adam --lr 0.01
239
+ python train.py sgd --momentum 0.95
240
+
241
+ # Prefixed syntax also works
209
242
  python train.py --optimizer:Adam --optimizer.beta1 0.95
210
243
  ```
211
244
 
@@ -237,10 +270,10 @@ def train(optimizer: Adam | SGD): # Union type selector
237
270
  Command-line usage:
238
271
 
239
272
  ```bash
240
- # Positional class selection
241
- python connect.py database-config --db.host prod.example.com --db.port 3306
273
+ # Positional class selection with unprefixed attrs
274
+ python connect.py database-config --host prod.example.com --port 3306
242
275
 
243
- # Or named selection
276
+ # Or named selection with prefixed attrs
244
277
  python connect.py --db:DatabaseConfig --db.user root
245
278
  ```
246
279
 
@@ -264,11 +297,11 @@ def connect(db: DatabaseConfig): # Single class (still uses Union mechanism)
264
297
  Command-line usage:
265
298
 
266
299
  ```bash
267
- # Union option + shared parameters
268
- python render.py perspective-camera --output scene.png --verbose
300
+ # Union option + shared parameters (unprefixed)
301
+ python render.py perspective-camera --fov 45 --output scene.png --verbose
269
302
 
270
303
  # Different union option with overrides
271
- python render.py --camera:OrthographicCamera --camera.scale 2.0 --verbose
304
+ python render.py orthographic-camera --scale 2.0 --verbose
272
305
 
273
306
  # Help shows all options
274
307
  python render.py --help
@@ -303,6 +336,47 @@ def render(
303
336
 
304
337
  **Key point:** Union parameters are **required**, while other parameters can be optional with defaults.
305
338
 
339
+ ### Example 4: @proto.prefix Classes Require Prefixed Syntax
340
+
341
+ When a Union class is decorated with `@proto.prefix`, its CLI attributes require prefixed syntax:
342
+
343
+ ```python
344
+ @proto.prefix # Requires prefixed syntax
345
+ @dataclass
346
+ class Train:
347
+ lr: float = 0.001
348
+ epochs: int = 100
349
+
350
+ @dataclass # Regular dataclass - unprefixed works
351
+ class Evaluate:
352
+ checkpoint: str = "model.pt"
353
+
354
+ @proto.cli
355
+ def main(mode: Train | Evaluate):
356
+ if isinstance(mode, Train):
357
+ print(f"Training: lr={mode.lr}, epochs={mode.epochs}")
358
+ else:
359
+ print(f"Evaluating: {mode.checkpoint}")
360
+
361
+ if __name__ == "__main__":
362
+ main()
363
+ ```
364
+
365
+ Command-line usage:
366
+
367
+ ```bash
368
+ # Train is @proto.prefix - requires prefixed syntax
369
+ python main.py train --mode.lr 0.01 --mode.epochs 50
370
+
371
+ # Evaluate is regular dataclass - unprefixed works
372
+ python main.py evaluate --checkpoint best.pt
373
+ ```
374
+
375
+ This is useful when:
376
+ - You have multiple Union params with overlapping attribute names
377
+ - You want explicit namespacing for clarity
378
+ - You're using the class as a singleton config elsewhere
379
+
306
380
  ## Related
307
381
 
308
382
  - [Core Concepts](core-concepts) - Three main decorators
@@ -2,6 +2,41 @@
2
2
 
3
3
  This page contains the release history and changelog for params-proto.
4
4
 
5
+ ## Version 3.2.0 (2025-02-01)
6
+
7
+ ### ✨ Features
8
+
9
+ - **Unprefixed CLI Subcommand Attributes**: Subcommand attributes no longer require prefix by default
10
+ - Old: `python train.py train-config --config.epochs 200`
11
+ - New: `python train.py train-config --epochs 200`
12
+ - Prefixed syntax still works for backwards compatibility
13
+
14
+ - **`@proto.prefix` Controls CLI Prefix Requirement**: Classes decorated with `@proto.prefix` require prefixed CLI syntax
15
+ - Regular dataclasses: `--epochs 200` (unprefixed)
16
+ - `@proto.prefix` classes: `--config.epochs 200` (prefixed required)
17
+
18
+ ### 🐛 Bug Fixes
19
+
20
+ - **`isinstance()` for `@proto.prefix` Instances**: Fixed `isinstance(instance, DecoratedClass)` returning `False`
21
+ - Instances of `@proto.prefix` decorated classes now correctly pass isinstance checks
22
+ - Enables proper type checking in Union subcommand handlers
23
+
24
+ ### 🧪 Testing
25
+
26
+ - Added comprehensive nested CLI subcommand tests (`test_nested_cli.py`)
27
+ - Added parallel test execution with `pytest-xdist`
28
+ - Run tests in parallel: `pytest -n auto`
29
+
30
+ ---
31
+
32
+ ## Version 3.1.2 (2025-01-27)
33
+
34
+ ### 📚 Documentation
35
+
36
+ - **Claude Code Plugin**: Fixed install command format in plugin documentation
37
+
38
+ ---
39
+
5
40
  ## Version 3.1.1 (2025-01-25)
6
41
 
7
42
  ### 🐛 Bug Fixes
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "params-proto"
3
- version = "3.1.2"
3
+ version = "3.2.0"
4
4
  description = "Modern Hyper Parameter Management for Machine Learning"
5
5
  authors = [
6
6
  { name = "Ge Yang" }
@@ -10,14 +10,14 @@ description: |
10
10
  (6) Work with Union types for subcommand-like CLI patterns
11
11
  ---
12
12
 
13
- # params-proto v3.1.1
13
+ # params-proto v3.2.0
14
14
 
15
15
  Declarative hyperparameter management for ML experiments with automatic CLI generation.
16
16
 
17
17
  ## Installation
18
18
 
19
19
  ```bash
20
- pip install params-proto==3.1.1
20
+ pip install params-proto==3.2.0
21
21
  ```
22
22
 
23
23
  ## Three Decorators
@@ -309,9 +309,39 @@ def main(mode: Train | Evaluate):
309
309
  ```
310
310
 
311
311
  ```bash
312
- python main.py train --lr 0.01
312
+ # v3.2.0+: Attributes are unprefixed by default
313
+ python main.py train --lr 0.01 --epochs 50
314
+ python main.py evaluate --checkpoint best.pt
315
+
316
+ # Prefixed syntax still works (backwards compatible)
317
+ python main.py --mode:train --mode.lr 0.01
318
+ ```
319
+
320
+ ### @proto.prefix Classes Require Prefixed Syntax
321
+
322
+ When a Union class is decorated with `@proto.prefix`, its attributes require prefixed CLI syntax:
323
+
324
+ ```python
325
+ @proto.prefix # Requires prefixed syntax
326
+ @dataclass
327
+ class Train:
328
+ lr: float = 0.001
329
+
330
+ @dataclass # Regular dataclass - unprefixed works
331
+ class Evaluate:
332
+ checkpoint: str = "model.pt"
333
+
334
+ @proto.cli
335
+ def main(mode: Train | Evaluate):
336
+ ...
337
+ ```
338
+
339
+ ```bash
340
+ # Train is @proto.prefix - requires prefix
341
+ python main.py train --mode.lr 0.01
342
+
343
+ # Evaluate is regular - unprefixed works
313
344
  python main.py evaluate --checkpoint best.pt
314
- # Also: --mode:train, --mode:Train, --mode:perspective-camera
315
345
  ```
316
346
 
317
347
  ### Enum Types
@@ -164,6 +164,23 @@ def parse_cli_args(wrapper) -> Dict[str, Any]:
164
164
  is_bool = annotation == bool
165
165
  prefix_params[kebab_key] = (singleton, param_name, annotation, is_bool)
166
166
 
167
+ # Build unprefixed union attribute map for classes NOT decorated with @proto.prefix
168
+ # Maps attr-name -> (union_param_name, attr_name_underscore)
169
+ # Classes in _SINGLETONS are @proto.prefix decorated and require prefixed attrs
170
+ unprefixed_attrs = {}
171
+ for kebab_name, (param_name, union_classes) in union_params.items():
172
+ for cls in union_classes:
173
+ # Skip if class is a @proto.prefix singleton (requires prefixed attrs)
174
+ is_prefix_class = cls in _SINGLETONS.values()
175
+ if is_prefix_class:
176
+ continue
177
+ if hasattr(cls, "__annotations__"):
178
+ for attr_name in cls.__annotations__:
179
+ kebab_attr = attr_name.replace("_", "-")
180
+ # Map to the union param (first one wins if multiple unions have same attr)
181
+ if kebab_attr not in unprefixed_attrs:
182
+ unprefixed_attrs[kebab_attr] = (param_name, attr_name)
183
+
167
184
  # Parse arguments
168
185
  result = {}
169
186
  prefix_values = {} # (singleton, param_name) -> value
@@ -336,6 +353,17 @@ def parse_cli_args(wrapper) -> Dict[str, Any]:
336
353
  i += 2
337
354
  continue
338
355
 
356
+ # Check unprefixed union attrs when cli_prefix=False
357
+ if key in unprefixed_attrs:
358
+ union_param_name, attr_name = unprefixed_attrs[key]
359
+ # Get the value
360
+ if i + 1 >= len(args):
361
+ raise SystemExit(f"error: argument --{key} requires a value")
362
+ value_str = args[i + 1]
363
+ union_attrs[(union_param_name, attr_name)] = value_str
364
+ i += 2
365
+ continue
366
+
339
367
  # Unknown argument
340
368
  raise SystemExit(f"error: unrecognized argument: {arg}")
341
369
 
@@ -143,6 +143,77 @@ class ParameterIterator:
143
143
  """Return number of configs (materializes list)."""
144
144
  return len(self.list)
145
145
 
146
+ def save(self, filename="sweep.jsonl", overwrite=True, verbose=True):
147
+ """
148
+ Save parameter configurations to JSONL file.
149
+
150
+ Args:
151
+ filename: Path to output file (str or PathLike)
152
+ overwrite: If True, overwrite existing file; if False, append
153
+ verbose: If True, print save confirmation
154
+
155
+ Example:
156
+ configs = piter @ {"lr": [0.001, 0.01]} * {"batch_size": [32, 64]}
157
+ configs.save("experiment.jsonl")
158
+ """
159
+ import json
160
+ import os
161
+ from urllib import parse
162
+
163
+ # Convert Path objects to string
164
+ filename_str = os.fspath(filename) if hasattr(os, "fspath") else str(filename)
165
+ configs = self.list
166
+
167
+ with open(filename_str, "w" if overwrite else "a+") as f:
168
+ for item in configs:
169
+ f.write(json.dumps(item) + "\n")
170
+
171
+ if verbose:
172
+ try:
173
+ from termcolor import colored as c
174
+
175
+ print(
176
+ c("saved", "blue"),
177
+ c(len(configs), "green"),
178
+ c("items to", "blue"),
179
+ filename_str,
180
+ ".",
181
+ "file://" + parse.quote(os.path.realpath(filename_str)),
182
+ )
183
+ except ImportError:
184
+ print(f"Saved {len(configs)} items to {filename_str}")
185
+
186
+ @staticmethod
187
+ def load(filename="sweep.jsonl"):
188
+ """
189
+ Load parameter configurations from JSONL file.
190
+
191
+ Args:
192
+ filename: Path to input file (str or PathLike)
193
+
194
+ Returns:
195
+ ParameterIterator with loaded configurations
196
+
197
+ Example:
198
+ configs = ParameterIterator.load("experiment.jsonl")
199
+ for config in configs:
200
+ run_experiment(**config)
201
+ """
202
+ import json
203
+ import os
204
+
205
+ # Convert Path objects to string
206
+ filename_str = os.fspath(filename) if hasattr(os, "fspath") else str(filename)
207
+
208
+ configs = []
209
+ with open(filename_str, "r") as f:
210
+ for line in f:
211
+ line = line.strip()
212
+ if line and not line.startswith("//"):
213
+ configs.append(json.loads(line))
214
+
215
+ return ParameterIterator(iter(configs))
216
+
146
217
 
147
218
  class PiterFactory:
148
219
  """
@@ -669,20 +669,32 @@ class ptype(type):
669
669
 
670
670
  # Get the original class
671
671
  original_cls = type.__getattribute__(cls, "__proto_original_class__")
672
+ annotations = getattr(cls, "__proto_annotations__", {})
672
673
 
673
- # Create instance
674
- instance = object.__new__(original_cls)
674
+ # Check if this is a dataclass (has generated __init__ that accepts kwargs)
675
+ is_dataclass = hasattr(original_cls, "__dataclass_fields__")
675
676
 
676
- # Set attributes
677
- annotations = getattr(cls, "__proto_annotations__", {})
678
- for name in annotations.keys():
679
- if name in final_kwargs:
680
- setattr(instance, name, final_kwargs[name])
681
- elif hasattr(cls, "__proto_defaults__") and name in cls.__proto_defaults__:
682
- setattr(instance, name, cls.__proto_defaults__[name])
683
- else:
684
- # Required field
685
- setattr(instance, name, None)
677
+ if is_dataclass:
678
+ # For dataclasses: use the constructor directly
679
+ instance = original_cls(**final_kwargs)
680
+ else:
681
+ # For regular classes: create instance and set attributes manually
682
+ instance = object.__new__(original_cls)
683
+ for name in annotations.keys():
684
+ if name in final_kwargs:
685
+ setattr(instance, name, final_kwargs[name])
686
+ elif hasattr(cls, "__proto_defaults__") and name in cls.__proto_defaults__:
687
+ setattr(instance, name, cls.__proto_defaults__[name])
688
+ else:
689
+ # Required field
690
+ setattr(instance, name, None)
691
+ # Call __post_init__ if defined (dataclasses call it in __init__)
692
+ if hasattr(instance, '__post_init__'):
693
+ instance.__post_init__()
694
+
695
+ # Update the instance's class to the decorated class
696
+ # This allows isinstance(instance, DecoratedClass) to work
697
+ object.__setattr__(instance, "__class__", cls)
686
698
 
687
699
  # Copy methods from original class and wrap to return self
688
700
  for name in dir(original_cls):
@@ -722,10 +734,6 @@ class ptype(type):
722
734
 
723
735
  setattr(instance, name, make_wrapper(method))
724
736
 
725
- # Call __post_init__ if defined (like dataclasses)
726
- if hasattr(instance, '__post_init__'):
727
- instance.__post_init__()
728
-
729
737
  return instance
730
738
 
731
739
 
@@ -1013,6 +1021,10 @@ def cli(obj: Any = None, *, prog: str = None):
1013
1021
  """
1014
1022
  Set up an object as a CLI entry point.
1015
1023
 
1024
+ By default, subcommand attributes don't require prefix (--epochs works).
1025
+ If the subcommand class is decorated with @proto.prefix, prefix is required
1026
+ (--config.epochs).
1027
+
1016
1028
  Args:
1017
1029
  obj: The class, function, or Union type to setup as CLI.
1018
1030
  If None, returns a decorator.
@@ -0,0 +1,56 @@
1
+ """Shared fixtures for v3 CLI tests."""
2
+
3
+ import subprocess
4
+ import sys
5
+ import tempfile
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+
11
+ @pytest.fixture
12
+ def run_cli():
13
+ """Fixture for running CLI scripts in shell."""
14
+
15
+ def _run(script_content: str, args: list[str] = None, expect_error: bool = False):
16
+ """
17
+ Run a Python script with CLI arguments.
18
+
19
+ Args:
20
+ script_content: Python code to execute
21
+ args: List of command-line arguments
22
+ expect_error: If True, expect non-zero exit code
23
+
24
+ Returns:
25
+ dict with keys: stdout, stderr, returncode
26
+ """
27
+ args = args or []
28
+
29
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
30
+ f.write(script_content)
31
+ script_path = f.name
32
+
33
+ try:
34
+ result = subprocess.run(
35
+ [sys.executable, script_path] + args,
36
+ capture_output=True,
37
+ text=True,
38
+ timeout=5,
39
+ )
40
+
41
+ if not expect_error and result.returncode != 0:
42
+ pytest.fail(
43
+ f"Script failed with exit code {result.returncode}\n"
44
+ f"stdout: {result.stdout}\n"
45
+ f"stderr: {result.stderr}"
46
+ )
47
+
48
+ return {
49
+ "stdout": result.stdout,
50
+ "stderr": result.stderr,
51
+ "returncode": result.returncode,
52
+ }
53
+ finally:
54
+ Path(script_path).unlink(missing_ok=True)
55
+
56
+ return _run