params-proto 3.2.3__tar.gz → 3.3.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.
- {params_proto-3.2.3 → params_proto-3.3.0}/.idea/workspace.xml +8 -3
- {params_proto-3.2.3 → params_proto-3.3.0}/PKG-INFO +1 -1
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/release_notes.md +25 -3
- {params_proto-3.2.3 → params_proto-3.3.0}/pyproject.toml +1 -1
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/cli/cli_parse.py +169 -17
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/proto.py +25 -73
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_class_level_methods.py +7 -5
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_nested_cli.py +12 -18
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_proto_core.py +2 -1
- {params_proto-3.2.3 → params_proto-3.3.0}/.claude-plugin/marketplace.json +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.claude-plugin/plugin.json +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.editorconfig +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.gitignore +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.idea/.gitignore +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.idea/codeStyles/codeStyleConfig.xml +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.idea/inspectionProfiles/Project_Default.xml +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.idea/inspectionProfiles/profiles_settings.xml +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.idea/markdown.xml +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.idea/misc.xml +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.idea/modules.xml +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.idea/params-proto.iml +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.idea/ruff.xml +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.idea/vcs.xml +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.readthedocs.yaml +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.run/pytest for test_neo_proto_cli.run.xml +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/.run/pytest in test_params_proto.run.xml +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/ANSI_HELP_CONSIDERATIONS.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/CLAUDE.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/LICENSE.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/Makefile +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/README +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/README.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/Makefile +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/_archive_v2/api/hyper.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/_archive_v2/api/proto.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/_archive_v2/api/utils.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/_archive_v2/examples/advanced_features.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/_archive_v2/examples/basic_usage.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/_archive_v2/examples/environment_variables.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/_archive_v2/examples/hyperparameter_sweeps.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/_archive_v2/examples/index.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/_archive_v2/examples/nested_configs.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/_archive_v2/quick_start.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/_static/ansi.css +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/_static/custom.css +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/api/index.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/api/proto.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/conf.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/examples/basic_usage.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/examples/cli_applications.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/examples/ml_training.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/examples/rl_agent.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/index.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/advanced_patterns.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/ansi_formatting.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/cli-fundamentals.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/cli-patterns.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/configuration-patterns.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/core-concepts.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/environment_variables.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/help-generation.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/hyperparameter_sweeps.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/naming-conventions.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/parameter-iteration.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/parameter-overrides.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/type-system.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/union_types.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/key_concepts/welcome.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/migration.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/quick_start.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/docs/requirements.txt +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/examples/union_subcommands.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/figures/man-page.png +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/figures/params-proto-autocompletion.gif +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/figures/spec_files.png +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/scratch/demo_params_proto.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/scratch/demo_v3.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/scratch/proto_DAT_scratch.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/scratch/proto_dependency_tree_pattern.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/scratch/test_super.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/scratch/test_super_minimal.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/skills/params-proto/SKILL.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/skills/params-proto/references/cli-and-types.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/skills/params-proto/references/environment-vars.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/skills/params-proto/references/patterns.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/skills/params-proto/references/sweeps.md +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/skills/params-proto.skill +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/__init__.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/app.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/cli/__init__.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/cli/ansi_help.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/cli/help_gen.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/documentation.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/envvar.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/hyper/__init__.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/hyper/proxies.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/hyper/sweep.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/parse_env_template.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/type_utils.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/v1/__init__.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/v1/hyper.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/v1/params_proto.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/v2/__init__.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/v2/hyper.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/v2/partial.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/v2/proto.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/src/params_proto/v2/utils.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v1/__init__.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v1/test_hyper.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v1/test_params_proto.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v2/test_Eval.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v2/test_neo_hyper.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v2/test_neo_proto.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v2/test_neo_proto_cli.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v2/test_neo_proto_partial.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v2/test_utils.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/conftest.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/samples/train.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_advanced_types.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_cli_parsing.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_help_strings.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_method_self_param.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_parse_env_template.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_piter.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_positional_example.sh +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_proto_comments.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_proto_context_manager.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_proto_envvar.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_proto_linebreaking.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_proto_partial.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_proto_required.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_strings.py +0 -0
- {params_proto-3.2.3 → params_proto-3.3.0}/tests/test_v3/test_sweep.py +0 -0
|
@@ -4,7 +4,10 @@
|
|
|
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/proto.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/params_proto/proto.py" afterDir="false" />
|
|
9
|
+
<change beforePath="$PROJECT_DIR$/tests/test_v3/test_class_level_methods.py" beforeDir="false" afterPath="$PROJECT_DIR$/tests/test_v3/test_class_level_methods.py" afterDir="false" />
|
|
10
|
+
</list>
|
|
8
11
|
<option name="SHOW_DIALOG" value="false" />
|
|
9
12
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
|
10
13
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
|
@@ -224,8 +227,8 @@
|
|
|
224
227
|
<component name="SharedIndexes">
|
|
225
228
|
<attachedChunks>
|
|
226
229
|
<set>
|
|
227
|
-
<option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-PY-253.30387.
|
|
228
|
-
<option value="bundled-python-sdk-
|
|
230
|
+
<option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-PY-253.30387.173" />
|
|
231
|
+
<option value="bundled-python-sdk-4762d8aabb82-6d6dccd035ac-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.30387.173" />
|
|
229
232
|
</set>
|
|
230
233
|
</attachedChunks>
|
|
231
234
|
</component>
|
|
@@ -260,6 +263,8 @@
|
|
|
260
263
|
<workItem from="1769933004821" duration="4284000" />
|
|
261
264
|
<workItem from="1770101044881" duration="1209000" />
|
|
262
265
|
<workItem from="1770110071925" duration="622000" />
|
|
266
|
+
<workItem from="1770163240415" duration="5903000" />
|
|
267
|
+
<workItem from="1770274540569" duration="373000" />
|
|
263
268
|
</task>
|
|
264
269
|
<task id="LOCAL-00001" summary="add design specs">
|
|
265
270
|
<option name="closed" value="true" />
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: params-proto
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.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
|
|
@@ -2,14 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
This page contains the release history and changelog for params-proto.
|
|
4
4
|
|
|
5
|
-
## Version 3.
|
|
5
|
+
## Version 3.3.0 (2025-02-04)
|
|
6
|
+
|
|
7
|
+
### ✨ Features
|
|
8
|
+
|
|
9
|
+
- **Deep Nested Dot Notation for CLI**: Support for overriding nested dataclass fields via CLI
|
|
10
|
+
```bash
|
|
11
|
+
# Before (not supported):
|
|
12
|
+
python train.py train-config --model.hidden-size 512 # error
|
|
13
|
+
|
|
14
|
+
# Now works:
|
|
15
|
+
python train.py train-config --epochs 200 --model.hidden-size 512 --model.num-layers 8
|
|
16
|
+
```
|
|
17
|
+
- Recursively detects nested dataclass fields in type annotations
|
|
18
|
+
- Supports arbitrary nesting depth (e.g., `--model.encoder.layers 12`)
|
|
19
|
+
- Automatically constructs nested dataclass instances
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Version 3.2.4 (2025-02-04)
|
|
6
24
|
|
|
7
25
|
### 🐛 Bug Fixes
|
|
8
26
|
|
|
9
27
|
- **Context Manager Protocol**: Fixed regression where `__enter__` and `__exit__` methods were stripped from `@proto` and `@proto.prefix` decorated classes
|
|
10
28
|
- Classes implementing context managers now work correctly with `with` statements
|
|
11
|
-
- Also preserves
|
|
12
|
-
|
|
29
|
+
- Also preserves all user-defined protocol methods: `__call__`, `__iter__`, `__next__`, `__getitem__`, `__len__`, etc.
|
|
30
|
+
|
|
31
|
+
- **Simplified Implementation**: Removed method wrapping that caused issues
|
|
32
|
+
- Decorated class is now a proper subclass of the original, so methods are inherited naturally
|
|
33
|
+
- No more wrapping of methods to return `self` - use explicit `return self` if needed
|
|
34
|
+
- Standard Python semantics: classmethods receive `cls`, not bound to instance
|
|
13
35
|
|
|
14
36
|
### 🧪 Testing
|
|
15
37
|
|
|
@@ -83,6 +83,101 @@ def _get_required_fields(cls) -> List[str]:
|
|
|
83
83
|
return required
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
def _is_nested_dataclass(annotation) -> bool:
|
|
87
|
+
"""Check if annotation is a nested dataclass type."""
|
|
88
|
+
import dataclasses
|
|
89
|
+
|
|
90
|
+
# Skip primitive types and common non-dataclass types
|
|
91
|
+
if annotation in {int, str, float, bool, list, dict, tuple, set, Path, type(None)}:
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
# Check if it's a type and a dataclass
|
|
95
|
+
if isinstance(annotation, type) and dataclasses.is_dataclass(annotation):
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _get_nested_attrs(cls, prefix: str = "") -> Dict[str, tuple]:
|
|
102
|
+
"""Recursively get all nested dataclass attributes.
|
|
103
|
+
|
|
104
|
+
Returns dict mapping dotted-kebab-name -> (dotted_underscore_path, leaf_type)
|
|
105
|
+
|
|
106
|
+
Example for TrainConfig with nested ModelConfig:
|
|
107
|
+
{
|
|
108
|
+
"model.hidden-size": ("model.hidden_size", int),
|
|
109
|
+
"model.num-layers": ("model.num_layers", int),
|
|
110
|
+
}
|
|
111
|
+
"""
|
|
112
|
+
import dataclasses
|
|
113
|
+
|
|
114
|
+
result = {}
|
|
115
|
+
annotations = getattr(cls, "__annotations__", {})
|
|
116
|
+
|
|
117
|
+
for attr_name, attr_type in annotations.items():
|
|
118
|
+
kebab_attr = attr_name.replace("_", "-")
|
|
119
|
+
full_kebab = f"{prefix}{kebab_attr}" if prefix else kebab_attr
|
|
120
|
+
full_underscore = f"{prefix.replace('-', '_')}{attr_name}" if prefix else attr_name
|
|
121
|
+
|
|
122
|
+
if _is_nested_dataclass(attr_type):
|
|
123
|
+
# Recursively get nested attributes
|
|
124
|
+
nested = _get_nested_attrs(attr_type, prefix=f"{full_kebab}.")
|
|
125
|
+
result.update(nested)
|
|
126
|
+
else:
|
|
127
|
+
# Leaf attribute
|
|
128
|
+
result[full_kebab] = (full_underscore, attr_type)
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _set_nested_value(d: dict, path: str, value: Any) -> None:
|
|
134
|
+
"""Set a value in a nested dict using dot notation path.
|
|
135
|
+
|
|
136
|
+
Example: _set_nested_value({}, "model.hidden_size", 512)
|
|
137
|
+
Results in {"model": {"hidden_size": 512}}
|
|
138
|
+
"""
|
|
139
|
+
parts = path.split(".")
|
|
140
|
+
current = d
|
|
141
|
+
for part in parts[:-1]:
|
|
142
|
+
if part not in current:
|
|
143
|
+
current[part] = {}
|
|
144
|
+
current = current[part]
|
|
145
|
+
current[parts[-1]] = value
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _build_nested_instance(cls, flat_attrs: dict, nested_attrs: dict):
|
|
149
|
+
"""Build a dataclass instance with nested dataclass fields.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
cls: The dataclass class to instantiate
|
|
153
|
+
flat_attrs: Dict of top-level attribute values
|
|
154
|
+
nested_attrs: Dict of nested dicts for nested dataclass fields
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Instance of cls with nested dataclasses properly constructed
|
|
158
|
+
"""
|
|
159
|
+
import dataclasses
|
|
160
|
+
|
|
161
|
+
final_attrs = dict(flat_attrs)
|
|
162
|
+
annotations = getattr(cls, "__annotations__", {})
|
|
163
|
+
|
|
164
|
+
for attr_name, attr_type in annotations.items():
|
|
165
|
+
if attr_name in nested_attrs and _is_nested_dataclass(attr_type):
|
|
166
|
+
# Recursively build nested dataclass
|
|
167
|
+
nested_data = nested_attrs[attr_name]
|
|
168
|
+
# Separate flat and nested for the nested class
|
|
169
|
+
nested_flat = {}
|
|
170
|
+
nested_nested = {}
|
|
171
|
+
for k, v in nested_data.items():
|
|
172
|
+
if isinstance(v, dict):
|
|
173
|
+
nested_nested[k] = v
|
|
174
|
+
else:
|
|
175
|
+
nested_flat[k] = v
|
|
176
|
+
final_attrs[attr_name] = _build_nested_instance(attr_type, nested_flat, nested_nested)
|
|
177
|
+
|
|
178
|
+
return cls(**final_attrs)
|
|
179
|
+
|
|
180
|
+
|
|
86
181
|
def _match_class_by_name(name: str, classes: list) -> Union[type, None]:
|
|
87
182
|
"""Match a string to one of the Union classes.
|
|
88
183
|
|
|
@@ -193,6 +288,7 @@ def parse_cli_args(wrapper) -> Dict[str, Any]:
|
|
|
193
288
|
# Build unprefixed union attribute map for classes NOT decorated with @proto.prefix
|
|
194
289
|
# Maps attr-name -> (union_param_name, attr_name_underscore)
|
|
195
290
|
# Classes in _SINGLETONS are @proto.prefix decorated and require prefixed attrs
|
|
291
|
+
# Also includes nested dataclass attributes (e.g., "model.hidden-size")
|
|
196
292
|
unprefixed_attrs = {}
|
|
197
293
|
for kebab_name, (param_name, union_classes) in union_params.items():
|
|
198
294
|
for cls in union_classes:
|
|
@@ -201,12 +297,20 @@ def parse_cli_args(wrapper) -> Dict[str, Any]:
|
|
|
201
297
|
if is_prefix_class:
|
|
202
298
|
continue
|
|
203
299
|
if hasattr(cls, "__annotations__"):
|
|
204
|
-
for attr_name in cls.__annotations__:
|
|
300
|
+
for attr_name, attr_type in cls.__annotations__.items():
|
|
205
301
|
kebab_attr = attr_name.replace("_", "-")
|
|
206
302
|
# Map to the union param (first one wins if multiple unions have same attr)
|
|
207
303
|
if kebab_attr not in unprefixed_attrs:
|
|
208
304
|
unprefixed_attrs[kebab_attr] = (param_name, attr_name)
|
|
209
305
|
|
|
306
|
+
# Check for nested dataclass and add its attributes
|
|
307
|
+
if _is_nested_dataclass(attr_type):
|
|
308
|
+
nested_attrs = _get_nested_attrs(attr_type, prefix=f"{kebab_attr}.")
|
|
309
|
+
for nested_kebab, (nested_path, nested_type) in nested_attrs.items():
|
|
310
|
+
full_path = f"{attr_name}.{nested_path.split('.', 1)[1] if '.' in nested_path else nested_path}"
|
|
311
|
+
if nested_kebab not in unprefixed_attrs:
|
|
312
|
+
unprefixed_attrs[nested_kebab] = (param_name, full_path)
|
|
313
|
+
|
|
210
314
|
# Parse arguments
|
|
211
315
|
result = {}
|
|
212
316
|
prefix_values = {} # (singleton, param_name) -> value
|
|
@@ -451,21 +555,65 @@ def parse_cli_args(wrapper) -> Dict[str, Any]:
|
|
|
451
555
|
# Instantiate Union classes with collected attributes
|
|
452
556
|
for param_name, selected_class in union_selections.items():
|
|
453
557
|
# Collect attributes for this Union parameter
|
|
454
|
-
|
|
455
|
-
|
|
558
|
+
# Separate flat (top-level) and nested attributes
|
|
559
|
+
flat_attrs = {}
|
|
560
|
+
nested_attrs = {} # For nested dataclass fields
|
|
561
|
+
|
|
562
|
+
for (union_param, attr_path), value_str in union_attrs.items():
|
|
456
563
|
if union_param == param_name:
|
|
457
|
-
#
|
|
458
|
-
if
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
564
|
+
# Check if this is a nested path (contains dots)
|
|
565
|
+
if "." in attr_path:
|
|
566
|
+
# Nested attribute like "model.hidden_size"
|
|
567
|
+
parts = attr_path.split(".")
|
|
568
|
+
top_level = parts[0]
|
|
569
|
+
rest_path = ".".join(parts[1:])
|
|
570
|
+
|
|
571
|
+
# Get the type of the nested field
|
|
572
|
+
if hasattr(selected_class, "__annotations__"):
|
|
573
|
+
top_type = selected_class.__annotations__.get(top_level)
|
|
574
|
+
if top_type and _is_nested_dataclass(top_type):
|
|
575
|
+
# Find the leaf type by traversing the path
|
|
576
|
+
current_type = top_type
|
|
577
|
+
for part in parts[1:]:
|
|
578
|
+
if hasattr(current_type, "__annotations__"):
|
|
579
|
+
current_type = current_type.__annotations__.get(part, str)
|
|
580
|
+
else:
|
|
581
|
+
current_type = str
|
|
582
|
+
break
|
|
583
|
+
|
|
584
|
+
try:
|
|
585
|
+
value = _convert_type(value_str, current_type)
|
|
586
|
+
except (ValueError, TypeError):
|
|
587
|
+
raise SystemExit(
|
|
588
|
+
f"error: invalid value for --{attr_path.replace('_', '-')}: {value_str}"
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
# Store in nested structure
|
|
592
|
+
if top_level not in nested_attrs:
|
|
593
|
+
nested_attrs[top_level] = {}
|
|
594
|
+
_set_nested_value(nested_attrs[top_level], rest_path, value)
|
|
595
|
+
continue
|
|
596
|
+
|
|
597
|
+
# Fallback: treat as string
|
|
598
|
+
if top_level not in nested_attrs:
|
|
599
|
+
nested_attrs[top_level] = {}
|
|
600
|
+
_set_nested_value(nested_attrs[top_level], rest_path, value_str)
|
|
466
601
|
else:
|
|
467
|
-
#
|
|
468
|
-
|
|
602
|
+
# Top-level attribute
|
|
603
|
+
if hasattr(selected_class, "__annotations__"):
|
|
604
|
+
attr_type = selected_class.__annotations__.get(attr_path, str)
|
|
605
|
+
try:
|
|
606
|
+
flat_attrs[attr_path] = _convert_type(value_str, attr_type)
|
|
607
|
+
except (ValueError, TypeError):
|
|
608
|
+
raise SystemExit(
|
|
609
|
+
f"error: invalid value for --{param_name.replace('_', '-')}.{attr_path.replace('_', '-')}: {value_str}"
|
|
610
|
+
)
|
|
611
|
+
else:
|
|
612
|
+
# No annotations, treat as string
|
|
613
|
+
flat_attrs[attr_path] = value_str
|
|
614
|
+
|
|
615
|
+
# Merge flat_attrs into attrs for compatibility with existing code
|
|
616
|
+
attrs = flat_attrs
|
|
469
617
|
|
|
470
618
|
# Assign positional args to required fields of the selected class
|
|
471
619
|
if param_name in union_positional and union_positional[param_name]:
|
|
@@ -508,17 +656,21 @@ def parse_cli_args(wrapper) -> Dict[str, Any]:
|
|
|
508
656
|
attrs[key] = value
|
|
509
657
|
break
|
|
510
658
|
|
|
511
|
-
# Check for missing required fields
|
|
659
|
+
# Check for missing required fields (only check top-level, nested have defaults)
|
|
512
660
|
required_fields = _get_required_fields(selected_class)
|
|
513
661
|
for field_name in required_fields:
|
|
514
|
-
if field_name not in attrs:
|
|
662
|
+
if field_name not in attrs and field_name not in nested_attrs:
|
|
515
663
|
raise SystemExit(
|
|
516
664
|
f"error: {selected_class.__name__} requires argument: {field_name}"
|
|
517
665
|
)
|
|
518
666
|
|
|
519
667
|
# Instantiate the class with collected attributes
|
|
520
668
|
try:
|
|
521
|
-
|
|
669
|
+
if nested_attrs:
|
|
670
|
+
# Build with nested dataclass support
|
|
671
|
+
instance = _build_nested_instance(selected_class, attrs, nested_attrs)
|
|
672
|
+
else:
|
|
673
|
+
instance = selected_class(**attrs)
|
|
522
674
|
result[param_name] = instance
|
|
523
675
|
except TypeError as e:
|
|
524
676
|
raise SystemExit(f"error: failed to instantiate {selected_class.__name__}: {e}")
|
|
@@ -694,59 +694,9 @@ class ptype(type):
|
|
|
694
694
|
|
|
695
695
|
# Update the instance's class to the decorated class
|
|
696
696
|
# This allows isinstance(instance, DecoratedClass) to work
|
|
697
|
+
# Since cls is a subclass of original_cls, methods are inherited naturally
|
|
697
698
|
object.__setattr__(instance, "__class__", cls)
|
|
698
699
|
|
|
699
|
-
# Copy methods from original class and wrap to return self
|
|
700
|
-
for name in dir(original_cls):
|
|
701
|
-
# Skip proto fields (fields are handled above)
|
|
702
|
-
if name in annotations:
|
|
703
|
-
continue
|
|
704
|
-
|
|
705
|
-
# For dunder methods, only copy user-defined ones (not inherited from object/type)
|
|
706
|
-
if name.startswith("__"):
|
|
707
|
-
# Check if this dunder method is user-defined (not from object or type)
|
|
708
|
-
is_user_defined = False
|
|
709
|
-
for klass in original_cls.__mro__:
|
|
710
|
-
if klass is object or klass is type:
|
|
711
|
-
break
|
|
712
|
-
if name in klass.__dict__:
|
|
713
|
-
is_user_defined = True
|
|
714
|
-
break
|
|
715
|
-
if not is_user_defined:
|
|
716
|
-
continue
|
|
717
|
-
|
|
718
|
-
# Check raw descriptor in MRO to detect staticmethod/classmethod (handles inheritance)
|
|
719
|
-
raw_attr = None
|
|
720
|
-
for klass in original_cls.__mro__:
|
|
721
|
-
if name in klass.__dict__:
|
|
722
|
-
raw_attr = klass.__dict__[name]
|
|
723
|
-
break
|
|
724
|
-
|
|
725
|
-
attr = getattr(original_cls, name)
|
|
726
|
-
|
|
727
|
-
# Only process actual methods (staticmethod, classmethod, or function)
|
|
728
|
-
if isinstance(raw_attr, staticmethod):
|
|
729
|
-
# For staticmethod, use directly (no binding needed)
|
|
730
|
-
method = attr
|
|
731
|
-
elif isinstance(raw_attr, classmethod) or inspect.isfunction(raw_attr) or inspect.ismethod(attr):
|
|
732
|
-
# For instance methods and classmethods, bind to instance
|
|
733
|
-
# Note: classmethods bound to instance is intentional for @proto
|
|
734
|
-
# semantics where instances have all attributes accessible
|
|
735
|
-
method = attr.__get__(instance, original_cls)
|
|
736
|
-
else:
|
|
737
|
-
# Not a method (e.g., _EnvVar, property, or other callable), skip
|
|
738
|
-
continue
|
|
739
|
-
|
|
740
|
-
# Wrap it to return self if it returns None
|
|
741
|
-
def make_wrapper(m):
|
|
742
|
-
def wrapper(*args, **kwargs):
|
|
743
|
-
result = m(*args, **kwargs)
|
|
744
|
-
return instance if result is None else result
|
|
745
|
-
|
|
746
|
-
return wrapper
|
|
747
|
-
|
|
748
|
-
setattr(instance, name, make_wrapper(method))
|
|
749
|
-
|
|
750
700
|
return instance
|
|
751
701
|
|
|
752
702
|
|
|
@@ -869,30 +819,32 @@ def proto(
|
|
|
869
819
|
else:
|
|
870
820
|
metaclass = ptype
|
|
871
821
|
|
|
872
|
-
#
|
|
873
|
-
#
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
except AttributeError:
|
|
885
|
-
pass
|
|
822
|
+
# Create new class with metaclass as subclass of original
|
|
823
|
+
# Since new class inherits from obj, methods are inherited naturally.
|
|
824
|
+
# We only need to provide:
|
|
825
|
+
# - Module/qualname metadata
|
|
826
|
+
# - Annotations (so annotated fields are visible on the class)
|
|
827
|
+
# - Resolved default values (with EnvVars resolved)
|
|
828
|
+
namespace = {
|
|
829
|
+
"__module__": obj.__module__,
|
|
830
|
+
"__qualname__": obj.__qualname__,
|
|
831
|
+
"__doc__": obj.__doc__,
|
|
832
|
+
"__annotations__": annotations,
|
|
833
|
+
}
|
|
886
834
|
|
|
887
|
-
#
|
|
888
|
-
#
|
|
889
|
-
for key
|
|
890
|
-
|
|
835
|
+
# Add resolved default values (EnvVars are already resolved in defaults dict)
|
|
836
|
+
# Also set None for annotated fields without defaults so they're accessible
|
|
837
|
+
for key in annotations.keys():
|
|
838
|
+
if key in defaults:
|
|
839
|
+
namespace[key] = defaults[key]
|
|
840
|
+
else:
|
|
841
|
+
namespace[key] = None
|
|
891
842
|
|
|
892
|
-
# Create new class
|
|
893
|
-
#
|
|
894
|
-
#
|
|
895
|
-
#
|
|
843
|
+
# Create new class as SUBCLASS of original.
|
|
844
|
+
# This ensures:
|
|
845
|
+
# 1. super() works correctly (original class is in MRO)
|
|
846
|
+
# 2. Methods, staticmethods, classmethods are inherited naturally
|
|
847
|
+
# 3. isinstance(instance, DecoratedClass) works
|
|
896
848
|
new_cls = metaclass(
|
|
897
849
|
obj.__name__,
|
|
898
850
|
(obj,),
|
|
@@ -150,13 +150,15 @@ class TestProtoPrefixWithMethods:
|
|
|
150
150
|
|
|
151
151
|
obj = Config()
|
|
152
152
|
|
|
153
|
-
# Classmethod should have access to config attributes via
|
|
153
|
+
# Classmethod should have access to config attributes via class
|
|
154
154
|
assert obj.get_lr() == 0.01
|
|
155
155
|
|
|
156
|
-
# Note: classmethods
|
|
157
|
-
# so they see
|
|
158
|
-
obj.lr = 0.001
|
|
159
|
-
assert obj.get_lr() == 0.
|
|
156
|
+
# Note: classmethods receive the class (cls), not instance,
|
|
157
|
+
# so they see class-level values (standard Python behavior)
|
|
158
|
+
obj.lr = 0.001 # Instance attribute
|
|
159
|
+
assert obj.get_lr() == 0.01 # Still sees class default
|
|
160
|
+
Config.lr = 0.002 # Class attribute
|
|
161
|
+
assert obj.get_lr() == 0.002 # Now sees class update
|
|
160
162
|
|
|
161
163
|
def test_proto_prefix_with_instance_method(self):
|
|
162
164
|
"""Test @proto.prefix on class with regular instance method."""
|
|
@@ -171,33 +171,27 @@ class TestThreeLayerNesting:
|
|
|
171
171
|
main()
|
|
172
172
|
""")
|
|
173
173
|
|
|
174
|
-
#
|
|
175
|
-
#
|
|
174
|
+
# Override nested model config via deep dot notation
|
|
175
|
+
# Plain dataclasses use unprefixed syntax (--epochs, not --config.epochs)
|
|
176
|
+
# Only @proto.prefix decorated classes require prefixed syntax
|
|
176
177
|
result = run_cli(
|
|
177
178
|
script,
|
|
178
179
|
[
|
|
179
|
-
"
|
|
180
|
-
"--
|
|
180
|
+
"train-config", # subcommand name (kebab-case)
|
|
181
|
+
"--epochs",
|
|
181
182
|
"200",
|
|
182
|
-
"--
|
|
183
|
+
"--model.hidden-size",
|
|
183
184
|
"512",
|
|
184
|
-
"--
|
|
185
|
+
"--model.num-layers",
|
|
185
186
|
"8",
|
|
186
187
|
],
|
|
187
|
-
expect_error=True, # This may not be supported yet
|
|
188
188
|
)
|
|
189
189
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
assert lines[2] == "model.num_layers=8"
|
|
196
|
-
else:
|
|
197
|
-
# If it failed, this documents the current limitation
|
|
198
|
-
pytest.skip(
|
|
199
|
-
"Deep nested dot notation (--config.model.hidden_size) not yet supported"
|
|
200
|
-
)
|
|
190
|
+
assert result["returncode"] == 0, f"CLI failed: {result['stderr']}"
|
|
191
|
+
lines = result["stdout"].strip().split("\n")
|
|
192
|
+
assert lines[0] == "epochs=200"
|
|
193
|
+
assert lines[1] == "model.hidden_size=512"
|
|
194
|
+
assert lines[2] == "model.num_layers=8"
|
|
201
195
|
|
|
202
196
|
|
|
203
197
|
class TestThreeLayerUnionNesting:
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{params_proto-3.2.3 → params_proto-3.3.0}/docs/_archive_v2/examples/environment_variables.md
RENAMED
|
File without changes
|
{params_proto-3.2.3 → params_proto-3.3.0}/docs/_archive_v2/examples/hyperparameter_sweeps.md
RENAMED
|
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
|
|
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
|
|
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
|
|
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
|
{params_proto-3.2.3 → params_proto-3.3.0}/skills/params-proto/references/environment-vars.md
RENAMED
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|