params-proto 3.2.0__tar.gz → 3.2.2__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 (132) hide show
  1. {params_proto-3.2.0 → params_proto-3.2.2}/.claude-plugin/plugin.json +1 -1
  2. {params_proto-3.2.0 → params_proto-3.2.2}/.idea/workspace.xml +5 -9
  3. {params_proto-3.2.0 → params_proto-3.2.2}/PKG-INFO +1 -2
  4. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/union_types.md +14 -18
  5. {params_proto-3.2.0 → params_proto-3.2.2}/docs/release_notes.md +31 -0
  6. {params_proto-3.2.0 → params_proto-3.2.2}/pyproject.toml +1 -2
  7. params_proto-3.2.2/scratch/test_super.py +146 -0
  8. params_proto-3.2.2/scratch/test_super_minimal.py +53 -0
  9. {params_proto-3.2.0 → params_proto-3.2.2}/skills/params-proto/SKILL.md +1 -1
  10. {params_proto-3.2.0 → params_proto-3.2.2}/skills/params-proto.skill +0 -0
  11. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/cli/cli_parse.py +70 -1
  12. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/proto.py +13 -5
  13. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/v1/params_proto.py +7 -1
  14. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/v2/proto.py +7 -1
  15. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_nested_cli.py +138 -0
  16. {params_proto-3.2.0 → params_proto-3.2.2}/.claude-plugin/marketplace.json +0 -0
  17. {params_proto-3.2.0 → params_proto-3.2.2}/.editorconfig +0 -0
  18. {params_proto-3.2.0 → params_proto-3.2.2}/.gitignore +0 -0
  19. {params_proto-3.2.0 → params_proto-3.2.2}/.idea/.gitignore +0 -0
  20. {params_proto-3.2.0 → params_proto-3.2.2}/.idea/codeStyles/codeStyleConfig.xml +0 -0
  21. {params_proto-3.2.0 → params_proto-3.2.2}/.idea/inspectionProfiles/Project_Default.xml +0 -0
  22. {params_proto-3.2.0 → params_proto-3.2.2}/.idea/inspectionProfiles/profiles_settings.xml +0 -0
  23. {params_proto-3.2.0 → params_proto-3.2.2}/.idea/markdown.xml +0 -0
  24. {params_proto-3.2.0 → params_proto-3.2.2}/.idea/misc.xml +0 -0
  25. {params_proto-3.2.0 → params_proto-3.2.2}/.idea/modules.xml +0 -0
  26. {params_proto-3.2.0 → params_proto-3.2.2}/.idea/params-proto.iml +0 -0
  27. {params_proto-3.2.0 → params_proto-3.2.2}/.idea/ruff.xml +0 -0
  28. {params_proto-3.2.0 → params_proto-3.2.2}/.idea/vcs.xml +0 -0
  29. {params_proto-3.2.0 → params_proto-3.2.2}/.readthedocs.yaml +0 -0
  30. {params_proto-3.2.0 → params_proto-3.2.2}/.run/pytest for test_neo_proto_cli.run.xml +0 -0
  31. {params_proto-3.2.0 → params_proto-3.2.2}/.run/pytest in test_params_proto.run.xml +0 -0
  32. {params_proto-3.2.0 → params_proto-3.2.2}/ANSI_HELP_CONSIDERATIONS.md +0 -0
  33. {params_proto-3.2.0 → params_proto-3.2.2}/CLAUDE.md +0 -0
  34. {params_proto-3.2.0 → params_proto-3.2.2}/LICENSE.md +0 -0
  35. {params_proto-3.2.0 → params_proto-3.2.2}/Makefile +0 -0
  36. {params_proto-3.2.0 → params_proto-3.2.2}/README +0 -0
  37. {params_proto-3.2.0 → params_proto-3.2.2}/README.md +0 -0
  38. {params_proto-3.2.0 → params_proto-3.2.2}/docs/Makefile +0 -0
  39. {params_proto-3.2.0 → params_proto-3.2.2}/docs/_archive_v2/api/hyper.md +0 -0
  40. {params_proto-3.2.0 → params_proto-3.2.2}/docs/_archive_v2/api/proto.md +0 -0
  41. {params_proto-3.2.0 → params_proto-3.2.2}/docs/_archive_v2/api/utils.md +0 -0
  42. {params_proto-3.2.0 → params_proto-3.2.2}/docs/_archive_v2/examples/advanced_features.md +0 -0
  43. {params_proto-3.2.0 → params_proto-3.2.2}/docs/_archive_v2/examples/basic_usage.md +0 -0
  44. {params_proto-3.2.0 → params_proto-3.2.2}/docs/_archive_v2/examples/environment_variables.md +0 -0
  45. {params_proto-3.2.0 → params_proto-3.2.2}/docs/_archive_v2/examples/hyperparameter_sweeps.md +0 -0
  46. {params_proto-3.2.0 → params_proto-3.2.2}/docs/_archive_v2/examples/index.md +0 -0
  47. {params_proto-3.2.0 → params_proto-3.2.2}/docs/_archive_v2/examples/nested_configs.md +0 -0
  48. {params_proto-3.2.0 → params_proto-3.2.2}/docs/_archive_v2/quick_start.md +0 -0
  49. {params_proto-3.2.0 → params_proto-3.2.2}/docs/_static/ansi.css +0 -0
  50. {params_proto-3.2.0 → params_proto-3.2.2}/docs/_static/custom.css +0 -0
  51. {params_proto-3.2.0 → params_proto-3.2.2}/docs/api/index.md +0 -0
  52. {params_proto-3.2.0 → params_proto-3.2.2}/docs/api/proto.md +0 -0
  53. {params_proto-3.2.0 → params_proto-3.2.2}/docs/conf.py +0 -0
  54. {params_proto-3.2.0 → params_proto-3.2.2}/docs/examples/basic_usage.md +0 -0
  55. {params_proto-3.2.0 → params_proto-3.2.2}/docs/examples/cli_applications.md +0 -0
  56. {params_proto-3.2.0 → params_proto-3.2.2}/docs/examples/ml_training.md +0 -0
  57. {params_proto-3.2.0 → params_proto-3.2.2}/docs/examples/rl_agent.md +0 -0
  58. {params_proto-3.2.0 → params_proto-3.2.2}/docs/index.md +0 -0
  59. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/advanced_patterns.md +0 -0
  60. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/ansi_formatting.md +0 -0
  61. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/cli-fundamentals.md +0 -0
  62. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/cli-patterns.md +0 -0
  63. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/configuration-patterns.md +0 -0
  64. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/core-concepts.md +0 -0
  65. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/environment_variables.md +0 -0
  66. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/help-generation.md +0 -0
  67. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/hyperparameter_sweeps.md +0 -0
  68. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/naming-conventions.md +0 -0
  69. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/parameter-iteration.md +0 -0
  70. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/parameter-overrides.md +0 -0
  71. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/type-system.md +0 -0
  72. {params_proto-3.2.0 → params_proto-3.2.2}/docs/key_concepts/welcome.md +0 -0
  73. {params_proto-3.2.0 → params_proto-3.2.2}/docs/migration.md +0 -0
  74. {params_proto-3.2.0 → params_proto-3.2.2}/docs/quick_start.md +0 -0
  75. {params_proto-3.2.0 → params_proto-3.2.2}/docs/requirements.txt +0 -0
  76. {params_proto-3.2.0 → params_proto-3.2.2}/examples/union_subcommands.py +0 -0
  77. {params_proto-3.2.0 → params_proto-3.2.2}/figures/man-page.png +0 -0
  78. {params_proto-3.2.0 → params_proto-3.2.2}/figures/params-proto-autocompletion.gif +0 -0
  79. {params_proto-3.2.0 → params_proto-3.2.2}/figures/spec_files.png +0 -0
  80. {params_proto-3.2.0 → params_proto-3.2.2}/scratch/demo_params_proto.py +0 -0
  81. {params_proto-3.2.0 → params_proto-3.2.2}/scratch/demo_v3.py +0 -0
  82. {params_proto-3.2.0 → params_proto-3.2.2}/scratch/proto_DAT_scratch.py +0 -0
  83. {params_proto-3.2.0 → params_proto-3.2.2}/scratch/proto_dependency_tree_pattern.py +0 -0
  84. {params_proto-3.2.0 → params_proto-3.2.2}/skills/params-proto/references/cli-and-types.md +0 -0
  85. {params_proto-3.2.0 → params_proto-3.2.2}/skills/params-proto/references/environment-vars.md +0 -0
  86. {params_proto-3.2.0 → params_proto-3.2.2}/skills/params-proto/references/patterns.md +0 -0
  87. {params_proto-3.2.0 → params_proto-3.2.2}/skills/params-proto/references/sweeps.md +0 -0
  88. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/__init__.py +0 -0
  89. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/app.py +0 -0
  90. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/cli/__init__.py +0 -0
  91. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/cli/ansi_help.py +0 -0
  92. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/cli/help_gen.py +0 -0
  93. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/documentation.py +0 -0
  94. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/envvar.py +0 -0
  95. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/hyper/__init__.py +0 -0
  96. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/hyper/proxies.py +0 -0
  97. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/hyper/sweep.py +0 -0
  98. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/parse_env_template.py +0 -0
  99. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/type_utils.py +0 -0
  100. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/v1/__init__.py +0 -0
  101. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/v1/hyper.py +0 -0
  102. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/v2/__init__.py +0 -0
  103. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/v2/hyper.py +0 -0
  104. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/v2/partial.py +0 -0
  105. {params_proto-3.2.0 → params_proto-3.2.2}/src/params_proto/v2/utils.py +0 -0
  106. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v1/__init__.py +0 -0
  107. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v1/test_hyper.py +0 -0
  108. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v1/test_params_proto.py +0 -0
  109. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v2/test_Eval.py +0 -0
  110. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v2/test_neo_hyper.py +0 -0
  111. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v2/test_neo_proto.py +0 -0
  112. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v2/test_neo_proto_cli.py +0 -0
  113. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v2/test_neo_proto_partial.py +0 -0
  114. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v2/test_utils.py +0 -0
  115. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/conftest.py +0 -0
  116. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/samples/train.py +0 -0
  117. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_advanced_types.py +0 -0
  118. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_class_level_methods.py +0 -0
  119. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_cli_parsing.py +0 -0
  120. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_help_strings.py +0 -0
  121. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_method_self_param.py +0 -0
  122. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_parse_env_template.py +0 -0
  123. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_piter.py +0 -0
  124. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_positional_example.sh +0 -0
  125. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_proto_comments.py +0 -0
  126. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_proto_core.py +0 -0
  127. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_proto_envvar.py +0 -0
  128. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_proto_linebreaking.py +0 -0
  129. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_proto_partial.py +0 -0
  130. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_proto_required.py +0 -0
  131. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_strings.py +0 -0
  132. {params_proto-3.2.0 → params_proto-3.2.2}/tests/test_v3/test_sweep.py +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "params-proto",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "Declarative hyperparameter management skills for ML/AI experiments",
5
5
  "author": {
6
6
  "name": "Ge Yang"
@@ -4,13 +4,7 @@
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">
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>
7
+ <list default="true" id="7a053ece-f497-4c97-ac58-a86c807155ac" name="Changes" comment="add design specs" />
14
8
  <option name="SHOW_DIALOG" value="false" />
15
9
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
16
10
  <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -263,7 +257,9 @@
263
257
  <workItem from="1768382679094" duration="3000" />
264
258
  <workItem from="1769135684123" duration="4247000" />
265
259
  <workItem from="1769569263076" duration="1863000" />
266
- <workItem from="1769933004821" duration="1704000" />
260
+ <workItem from="1769933004821" duration="4284000" />
261
+ <workItem from="1770101044881" duration="1209000" />
262
+ <workItem from="1770110071925" duration="622000" />
267
263
  </task>
268
264
  <task id="LOCAL-00001" summary="add design specs">
269
265
  <option name="closed" value="true" />
@@ -304,7 +300,7 @@
304
300
  <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$" />
305
301
  <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" />
306
302
  <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$" />
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" />
303
+ <SUITE FILE_PATH="coverage/params_proto$pytest_in_tests.coverage" NAME="pytest in tests Coverage Results" MODIFIED="1769937166592" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/tests" />
308
304
  <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$" />
309
305
  <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" />
310
306
  <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.2.0
3
+ Version: 3.2.2
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
@@ -23,7 +23,6 @@ Requires-Dist: argcomplete
23
23
  Requires-Dist: argparse
24
24
  Requires-Dist: expandvars
25
25
  Requires-Dist: termcolor
26
- Requires-Dist: waterbear>=2.6.8
27
26
  Provides-Extra: dev
28
27
  Requires-Dist: pandas; extra == 'dev'
29
28
  Requires-Dist: pytest; extra == 'dev'
@@ -61,10 +61,11 @@ def main(model: Model):
61
61
  ```
62
62
 
63
63
  ```python
64
- # Pattern 3: Optional simple parameters (workaround)
65
- # Note: Optional[str] is not fully supported; use str with default instead
64
+ # Pattern 3: Optional simple parameters
65
+ from typing import Optional
66
+
66
67
  @proto.cli
67
- def process(checkpoint: str = None, batch_size: int = 32):
68
+ def process(checkpoint: Optional[str] = None, batch_size: int = 32):
68
69
  pass
69
70
 
70
71
  # CLI: python process.py --checkpoint model.pt
@@ -134,17 +135,18 @@ if __name__ == "__main__":
134
135
  `Optional[T]` is for parameters that **may or may not be provided**:
135
136
 
136
137
  ```python
138
+ from typing import Optional
139
+
137
140
  @proto.cli
138
141
  def train(
139
- checkpoint: str = None, # Works (workaround)
140
- # checkpoint: Optional[str] = None, # ⚠️ Doesn't fully work yet
142
+ checkpoint: Optional[str] = None, # Optional with None default
141
143
  epochs: int = 10,
142
144
  ):
143
145
  """Train model."""
144
146
  pass
145
147
  ```
146
148
 
147
- **Expected CLI usage:**
149
+ **CLI usage:**
148
150
 
149
151
  ```bash
150
152
  python train.py --checkpoint model.pt # Provide value
@@ -152,21 +154,15 @@ python train.py # Omit for None default
152
154
  python train.py --checkpoint model.pt --epochs 50
153
155
  ```
154
156
 
155
- ```{note}
156
- **Current limitation:** `Optional[str]`, `Optional[int]`, etc. are not fully supported.
157
- Use regular parameters with defaults as a workaround:
157
+ Both `Optional[str]` and `str = None` work equivalently:
158
158
 
159
159
  ```python
160
- # Works
160
+ # Both work
161
161
  @proto.cli
162
- def train(checkpoint: str = None, epochs: int = 10):
163
- pass
162
+ def train(checkpoint: Optional[str] = None): ...
164
163
 
165
- # ⚠️ Doesn't work yet
166
164
  @proto.cli
167
- def train(checkpoint: Optional[str] = None, epochs: int = 10):
168
- pass
169
- ```
165
+ def train(checkpoint: str = None): ...
170
166
  ```
171
167
 
172
168
  ## Key Differences
@@ -174,8 +170,8 @@ def train(checkpoint: Optional[str] = None, epochs: int = 10):
174
170
  | Type | Purpose | CLI Syntax | When to Use |
175
171
  |------|---------|-----------|-------------|
176
172
  | `Union[ClassA, ClassB]` | Choose which class instance | `--param:ClassName` or positional | Multiple configurations (optimizers, models, etc.) |
177
- | `Optional[str]` | Value may or may not be provided | `--param value` | Optional simple parameters (**currently use workaround**) |
178
- | `str` with default | Same as Optional | `--param value` | Simple optional parameters (**recommended workaround**) |
173
+ | `Optional[str]` | Value may or may not be provided | `--param value` | Optional simple parameters |
174
+ | `str = None` | Same as Optional | `--param value` | Alternative syntax for optional parameters |
179
175
 
180
176
  ## Syntax Variations
181
177
 
@@ -2,6 +2,37 @@
2
2
 
3
3
  This page contains the release history and changelog for params-proto.
4
4
 
5
+ ## Version 3.2.2 (2025-02-03)
6
+
7
+ ### 🐛 Bug Fixes
8
+
9
+ - **`super()` in `@proto` Methods**: Fixed `super()` calls failing with `TypeError: super(type, obj): obj must be an instance or subtype of type`
10
+ - Methods using `super()` in `@proto` or `@proto.prefix` decorated classes now work correctly
11
+ - Root cause: decorated class was recreated as sibling instead of subclass of original
12
+ - Fix: decorated class is now a proper subclass, preserving the MRO chain
13
+
14
+ - **`@proto` Inheriting from `@proto`**: Fixed `TypeError: duplicate base class ptype` when a `@proto` class inherits from another `@proto` class
15
+ - Nested `@proto` inheritance now works correctly
16
+ - Metaclass merging skips duplicate `ptype` bases
17
+
18
+ ---
19
+
20
+ ## Version 3.2.1 (2025-02-01)
21
+
22
+ ### 🐛 Bug Fixes
23
+
24
+ - **Positional Arguments in Subcommands**: Fixed positional arguments being silently ignored after subcommand name
25
+ - Before: `myapp add my-env/v1.2.3` → positional arg silently dropped
26
+ - After: `myapp add my-env/v1.2.3` → positional arg captured by subcommand's required field
27
+ - Enables CLI patterns like `pip install requests`, `cargo add serde`
28
+ - Raises clear error for extra unrecognized positional arguments
29
+
30
+ ### 📚 Documentation
31
+
32
+ - Fixed outdated notes claiming `Optional[str]` was not supported (it works)
33
+
34
+ ---
35
+
5
36
  ## Version 3.2.0 (2025-02-01)
6
37
 
7
38
  ### ✨ Features
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "params-proto"
3
- version = "3.2.0"
3
+ version = "3.2.2"
4
4
  description = "Modern Hyper Parameter Management for Machine Learning"
5
5
  authors = [
6
6
  { name = "Ge Yang" }
@@ -32,7 +32,6 @@ classifiers = [
32
32
  ]
33
33
 
34
34
  dependencies = [
35
- "waterbear>=2.6.8",
36
35
  "argparse",
37
36
  "argcomplete",
38
37
  "expandvars",
@@ -0,0 +1,146 @@
1
+ """Test super() calls inside @proto and @proto.prefix decorated classes."""
2
+
3
+ from params_proto import proto
4
+
5
+
6
+ # Test 1: Basic inheritance with @proto
7
+ class BaseClass:
8
+ base_value: int = 10
9
+
10
+ def base_method(self):
11
+ return f"base: {self.base_value}"
12
+
13
+
14
+ @proto
15
+ class ChildProto(BaseClass):
16
+ child_value: int = 20
17
+
18
+ def child_method(self):
19
+ base_result = super().base_method()
20
+ return f"{base_result}, child: {self.child_value}"
21
+
22
+
23
+ # Test 2: Inheritance with @proto.prefix
24
+ class BaseConfig:
25
+ lr: float = 0.01
26
+
27
+ def describe(self):
28
+ return f"lr={self.lr}"
29
+
30
+
31
+ @proto.prefix
32
+ class ChildConfig(BaseConfig):
33
+ batch_size: int = 32
34
+
35
+ def describe(self):
36
+ base = super().describe()
37
+ return f"{base}, batch_size={self.batch_size}"
38
+
39
+
40
+ # Test 3: Inheritance between two @proto decorated classes
41
+ @proto
42
+ class ParentProto:
43
+ parent_attr: str = "parent"
44
+
45
+ def get_info(self):
46
+ return f"Parent: {self.parent_attr}"
47
+
48
+
49
+ @proto
50
+ class GrandchildProto(ParentProto):
51
+ grandchild_attr: str = "grandchild"
52
+
53
+ def get_info(self):
54
+ parent_info = super().get_info()
55
+ return f"{parent_info}, Grandchild: {self.grandchild_attr}"
56
+
57
+
58
+ # Test 4: __post_init__ with super() (undecorated base)
59
+ class BaseWithInit:
60
+ value: int = 1
61
+ processed: bool = False
62
+
63
+ def __post_init__(self):
64
+ self.processed = True
65
+
66
+
67
+ @proto
68
+ class ChildWithInit(BaseWithInit):
69
+ multiplier: int = 2
70
+ child_processed: bool = False
71
+
72
+ def __post_init__(self):
73
+ super().__post_init__()
74
+ self.child_processed = True
75
+ self.value *= self.multiplier
76
+
77
+
78
+ def main():
79
+ print("=" * 60)
80
+ print("Testing super() in @proto and @proto.prefix classes")
81
+ print("=" * 60)
82
+
83
+ # Test 1
84
+ print("\n--- Test 1: Basic inheritance with @proto ---")
85
+ try:
86
+ child = ChildProto()
87
+ print(f"child class: {child.__class__}")
88
+ print(f"child MRO: {child.__class__.__mro__}")
89
+ print(f"ChildProto: {ChildProto}")
90
+ print(f"ChildProto MRO: {ChildProto.__mro__}")
91
+ print(f"isinstance(child, ChildProto): {isinstance(child, ChildProto)}")
92
+ print(f"isinstance(child, BaseClass): {isinstance(child, BaseClass)}")
93
+ result = child.child_method()
94
+ print(f"Result: {result}")
95
+ assert "base: 10" in result, f"Expected 'base: 10' in result"
96
+ assert "child: 20" in result, f"Expected 'child: 20' in result"
97
+ print("✓ PASSED")
98
+ except Exception as e:
99
+ import traceback
100
+ print(f"✗ FAILED: {e}")
101
+ traceback.print_exc()
102
+
103
+ # Test 2
104
+ print("\n--- Test 2: Inheritance with @proto.prefix ---")
105
+ try:
106
+ config = ChildConfig()
107
+ result = config.describe()
108
+ print(f"Result: {result}")
109
+ assert "lr=" in result, f"Expected 'lr=' in result"
110
+ assert "batch_size=" in result, f"Expected 'batch_size=' in result"
111
+ print("✓ PASSED")
112
+ except Exception as e:
113
+ print(f"✗ FAILED: {e}")
114
+
115
+ # Test 3
116
+ print("\n--- Test 3: Inheritance between @proto classes ---")
117
+ try:
118
+ grandchild = GrandchildProto()
119
+ result = grandchild.get_info()
120
+ print(f"Result: {result}")
121
+ assert "Parent:" in result, f"Expected 'Parent:' in result"
122
+ assert "Grandchild:" in result, f"Expected 'Grandchild:' in result"
123
+ print("✓ PASSED")
124
+ except Exception as e:
125
+ import traceback
126
+ print(f"✗ FAILED: {e}")
127
+ traceback.print_exc()
128
+
129
+ # Test 4
130
+ print("\n--- Test 4: __post_init__ with super() ---")
131
+ try:
132
+ obj = ChildWithInit()
133
+ print(f"value={obj.value}, processed={obj.processed}, child_processed={obj.child_processed}")
134
+ assert obj.processed, "Expected processed=True from parent __post_init__"
135
+ assert obj.child_processed, "Expected child_processed=True from child __post_init__"
136
+ assert obj.value == 2, f"Expected value=2 (1*2), got {obj.value}"
137
+ print("✓ PASSED")
138
+ except Exception as e:
139
+ print(f"✗ FAILED: {e}")
140
+
141
+ print("\n" + "=" * 60)
142
+ print("All tests completed!")
143
+
144
+
145
+ if __name__ == "__main__":
146
+ main()
@@ -0,0 +1,53 @@
1
+ """Minimal test for super() issue."""
2
+
3
+ from params_proto import proto
4
+
5
+
6
+ class Base:
7
+ def method(self):
8
+ return "base"
9
+
10
+
11
+ @proto
12
+ class Child(Base):
13
+ attr: int = 1
14
+
15
+ def method(self):
16
+ # This should call Base.method()
17
+ return f"child + {super().method()}"
18
+
19
+
20
+ # Test without instantiation
21
+ print("Child class:", Child)
22
+ print("Child id:", id(Child))
23
+ print("Child.__mro__:", Child.__mro__)
24
+ print("Child.__proto_original_class__:", Child.__proto_original_class__)
25
+ print("Original class id:", id(Child.__proto_original_class__))
26
+ print("Same class?", Child is Child.__proto_original_class__)
27
+
28
+ # Test instantiation
29
+ print("\nCreating instance...")
30
+ child = Child()
31
+ print("Instance class:", child.__class__)
32
+ print("Instance type:", type(child))
33
+
34
+ # Check what method is on the instance
35
+ print("\nMethod on instance:", child.method)
36
+
37
+ # Try calling
38
+ print("\nCalling method...")
39
+ try:
40
+ result = child.method()
41
+ print(f"Result: {result}")
42
+ except Exception as e:
43
+ print(f"Error: {e}")
44
+
45
+ # Debug: manually call the original method
46
+ print("\nDebug: Trying to call original method directly...")
47
+ original_cls = Child.__proto_original_class__
48
+ print(f"Original class: {original_cls}")
49
+ print(f"Original method: {original_cls.method}")
50
+
51
+ # Create instance via original class
52
+ from params_proto.proto import _SINGLETONS
53
+ print(f"\nSINGLETONS: {_SINGLETONS}")
@@ -10,7 +10,7 @@ description: |
10
10
  (6) Work with Union types for subcommand-like CLI patterns
11
11
  ---
12
12
 
13
- # params-proto v3.2.0
13
+ # params-proto v3.2.1
14
14
 
15
15
  Declarative hyperparameter management for ML experiments with automatic CLI generation.
16
16
 
@@ -57,6 +57,32 @@ def _normalize_class_name(class_name: str) -> str:
57
57
  return class_name.replace("-", "").replace("_", "").lower()
58
58
 
59
59
 
60
+ def _get_required_fields(cls) -> List[str]:
61
+ """Get list of required field names (fields without defaults) in order."""
62
+ import dataclasses
63
+
64
+ required = []
65
+
66
+ # Check if it's a dataclass
67
+ if dataclasses.is_dataclass(cls):
68
+ for field in dataclasses.fields(cls):
69
+ has_default = (
70
+ field.default is not dataclasses.MISSING
71
+ or field.default_factory is not dataclasses.MISSING
72
+ )
73
+ if not has_default:
74
+ required.append(field.name)
75
+ else:
76
+ # For regular classes, check annotations and class-level defaults
77
+ annotations = getattr(cls, "__annotations__", {})
78
+ for name in annotations:
79
+ if not hasattr(cls, name):
80
+ # No class-level default
81
+ required.append(name)
82
+
83
+ return required
84
+
85
+
60
86
  def _match_class_by_name(name: str, classes: list) -> Union[type, None]:
61
87
  """Match a string to one of the Union classes.
62
88
 
@@ -187,6 +213,8 @@ def parse_cli_args(wrapper) -> Dict[str, Any]:
187
213
  positional_values = []
188
214
  union_selections = {} # param_name -> selected_class
189
215
  union_attrs = {} # (param_name, attr_name) -> value
216
+ union_positional = {} # param_name -> [positional_args] for subcommand fields
217
+ current_union_param = None # Track which union we're collecting positionals for
190
218
 
191
219
  args = sys.argv[1:]
192
220
  i = 0
@@ -377,12 +405,18 @@ def parse_cli_args(wrapper) -> Dict[str, Any]:
377
405
  selected_class = _match_class_by_name(arg, union_classes)
378
406
  if selected_class:
379
407
  union_selections[param_name] = selected_class
408
+ current_union_param = param_name # Track for following positionals
409
+ union_positional[param_name] = []
380
410
  matched_union = True
381
411
  i += 1
382
412
  break
383
413
 
384
414
  if not matched_union:
385
- positional_values.append(arg)
415
+ # If we have a current union, add positional to its list
416
+ if current_union_param is not None:
417
+ union_positional[current_union_param].append(arg)
418
+ else:
419
+ positional_values.append(arg)
386
420
  i += 1
387
421
 
388
422
  # Assign positional arguments to required parameters
@@ -433,6 +467,33 @@ def parse_cli_args(wrapper) -> Dict[str, Any]:
433
467
  # No annotations, treat as string
434
468
  attrs[attr_name] = value_str
435
469
 
470
+ # Assign positional args to required fields of the selected class
471
+ if param_name in union_positional and union_positional[param_name]:
472
+ positionals = union_positional[param_name]
473
+ required_fields = _get_required_fields(selected_class)
474
+
475
+ for field_idx, field_name in enumerate(required_fields):
476
+ if field_name in attrs:
477
+ # Already set by named arg, skip
478
+ continue
479
+ if field_idx < len(positionals):
480
+ # Get type annotation for conversion
481
+ if hasattr(selected_class, "__annotations__"):
482
+ field_type = selected_class.__annotations__.get(field_name, str)
483
+ try:
484
+ attrs[field_name] = _convert_type(positionals[field_idx], field_type)
485
+ except (ValueError, TypeError):
486
+ raise SystemExit(
487
+ f"error: invalid value for {field_name}: {positionals[field_idx]}"
488
+ )
489
+ else:
490
+ attrs[field_name] = positionals[field_idx]
491
+
492
+ # Check for extra positional args
493
+ if len(positionals) > len(required_fields):
494
+ extra = positionals[len(required_fields):]
495
+ raise SystemExit(f"error: unrecognized arguments: {' '.join(extra)}")
496
+
436
497
  # If selected_class is a proto.prefix singleton, merge its overrides
437
498
  from params_proto.proto import _SINGLETONS, ptype
438
499
 
@@ -447,6 +508,14 @@ def parse_cli_args(wrapper) -> Dict[str, Any]:
447
508
  attrs[key] = value
448
509
  break
449
510
 
511
+ # Check for missing required fields
512
+ required_fields = _get_required_fields(selected_class)
513
+ for field_name in required_fields:
514
+ if field_name not in attrs:
515
+ raise SystemExit(
516
+ f"error: {selected_class.__name__} requires argument: {field_name}"
517
+ )
518
+
450
519
  # Instantiate the class with collected attributes
451
520
  try:
452
521
  instance = selected_class(**attrs)
@@ -844,10 +844,15 @@ def proto(
844
844
  # Handle existing metaclass
845
845
  existing_meta = type(obj)
846
846
  if existing_meta is not type:
847
- # Merge with existing metaclass
848
- class MergedMeta(existing_meta, ptype):
849
- pass
850
- metaclass = MergedMeta
847
+ # Check if existing metaclass is already ptype or a subclass of ptype
848
+ if issubclass(existing_meta, ptype):
849
+ # Already using ptype, no need to merge
850
+ metaclass = existing_meta
851
+ else:
852
+ # Merge with existing metaclass
853
+ class MergedMeta(existing_meta, ptype):
854
+ pass
855
+ metaclass = MergedMeta
851
856
  else:
852
857
  metaclass = ptype
853
858
 
@@ -872,9 +877,12 @@ def proto(
872
877
  namespace[key] = value
873
878
 
874
879
  # Create new class with metaclass
880
+ # IMPORTANT: Use (obj,) as bases to make new class a SUBCLASS of original.
881
+ # This ensures super() works correctly - the original class is in the MRO,
882
+ # so Python's super() validation passes when checking isinstance(self, original_class).
875
883
  new_cls = metaclass(
876
884
  obj.__name__,
877
- obj.__bases__,
885
+ (obj,),
878
886
  namespace
879
887
  )
880
888
 
@@ -17,7 +17,13 @@ import inspect
17
17
  import re
18
18
  from typing import Callable, TypeVar, Union
19
19
 
20
- from waterbear import DefaultBear
20
+ try:
21
+ from waterbear import DefaultBear
22
+ except ImportError:
23
+ raise ImportError(
24
+ "params_proto.v1 requires waterbear. Install it with: pip install waterbear\n"
25
+ "Note: The v3 API (from params_proto import proto) does not require waterbear."
26
+ )
21
27
 
22
28
 
23
29
  def _strtobool(val):
@@ -8,7 +8,13 @@ from types import BuiltinFunctionType, SimpleNamespace
8
8
  from warnings import warn
9
9
 
10
10
  from expandvars import expandvars
11
- from waterbear import Bear
11
+ try:
12
+ from waterbear import Bear
13
+ except ImportError:
14
+ raise ImportError(
15
+ "params_proto.v2 requires waterbear. Install it with: pip install waterbear\n"
16
+ "Note: The v3 API (from params_proto import proto) does not require waterbear."
17
+ )
12
18
 
13
19
  from params_proto.parse_env_template import all_available
14
20
  from params_proto.v2.utils import dot_to_deps
@@ -816,3 +816,141 @@ class TestProtoPrefixRequiresPrefix:
816
816
  # Unprefixed should error for @proto.prefix class
817
817
  result = run_cli(script, ["train-config", "--epochs", "200"], expect_error=True)
818
818
  assert "unrecognized argument" in result["stderr"]
819
+
820
+
821
+ class TestPositionalArgsInSubcommands:
822
+ """Test positional arguments captured by subcommand fields.
823
+
824
+ This tests the pattern: myapp add my-env/v1.2.3
825
+ Where 'add' is the subcommand and 'my-env/v1.2.3' is captured by the
826
+ subcommand's first required field.
827
+ """
828
+
829
+ def test_positional_arg_captured_by_subcommand_field(self, run_cli):
830
+ """Test positional arg after subcommand name is captured."""
831
+ script = dedent("""
832
+ from dataclasses import dataclass
833
+ from params_proto import proto
834
+
835
+ @dataclass
836
+ class AddCommand:
837
+ env: str # Required field, no default
838
+
839
+ @dataclass
840
+ class RemoveCommand:
841
+ env: str # Required field
842
+
843
+ @proto.cli
844
+ def main(command: AddCommand | RemoveCommand):
845
+ print(f"command={command.__class__.__name__}")
846
+ print(f"env={command.env}")
847
+
848
+ if __name__ == "__main__":
849
+ main()
850
+ """)
851
+
852
+ # Positional arg after subcommand should be captured by 'env' field
853
+ result = run_cli(script, ["add", "my-env/v1.2.3"])
854
+ lines = result["stdout"].strip().split("\n")
855
+ assert lines[0] == "command=AddCommand"
856
+ assert lines[1] == "env=my-env/v1.2.3"
857
+
858
+ result = run_cli(script, ["remove", "old-env/v0.9.0"])
859
+ lines = result["stdout"].strip().split("\n")
860
+ assert lines[0] == "command=RemoveCommand"
861
+ assert lines[1] == "env=old-env/v0.9.0"
862
+
863
+ def test_multiple_positional_args_in_subcommand(self, run_cli):
864
+ """Test multiple positional args captured by subcommand fields."""
865
+ script = dedent("""
866
+ from dataclasses import dataclass
867
+ from params_proto import proto
868
+
869
+ @dataclass
870
+ class InstallCommand:
871
+ package: str # Required, first positional
872
+ version: str # Required, second positional
873
+
874
+ @proto.cli
875
+ def main(command: InstallCommand):
876
+ print(f"package={command.package}")
877
+ print(f"version={command.version}")
878
+
879
+ if __name__ == "__main__":
880
+ main()
881
+ """)
882
+
883
+ result = run_cli(script, ["install", "requests", "2.28.0"])
884
+ lines = result["stdout"].strip().split("\n")
885
+ assert lines[0] == "package=requests"
886
+ assert lines[1] == "version=2.28.0"
887
+
888
+ def test_positional_with_named_args(self, run_cli):
889
+ """Test mixing positional and named args in subcommand."""
890
+ script = dedent("""
891
+ from dataclasses import dataclass
892
+ from params_proto import proto
893
+
894
+ @dataclass
895
+ class CloneCommand:
896
+ url: str # Required positional
897
+ depth: int = 0 # Optional with default
898
+
899
+ @proto.cli
900
+ def main(command: CloneCommand):
901
+ print(f"url={command.url}")
902
+ print(f"depth={command.depth}")
903
+
904
+ if __name__ == "__main__":
905
+ main()
906
+ """)
907
+
908
+ # Positional url, named depth
909
+ result = run_cli(script, ["clone", "https://github.com/test", "--depth", "1"])
910
+ lines = result["stdout"].strip().split("\n")
911
+ assert lines[0] == "url=https://github.com/test"
912
+ assert lines[1] == "depth=1"
913
+
914
+ def test_positional_arg_missing_should_error(self, run_cli):
915
+ """Test that missing required positional arg errors."""
916
+ script = dedent("""
917
+ from dataclasses import dataclass
918
+ from params_proto import proto
919
+
920
+ @dataclass
921
+ class AddCommand:
922
+ env: str # Required field
923
+
924
+ @proto.cli
925
+ def main(command: AddCommand):
926
+ print(f"env={command.env}")
927
+
928
+ if __name__ == "__main__":
929
+ main()
930
+ """)
931
+
932
+ # Missing required positional should error
933
+ result = run_cli(script, ["add"], expect_error=True)
934
+ assert result["returncode"] != 0
935
+
936
+ def test_extra_positional_should_error(self, run_cli):
937
+ """Test that extra unrecognized positional args error."""
938
+ script = dedent("""
939
+ from dataclasses import dataclass
940
+ from params_proto import proto
941
+
942
+ @dataclass
943
+ class AddCommand:
944
+ env: str # Required field
945
+
946
+ @proto.cli
947
+ def main(command: AddCommand):
948
+ print(f"env={command.env}")
949
+
950
+ if __name__ == "__main__":
951
+ main()
952
+ """)
953
+
954
+ # Extra positional arg should error
955
+ result = run_cli(script, ["add", "env1", "extra-arg"], expect_error=True)
956
+ assert result["returncode"] != 0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes