params-proto 3.1.1__py3-none-any.whl → 3.2.0__py3-none-any.whl

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.
@@ -164,6 +164,23 @@ def parse_cli_args(wrapper) -> Dict[str, Any]:
164
164
  is_bool = annotation == bool
165
165
  prefix_params[kebab_key] = (singleton, param_name, annotation, is_bool)
166
166
 
167
+ # Build unprefixed union attribute map for classes NOT decorated with @proto.prefix
168
+ # Maps attr-name -> (union_param_name, attr_name_underscore)
169
+ # Classes in _SINGLETONS are @proto.prefix decorated and require prefixed attrs
170
+ unprefixed_attrs = {}
171
+ for kebab_name, (param_name, union_classes) in union_params.items():
172
+ for cls in union_classes:
173
+ # Skip if class is a @proto.prefix singleton (requires prefixed attrs)
174
+ is_prefix_class = cls in _SINGLETONS.values()
175
+ if is_prefix_class:
176
+ continue
177
+ if hasattr(cls, "__annotations__"):
178
+ for attr_name in cls.__annotations__:
179
+ kebab_attr = attr_name.replace("_", "-")
180
+ # Map to the union param (first one wins if multiple unions have same attr)
181
+ if kebab_attr not in unprefixed_attrs:
182
+ unprefixed_attrs[kebab_attr] = (param_name, attr_name)
183
+
167
184
  # Parse arguments
168
185
  result = {}
169
186
  prefix_values = {} # (singleton, param_name) -> value
@@ -336,6 +353,17 @@ def parse_cli_args(wrapper) -> Dict[str, Any]:
336
353
  i += 2
337
354
  continue
338
355
 
356
+ # Check unprefixed union attrs when cli_prefix=False
357
+ if key in unprefixed_attrs:
358
+ union_param_name, attr_name = unprefixed_attrs[key]
359
+ # Get the value
360
+ if i + 1 >= len(args):
361
+ raise SystemExit(f"error: argument --{key} requires a value")
362
+ value_str = args[i + 1]
363
+ union_attrs[(union_param_name, attr_name)] = value_str
364
+ i += 2
365
+ continue
366
+
339
367
  # Unknown argument
340
368
  raise SystemExit(f"error: unrecognized argument: {arg}")
341
369
 
@@ -143,6 +143,77 @@ class ParameterIterator:
143
143
  """Return number of configs (materializes list)."""
144
144
  return len(self.list)
145
145
 
146
+ def save(self, filename="sweep.jsonl", overwrite=True, verbose=True):
147
+ """
148
+ Save parameter configurations to JSONL file.
149
+
150
+ Args:
151
+ filename: Path to output file (str or PathLike)
152
+ overwrite: If True, overwrite existing file; if False, append
153
+ verbose: If True, print save confirmation
154
+
155
+ Example:
156
+ configs = piter @ {"lr": [0.001, 0.01]} * {"batch_size": [32, 64]}
157
+ configs.save("experiment.jsonl")
158
+ """
159
+ import json
160
+ import os
161
+ from urllib import parse
162
+
163
+ # Convert Path objects to string
164
+ filename_str = os.fspath(filename) if hasattr(os, "fspath") else str(filename)
165
+ configs = self.list
166
+
167
+ with open(filename_str, "w" if overwrite else "a+") as f:
168
+ for item in configs:
169
+ f.write(json.dumps(item) + "\n")
170
+
171
+ if verbose:
172
+ try:
173
+ from termcolor import colored as c
174
+
175
+ print(
176
+ c("saved", "blue"),
177
+ c(len(configs), "green"),
178
+ c("items to", "blue"),
179
+ filename_str,
180
+ ".",
181
+ "file://" + parse.quote(os.path.realpath(filename_str)),
182
+ )
183
+ except ImportError:
184
+ print(f"Saved {len(configs)} items to {filename_str}")
185
+
186
+ @staticmethod
187
+ def load(filename="sweep.jsonl"):
188
+ """
189
+ Load parameter configurations from JSONL file.
190
+
191
+ Args:
192
+ filename: Path to input file (str or PathLike)
193
+
194
+ Returns:
195
+ ParameterIterator with loaded configurations
196
+
197
+ Example:
198
+ configs = ParameterIterator.load("experiment.jsonl")
199
+ for config in configs:
200
+ run_experiment(**config)
201
+ """
202
+ import json
203
+ import os
204
+
205
+ # Convert Path objects to string
206
+ filename_str = os.fspath(filename) if hasattr(os, "fspath") else str(filename)
207
+
208
+ configs = []
209
+ with open(filename_str, "r") as f:
210
+ for line in f:
211
+ line = line.strip()
212
+ if line and not line.startswith("//"):
213
+ configs.append(json.loads(line))
214
+
215
+ return ParameterIterator(iter(configs))
216
+
146
217
 
147
218
  class PiterFactory:
148
219
  """
params_proto/proto.py CHANGED
@@ -669,20 +669,32 @@ class ptype(type):
669
669
 
670
670
  # Get the original class
671
671
  original_cls = type.__getattribute__(cls, "__proto_original_class__")
672
+ annotations = getattr(cls, "__proto_annotations__", {})
672
673
 
673
- # Create instance
674
- instance = object.__new__(original_cls)
674
+ # Check if this is a dataclass (has generated __init__ that accepts kwargs)
675
+ is_dataclass = hasattr(original_cls, "__dataclass_fields__")
675
676
 
676
- # Set attributes
677
- annotations = getattr(cls, "__proto_annotations__", {})
678
- for name in annotations.keys():
679
- if name in final_kwargs:
680
- setattr(instance, name, final_kwargs[name])
681
- elif hasattr(cls, "__proto_defaults__") and name in cls.__proto_defaults__:
682
- setattr(instance, name, cls.__proto_defaults__[name])
683
- else:
684
- # Required field
685
- setattr(instance, name, None)
677
+ if is_dataclass:
678
+ # For dataclasses: use the constructor directly
679
+ instance = original_cls(**final_kwargs)
680
+ else:
681
+ # For regular classes: create instance and set attributes manually
682
+ instance = object.__new__(original_cls)
683
+ for name in annotations.keys():
684
+ if name in final_kwargs:
685
+ setattr(instance, name, final_kwargs[name])
686
+ elif hasattr(cls, "__proto_defaults__") and name in cls.__proto_defaults__:
687
+ setattr(instance, name, cls.__proto_defaults__[name])
688
+ else:
689
+ # Required field
690
+ setattr(instance, name, None)
691
+ # Call __post_init__ if defined (dataclasses call it in __init__)
692
+ if hasattr(instance, '__post_init__'):
693
+ instance.__post_init__()
694
+
695
+ # Update the instance's class to the decorated class
696
+ # This allows isinstance(instance, DecoratedClass) to work
697
+ object.__setattr__(instance, "__class__", cls)
686
698
 
687
699
  # Copy methods from original class and wrap to return self
688
700
  for name in dir(original_cls):
@@ -722,10 +734,6 @@ class ptype(type):
722
734
 
723
735
  setattr(instance, name, make_wrapper(method))
724
736
 
725
- # Call __post_init__ if defined (like dataclasses)
726
- if hasattr(instance, '__post_init__'):
727
- instance.__post_init__()
728
-
729
737
  return instance
730
738
 
731
739
 
@@ -1013,6 +1021,10 @@ def cli(obj: Any = None, *, prog: str = None):
1013
1021
  """
1014
1022
  Set up an object as a CLI entry point.
1015
1023
 
1024
+ By default, subcommand attributes don't require prefix (--epochs works).
1025
+ If the subcommand class is decorated with @proto.prefix, prefix is required
1026
+ (--config.epochs).
1027
+
1016
1028
  Args:
1017
1029
  obj: The class, function, or Union type to setup as CLI.
1018
1030
  If None, returns a decorator.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: params-proto
3
- Version: 3.1.1
3
+ Version: 3.2.0
4
4
  Summary: Modern Hyper Parameter Management for Machine Learning
5
5
  Project-URL: Homepage, https://github.com/geyang/params-proto
6
6
  Project-URL: Documentation, https://params-proto.readthedocs.io
@@ -3,15 +3,15 @@ params_proto/app.py,sha256=UySpd1op3M44Szk6Ekyn0fJcnZsQvMTMPdaEybwWsLE,19
3
3
  params_proto/documentation.py,sha256=mIqmcwGWo8tM1BuNzLIwVTzdbQ3qyPus7yWTaOce4dM,8091
4
4
  params_proto/envvar.py,sha256=A87jxSAQ2tjbKLbrm96lblV90zNdtBGCSV6QRe2DrgA,8398
5
5
  params_proto/parse_env_template.py,sha256=mXTvKpNhT2jGr3HpwKw42shd18O0QACmSJn6yWMDdKA,1298
6
- params_proto/proto.py,sha256=81q5EyPYtH7uHCT0L2ydiin5Na2kfJ2VO3LmDfu8AXM,38386
6
+ params_proto/proto.py,sha256=AGUHrU0OmWxqkYvJJqR6SzQTxZz_Zis0XYYaiFentmo,39077
7
7
  params_proto/type_utils.py,sha256=x68rL5m76ZFRKsCRgH_i_4vLpt6ldWEsEAalgacFIH8,7364
8
8
  params_proto/cli/__init__.py,sha256=sLpN3GmaBqd_d0J0nvUNOeGlV74_-jQGW0nDUU34tjA,493
9
9
  params_proto/cli/ansi_help.py,sha256=-1gzbvOpi9GjPlqgiINOYQAfIstzg0-ukv1se88TYCQ,10967
10
- params_proto/cli/cli_parse.py,sha256=aRXkPdfyRFJeXb6W6NwQ1bkkkhjUQlvin1k4GaUtbFg,14848
10
+ params_proto/cli/cli_parse.py,sha256=qf9HFTOIJiIMKboqxa6sWdfIEzG9VN8JUHq05L9XDls,16126
11
11
  params_proto/cli/help_gen.py,sha256=Iv9MWC7TJT4_OUWozTfCr8-Nmp_-K8Ohoim_dtsN5AY,12921
12
12
  params_proto/hyper/__init__.py,sha256=4zMnKk9H7NPlaTTRzbL2MC7anzwkBbd2_kW51aYhCPs,157
13
13
  params_proto/hyper/proxies.py,sha256=OMiaKK-gQx-zT1xeCmZevBSDgWUwwkzz0n54A5_wC60,4492
14
- params_proto/hyper/sweep.py,sha256=F6uac1o7_zzahgvnwcuqwn-7C48Xl-RlHKsrk6wulsU,29475
14
+ params_proto/hyper/sweep.py,sha256=QUjsNdpJUoZU2WMF022LRqTh9rx5dgabpRCfs895q2Q,31444
15
15
  params_proto/v1/__init__.py,sha256=NGYZ6Iqicc5M6iyWT6N8FsD0iGLl2by5yZUIsHKhjXw,48
16
16
  params_proto/v1/hyper.py,sha256=zFzViWtSkQdqDJXuan33X2OZwKSHHY39Q5HSNPXl0iQ,2883
17
17
  params_proto/v1/params_proto.py,sha256=g2TMTG0SXyp01gsvd9EO42m28Hr2aS79xzOnMeH_WVk,8728
@@ -20,7 +20,7 @@ params_proto/v2/hyper.py,sha256=onBAkT8Ja8IkeHEOq1AwCdTuBzAnthIe766ZE0lAy-M,1146
20
20
  params_proto/v2/partial.py,sha256=_ovi4NY8goYgHurfYt1OV0E9DSMXGYucjMVIyG1Q_xc,983
21
21
  params_proto/v2/proto.py,sha256=KvinzgzwRQr2bHDNtrU7App2kgAyB-SEfBe4SNYceh0,18995
22
22
  params_proto/v2/utils.py,sha256=5EWvwboZDTsCYfzSED_J6RVFyNLIlf95nIu4p_ZSVxA,3540
23
- params_proto-3.1.1.dist-info/METADATA,sha256=Z8W21Wa1zInNKFoeY7TVXu2OQNo3TCQWjtX_RzmjgWM,8991
24
- params_proto-3.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
25
- params_proto-3.1.1.dist-info/licenses/LICENSE.md,sha256=c2qSYi9tUMZtzj9SEsMeKhub5LJUmHwBtDLiIMM5b6U,1526
26
- params_proto-3.1.1.dist-info/RECORD,,
23
+ params_proto-3.2.0.dist-info/METADATA,sha256=p5CeTi6QrS9C-ipTVzIsfQAvptl2gFLfLccdUBqsfGs,8991
24
+ params_proto-3.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
25
+ params_proto-3.2.0.dist-info/licenses/LICENSE.md,sha256=c2qSYi9tUMZtzj9SEsMeKhub5LJUmHwBtDLiIMM5b6U,1526
26
+ params_proto-3.2.0.dist-info/RECORD,,