params-proto 3.2.2__tar.gz → 3.2.4__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 (133) hide show
  1. {params_proto-3.2.2 → params_proto-3.2.4}/.idea/workspace.xml +8 -3
  2. {params_proto-3.2.2 → params_proto-3.2.4}/PKG-INFO +1 -1
  3. {params_proto-3.2.2 → params_proto-3.2.4}/docs/release_notes.md +19 -0
  4. {params_proto-3.2.2 → params_proto-3.2.4}/pyproject.toml +1 -1
  5. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/proto.py +25 -60
  6. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_class_level_methods.py +7 -5
  7. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_nested_cli.py +8 -7
  8. params_proto-3.2.4/tests/test_v3/test_proto_context_manager.py +179 -0
  9. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_proto_core.py +2 -1
  10. {params_proto-3.2.2 → params_proto-3.2.4}/.claude-plugin/marketplace.json +0 -0
  11. {params_proto-3.2.2 → params_proto-3.2.4}/.claude-plugin/plugin.json +0 -0
  12. {params_proto-3.2.2 → params_proto-3.2.4}/.editorconfig +0 -0
  13. {params_proto-3.2.2 → params_proto-3.2.4}/.gitignore +0 -0
  14. {params_proto-3.2.2 → params_proto-3.2.4}/.idea/.gitignore +0 -0
  15. {params_proto-3.2.2 → params_proto-3.2.4}/.idea/codeStyles/codeStyleConfig.xml +0 -0
  16. {params_proto-3.2.2 → params_proto-3.2.4}/.idea/inspectionProfiles/Project_Default.xml +0 -0
  17. {params_proto-3.2.2 → params_proto-3.2.4}/.idea/inspectionProfiles/profiles_settings.xml +0 -0
  18. {params_proto-3.2.2 → params_proto-3.2.4}/.idea/markdown.xml +0 -0
  19. {params_proto-3.2.2 → params_proto-3.2.4}/.idea/misc.xml +0 -0
  20. {params_proto-3.2.2 → params_proto-3.2.4}/.idea/modules.xml +0 -0
  21. {params_proto-3.2.2 → params_proto-3.2.4}/.idea/params-proto.iml +0 -0
  22. {params_proto-3.2.2 → params_proto-3.2.4}/.idea/ruff.xml +0 -0
  23. {params_proto-3.2.2 → params_proto-3.2.4}/.idea/vcs.xml +0 -0
  24. {params_proto-3.2.2 → params_proto-3.2.4}/.readthedocs.yaml +0 -0
  25. {params_proto-3.2.2 → params_proto-3.2.4}/.run/pytest for test_neo_proto_cli.run.xml +0 -0
  26. {params_proto-3.2.2 → params_proto-3.2.4}/.run/pytest in test_params_proto.run.xml +0 -0
  27. {params_proto-3.2.2 → params_proto-3.2.4}/ANSI_HELP_CONSIDERATIONS.md +0 -0
  28. {params_proto-3.2.2 → params_proto-3.2.4}/CLAUDE.md +0 -0
  29. {params_proto-3.2.2 → params_proto-3.2.4}/LICENSE.md +0 -0
  30. {params_proto-3.2.2 → params_proto-3.2.4}/Makefile +0 -0
  31. {params_proto-3.2.2 → params_proto-3.2.4}/README +0 -0
  32. {params_proto-3.2.2 → params_proto-3.2.4}/README.md +0 -0
  33. {params_proto-3.2.2 → params_proto-3.2.4}/docs/Makefile +0 -0
  34. {params_proto-3.2.2 → params_proto-3.2.4}/docs/_archive_v2/api/hyper.md +0 -0
  35. {params_proto-3.2.2 → params_proto-3.2.4}/docs/_archive_v2/api/proto.md +0 -0
  36. {params_proto-3.2.2 → params_proto-3.2.4}/docs/_archive_v2/api/utils.md +0 -0
  37. {params_proto-3.2.2 → params_proto-3.2.4}/docs/_archive_v2/examples/advanced_features.md +0 -0
  38. {params_proto-3.2.2 → params_proto-3.2.4}/docs/_archive_v2/examples/basic_usage.md +0 -0
  39. {params_proto-3.2.2 → params_proto-3.2.4}/docs/_archive_v2/examples/environment_variables.md +0 -0
  40. {params_proto-3.2.2 → params_proto-3.2.4}/docs/_archive_v2/examples/hyperparameter_sweeps.md +0 -0
  41. {params_proto-3.2.2 → params_proto-3.2.4}/docs/_archive_v2/examples/index.md +0 -0
  42. {params_proto-3.2.2 → params_proto-3.2.4}/docs/_archive_v2/examples/nested_configs.md +0 -0
  43. {params_proto-3.2.2 → params_proto-3.2.4}/docs/_archive_v2/quick_start.md +0 -0
  44. {params_proto-3.2.2 → params_proto-3.2.4}/docs/_static/ansi.css +0 -0
  45. {params_proto-3.2.2 → params_proto-3.2.4}/docs/_static/custom.css +0 -0
  46. {params_proto-3.2.2 → params_proto-3.2.4}/docs/api/index.md +0 -0
  47. {params_proto-3.2.2 → params_proto-3.2.4}/docs/api/proto.md +0 -0
  48. {params_proto-3.2.2 → params_proto-3.2.4}/docs/conf.py +0 -0
  49. {params_proto-3.2.2 → params_proto-3.2.4}/docs/examples/basic_usage.md +0 -0
  50. {params_proto-3.2.2 → params_proto-3.2.4}/docs/examples/cli_applications.md +0 -0
  51. {params_proto-3.2.2 → params_proto-3.2.4}/docs/examples/ml_training.md +0 -0
  52. {params_proto-3.2.2 → params_proto-3.2.4}/docs/examples/rl_agent.md +0 -0
  53. {params_proto-3.2.2 → params_proto-3.2.4}/docs/index.md +0 -0
  54. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/advanced_patterns.md +0 -0
  55. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/ansi_formatting.md +0 -0
  56. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/cli-fundamentals.md +0 -0
  57. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/cli-patterns.md +0 -0
  58. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/configuration-patterns.md +0 -0
  59. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/core-concepts.md +0 -0
  60. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/environment_variables.md +0 -0
  61. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/help-generation.md +0 -0
  62. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/hyperparameter_sweeps.md +0 -0
  63. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/naming-conventions.md +0 -0
  64. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/parameter-iteration.md +0 -0
  65. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/parameter-overrides.md +0 -0
  66. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/type-system.md +0 -0
  67. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/union_types.md +0 -0
  68. {params_proto-3.2.2 → params_proto-3.2.4}/docs/key_concepts/welcome.md +0 -0
  69. {params_proto-3.2.2 → params_proto-3.2.4}/docs/migration.md +0 -0
  70. {params_proto-3.2.2 → params_proto-3.2.4}/docs/quick_start.md +0 -0
  71. {params_proto-3.2.2 → params_proto-3.2.4}/docs/requirements.txt +0 -0
  72. {params_proto-3.2.2 → params_proto-3.2.4}/examples/union_subcommands.py +0 -0
  73. {params_proto-3.2.2 → params_proto-3.2.4}/figures/man-page.png +0 -0
  74. {params_proto-3.2.2 → params_proto-3.2.4}/figures/params-proto-autocompletion.gif +0 -0
  75. {params_proto-3.2.2 → params_proto-3.2.4}/figures/spec_files.png +0 -0
  76. {params_proto-3.2.2 → params_proto-3.2.4}/scratch/demo_params_proto.py +0 -0
  77. {params_proto-3.2.2 → params_proto-3.2.4}/scratch/demo_v3.py +0 -0
  78. {params_proto-3.2.2 → params_proto-3.2.4}/scratch/proto_DAT_scratch.py +0 -0
  79. {params_proto-3.2.2 → params_proto-3.2.4}/scratch/proto_dependency_tree_pattern.py +0 -0
  80. {params_proto-3.2.2 → params_proto-3.2.4}/scratch/test_super.py +0 -0
  81. {params_proto-3.2.2 → params_proto-3.2.4}/scratch/test_super_minimal.py +0 -0
  82. {params_proto-3.2.2 → params_proto-3.2.4}/skills/params-proto/SKILL.md +0 -0
  83. {params_proto-3.2.2 → params_proto-3.2.4}/skills/params-proto/references/cli-and-types.md +0 -0
  84. {params_proto-3.2.2 → params_proto-3.2.4}/skills/params-proto/references/environment-vars.md +0 -0
  85. {params_proto-3.2.2 → params_proto-3.2.4}/skills/params-proto/references/patterns.md +0 -0
  86. {params_proto-3.2.2 → params_proto-3.2.4}/skills/params-proto/references/sweeps.md +0 -0
  87. {params_proto-3.2.2 → params_proto-3.2.4}/skills/params-proto.skill +0 -0
  88. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/__init__.py +0 -0
  89. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/app.py +0 -0
  90. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/cli/__init__.py +0 -0
  91. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/cli/ansi_help.py +0 -0
  92. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/cli/cli_parse.py +0 -0
  93. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/cli/help_gen.py +0 -0
  94. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/documentation.py +0 -0
  95. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/envvar.py +0 -0
  96. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/hyper/__init__.py +0 -0
  97. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/hyper/proxies.py +0 -0
  98. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/hyper/sweep.py +0 -0
  99. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/parse_env_template.py +0 -0
  100. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/type_utils.py +0 -0
  101. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/v1/__init__.py +0 -0
  102. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/v1/hyper.py +0 -0
  103. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/v1/params_proto.py +0 -0
  104. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/v2/__init__.py +0 -0
  105. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/v2/hyper.py +0 -0
  106. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/v2/partial.py +0 -0
  107. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/v2/proto.py +0 -0
  108. {params_proto-3.2.2 → params_proto-3.2.4}/src/params_proto/v2/utils.py +0 -0
  109. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v1/__init__.py +0 -0
  110. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v1/test_hyper.py +0 -0
  111. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v1/test_params_proto.py +0 -0
  112. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v2/test_Eval.py +0 -0
  113. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v2/test_neo_hyper.py +0 -0
  114. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v2/test_neo_proto.py +0 -0
  115. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v2/test_neo_proto_cli.py +0 -0
  116. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v2/test_neo_proto_partial.py +0 -0
  117. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v2/test_utils.py +0 -0
  118. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/conftest.py +0 -0
  119. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/samples/train.py +0 -0
  120. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_advanced_types.py +0 -0
  121. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_cli_parsing.py +0 -0
  122. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_help_strings.py +0 -0
  123. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_method_self_param.py +0 -0
  124. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_parse_env_template.py +0 -0
  125. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_piter.py +0 -0
  126. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_positional_example.sh +0 -0
  127. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_proto_comments.py +0 -0
  128. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_proto_envvar.py +0 -0
  129. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_proto_linebreaking.py +0 -0
  130. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_proto_partial.py +0 -0
  131. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_proto_required.py +0 -0
  132. {params_proto-3.2.2 → params_proto-3.2.4}/tests/test_v3/test_strings.py +0 -0
  133. {params_proto-3.2.2 → params_proto-3.2.4}/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.127" />
228
- <option value="bundled-python-sdk-3944b0c99280-6d6dccd035ac-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.30387.127" />
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.2.2
3
+ Version: 3.2.4
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,6 +2,25 @@
2
2
 
3
3
  This page contains the release history and changelog for params-proto.
4
4
 
5
+ ## Version 3.2.4 (2025-02-04)
6
+
7
+ ### 🐛 Bug Fixes
8
+
9
+ - **Context Manager Protocol**: Fixed regression where `__enter__` and `__exit__` methods were stripped from `@proto` and `@proto.prefix` decorated classes
10
+ - Classes implementing context managers now work correctly with `with` statements
11
+ - Also preserves all user-defined protocol methods: `__call__`, `__iter__`, `__next__`, `__getitem__`, `__len__`, etc.
12
+
13
+ - **Simplified Implementation**: Removed method wrapping that caused issues
14
+ - Decorated class is now a proper subclass of the original, so methods are inherited naturally
15
+ - No more wrapping of methods to return `self` - use explicit `return self` if needed
16
+ - Standard Python semantics: classmethods receive `cls`, not bound to instance
17
+
18
+ ### 🧪 Testing
19
+
20
+ - Added comprehensive tests for context manager and other Python protocol preservation
21
+
22
+ ---
23
+
5
24
  ## Version 3.2.2 (2025-02-03)
6
25
 
7
26
  ### 🐛 Bug Fixes
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "params-proto"
3
- version = "3.2.2"
3
+ version = "3.2.4"
4
4
  description = "Modern Hyper Parameter Management for Machine Learning"
5
5
  authors = [
6
6
  { name = "Ge Yang" }
@@ -694,46 +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 dunder methods and proto fields (fields are handled above)
702
- if name.startswith("__") or name in annotations:
703
- continue
704
-
705
- # Check raw descriptor in MRO to detect staticmethod/classmethod (handles inheritance)
706
- raw_attr = None
707
- for klass in original_cls.__mro__:
708
- if name in klass.__dict__:
709
- raw_attr = klass.__dict__[name]
710
- break
711
-
712
- attr = getattr(original_cls, name)
713
-
714
- # Only process actual methods (staticmethod, classmethod, or function)
715
- if isinstance(raw_attr, staticmethod):
716
- # For staticmethod, use directly (no binding needed)
717
- method = attr
718
- elif isinstance(raw_attr, classmethod) or inspect.isfunction(raw_attr) or inspect.ismethod(attr):
719
- # For instance methods and classmethods, bind to instance
720
- # Note: classmethods bound to instance is intentional for @proto
721
- # semantics where instances have all attributes accessible
722
- method = attr.__get__(instance, original_cls)
723
- else:
724
- # Not a method (e.g., _EnvVar, property, or other callable), skip
725
- continue
726
-
727
- # Wrap it to return self if it returns None
728
- def make_wrapper(m):
729
- def wrapper(*args, **kwargs):
730
- result = m(*args, **kwargs)
731
- return instance if result is None else result
732
-
733
- return wrapper
734
-
735
- setattr(instance, name, make_wrapper(method))
736
-
737
700
  return instance
738
701
 
739
702
 
@@ -856,30 +819,32 @@ def proto(
856
819
  else:
857
820
  metaclass = ptype
858
821
 
859
- # Recreate the class with ptype as its metaclass
860
- # Collect class namespace (attributes and methods)
861
- namespace = {}
862
- for key in dir(obj):
863
- if not key.startswith("__") or key in ("__annotations__", "__module__", "__qualname__", "__doc__"):
864
- try:
865
- # Use __dict__ to preserve classmethod/staticmethod descriptors
866
- # getattr() would return bound methods instead of descriptors
867
- if key in obj.__dict__:
868
- namespace[key] = obj.__dict__[key]
869
- else:
870
- namespace[key] = getattr(obj, key)
871
- except AttributeError:
872
- 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
+ }
873
834
 
874
- # Replace _EnvVar objects with resolved values from defaults
875
- # This ensures the descriptor doesn't interfere with class attribute access
876
- for key, value in defaults.items():
877
- namespace[key] = value
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
878
842
 
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).
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
883
848
  new_cls = metaclass(
884
849
  obj.__name__,
885
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 instance
153
+ # Classmethod should have access to config attributes via class
154
154
  assert obj.get_lr() == 0.01
155
155
 
156
- # Note: classmethods on instances are bound to the instance,
157
- # so they see instance attributes (not class-level updates)
158
- obj.lr = 0.001
159
- assert obj.get_lr() == 0.001
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."""
@@ -172,19 +172,20 @@ class TestThreeLayerNesting:
172
172
  """)
173
173
 
174
174
  # Try to override nested model config via deep dot notation
175
- # This tests if --config.model.hidden_size works
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
- "--config:TrainConfig",
180
- "--config.epochs",
180
+ "train-config", # subcommand name (kebab-case)
181
+ "--epochs",
181
182
  "200",
182
- "--config.model.hidden_size",
183
+ "--model.hidden_size",
183
184
  "512",
184
- "--config.model.num_layers",
185
+ "--model.num_layers",
185
186
  "8",
186
187
  ],
187
- expect_error=True, # This may not be supported yet
188
+ expect_error=True, # Deep nested dot notation may not be supported yet
188
189
  )
189
190
 
190
191
  # Check if it succeeded or failed
@@ -196,7 +197,7 @@ class TestThreeLayerNesting:
196
197
  else:
197
198
  # If it failed, this documents the current limitation
198
199
  pytest.skip(
199
- "Deep nested dot notation (--config.model.hidden_size) not yet supported"
200
+ "Deep nested dot notation (--model.hidden_size) not yet supported"
200
201
  )
201
202
 
202
203
 
@@ -0,0 +1,179 @@
1
+ """Test that @proto and @proto.prefix preserve context manager protocol."""
2
+
3
+ import pytest
4
+ from params_proto import proto
5
+
6
+
7
+ class TestContextManager:
8
+ """Test context manager protocol preservation."""
9
+
10
+ def test_proto_prefix_context_manager(self):
11
+ """Test that @proto.prefix preserves __enter__ and __exit__."""
12
+
13
+ @proto.prefix
14
+ class RUN:
15
+ prefix: str = "test"
16
+ entered: bool = False
17
+ exited: bool = False
18
+
19
+ def __enter__(self):
20
+ self.entered = True
21
+ return self
22
+
23
+ def __exit__(self, exc_type, exc_val, exc_tb):
24
+ self.exited = True
25
+ return False
26
+
27
+ run = RUN()
28
+
29
+ # Check methods exist
30
+ assert hasattr(run, "__enter__"), "Should have __enter__ method"
31
+ assert hasattr(run, "__exit__"), "Should have __exit__ method"
32
+
33
+ # Test actual context manager usage
34
+ with run as r:
35
+ assert r.entered, "Should have called __enter__"
36
+ assert not r.exited, "Should not have called __exit__ yet"
37
+
38
+ assert run.exited, "Should have called __exit__"
39
+
40
+ def test_proto_context_manager(self):
41
+ """Test that @proto preserves __enter__ and __exit__."""
42
+
43
+ @proto
44
+ class Session:
45
+ name: str = "session"
46
+ active: bool = False
47
+
48
+ def __enter__(self):
49
+ self.active = True
50
+ return self
51
+
52
+ def __exit__(self, exc_type, exc_val, exc_tb):
53
+ self.active = False
54
+ return False
55
+
56
+ session = Session()
57
+
58
+ with session as s:
59
+ assert s.active, "Should be active inside context"
60
+
61
+ assert not session.active, "Should be inactive after context"
62
+
63
+ def test_context_manager_with_exception(self):
64
+ """Test context manager handles exceptions correctly."""
65
+
66
+ @proto.prefix
67
+ class RUN:
68
+ prefix: str = "test"
69
+ exc_info: tuple = None
70
+
71
+ def __enter__(self):
72
+ return self
73
+
74
+ def __exit__(self, exc_type, exc_val, exc_tb):
75
+ self.exc_info = (exc_type, exc_val, exc_tb)
76
+ return True # Suppress exception
77
+
78
+ run = RUN()
79
+
80
+ with run:
81
+ raise ValueError("test error")
82
+
83
+ # Exception should have been captured
84
+ assert run.exc_info[0] is ValueError
85
+ assert str(run.exc_info[1]) == "test error"
86
+
87
+ def test_context_manager_inheritance(self):
88
+ """Test context manager works with inheritance."""
89
+
90
+ class BaseContext:
91
+ def __enter__(self):
92
+ self.entered = True
93
+ return self
94
+
95
+ def __exit__(self, exc_type, exc_val, exc_tb):
96
+ self.exited = True
97
+ return False
98
+
99
+ @proto.prefix
100
+ class ChildRUN(BaseContext):
101
+ prefix: str = "child"
102
+ entered: bool = False
103
+ exited: bool = False
104
+
105
+ run = ChildRUN()
106
+
107
+ with run as r:
108
+ assert r.entered
109
+
110
+ assert run.exited
111
+
112
+
113
+ class TestOtherProtocols:
114
+ """Test other Python protocol methods are preserved."""
115
+
116
+ def test_callable_protocol(self):
117
+ """Test __call__ is preserved."""
118
+
119
+ @proto.prefix
120
+ class Callable:
121
+ value: int = 0
122
+ call_count: int = 0
123
+
124
+ def __call__(self, x):
125
+ self.call_count += 1
126
+ return self.value + x
127
+
128
+ c = Callable()
129
+ c.value = 10
130
+
131
+ result = c(5)
132
+ assert result == 15
133
+ assert c.call_count == 1
134
+
135
+ def test_iterator_protocol(self):
136
+ """Test __iter__ and __next__ are preserved."""
137
+
138
+ @proto.prefix
139
+ class Counter:
140
+ start: int = 0
141
+ end: int = 3
142
+ _current: int = 0
143
+
144
+ def __iter__(self):
145
+ self._current = self.start
146
+ return self
147
+
148
+ def __next__(self):
149
+ if self._current >= self.end:
150
+ raise StopIteration
151
+ value = self._current
152
+ self._current += 1
153
+ return value
154
+
155
+ counter = Counter()
156
+ result = list(counter)
157
+ assert result == [0, 1, 2]
158
+
159
+ def test_container_protocol(self):
160
+ """Test __getitem__ and __len__ are preserved."""
161
+
162
+ @proto.prefix
163
+ class Container:
164
+ items: list = None
165
+
166
+ def __post_init__(self):
167
+ if self.items is None:
168
+ self.items = [1, 2, 3]
169
+
170
+ def __getitem__(self, index):
171
+ return self.items[index]
172
+
173
+ def __len__(self):
174
+ return len(self.items)
175
+
176
+ c = Container()
177
+ assert len(c) == 3
178
+ assert c[0] == 1
179
+ assert c[1] == 2
@@ -71,7 +71,8 @@ def test_canonical_usage():
71
71
  Nerf.lr = 0.001
72
72
 
73
73
  # main function
74
- nerf = Nerf().main()
74
+ nerf = Nerf()
75
+ nerf.main()
75
76
  assert nerf.seed == 100, "should be overriden"
76
77
  assert nerf.lr == 0.001, "should be overriden"
77
78
 
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