params-proto 3.0.0rc26__py3-none-any.whl → 3.0.0rc27__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.
params_proto/__init__.py CHANGED
@@ -1,3 +1,4 @@
1
1
  from .app import app
2
- from .proto import EnvVar, Field, get_var, proto
2
+ from .envvar import EnvVar, Field, get_var
3
+ from .proto import proto
3
4
  from .hyper import Sweep
params_proto/envvar.py CHANGED
@@ -25,24 +25,50 @@ class _EnvVar:
25
25
  db_url: str = EnvVar("DATABASE_URL", default="localhost")
26
26
  data_dir: str = EnvVar("$DATA_DIR/models", default="/tmp/models")
27
27
 
28
+ 4. OR operation with multiple env var names (tries each in order):
29
+ api_key: str = EnvVar @ "API_KEY" @ "SECRET_KEY" | "default"
30
+ # Or function syntax:
31
+ api_key: str = EnvVar("API_KEY", "SECRET_KEY", default="default")
32
+
28
33
  The pipe operator (|) allows clean chaining of env var name with fallback value.
34
+ Values are loaded lazily - environment variables are read at access time, not definition time.
29
35
  """
30
36
 
31
- def __init__(self, template: str = None, *, default: Any = None):
37
+ def __init__(self, *templates: str, default: Any = None, dtype: type = None):
32
38
  """
33
39
  Create an environment variable reader.
34
40
 
35
41
  Args:
36
- template: Environment variable name or template string (e.g., "$VAR" or "VAR")
37
- default: Default value if environment variable is not set
42
+ *templates: One or more environment variable names or template strings.
43
+ When multiple are provided, they are checked in order (OR operation).
44
+ default: Default value if no environment variable is set
45
+ dtype: Optional type to convert the value to (overrides annotation inference)
38
46
  """
39
- self.template = template
47
+ # Store templates as a tuple for immutability
48
+ self.templates = tuple(t for t in templates if t is not None)
40
49
  self.default = default
50
+ self.dtype = dtype
41
51
  self._env_name = None
52
+ # For lazy loading - cached value
53
+ self._cached_value = None
54
+ self._is_cached = False
55
+
56
+ @property
57
+ def template(self):
58
+ """Backward compatibility: return first template or None."""
59
+ return self.templates[0] if self.templates else None
42
60
 
43
61
  def __matmul__(self, other: Any):
44
62
  """
45
- Support EnvVar @ "VAR_NAME" or EnvVar @ default_value syntax.
63
+ Support EnvVar @ "VAR_NAME" syntax, chainable for OR operation.
64
+
65
+ Examples:
66
+ EnvVar @ "VAR_NAME" # Single env var
67
+ EnvVar @ "VAR1" @ "VAR2" # OR: try VAR1, then VAR2
68
+ EnvVar @ "VAR1" @ "VAR2" | default # With fallback
69
+
70
+ Note: The | operator has lower precedence than @, so
71
+ `EnvVar @ "A" @ "B" | default` is parsed as `(EnvVar @ "A" @ "B") | default`.
46
72
 
47
73
  Args:
48
74
  other: Either an environment variable name (str) or a default value
@@ -51,11 +77,11 @@ class _EnvVar:
51
77
  New _EnvVar instance configured with the given parameter
52
78
  """
53
79
  if isinstance(other, str):
54
- # EnvVar @ "VAR_NAME" - treat as env var name
55
- return _EnvVar(template=other, default=None)
80
+ # EnvVar @ "VAR_NAME" - add to templates list (OR operation)
81
+ return _EnvVar(*self.templates, other, default=self.default, dtype=self.dtype)
56
82
  else:
57
83
  # EnvVar @ some_value - treat as default value
58
- return _EnvVar(template=None, default=other)
84
+ return _EnvVar(*self.templates, default=other, dtype=self.dtype)
59
85
 
60
86
  def __or__(self, other: Any):
61
87
  """
@@ -63,46 +89,56 @@ class _EnvVar:
63
89
 
64
90
  Syntax: EnvVar @ "VAR_NAME" | default_value
65
91
 
92
+ Note: Using `or` instead of `|` will NOT work because `or` has lower
93
+ precedence than @ and evaluates truthiness instead of calling __or__.
94
+
66
95
  Args:
67
96
  other: Default value to use if env var is not set
68
97
 
69
98
  Returns:
70
- New _EnvVar instance with both template and default
99
+ New _EnvVar instance with both template(s) and default
71
100
  """
72
- return _EnvVar(template=self.template, default=other)
101
+ return _EnvVar(*self.templates, default=other, dtype=self.dtype)
73
102
 
74
- def __call__(self, template: str, *, default: Any = None):
103
+ def __call__(self, *templates: str, default: Any = None, dtype: type = None):
75
104
  """
76
- Support EnvVar("VAR_NAME", default=...) function call syntax.
105
+ Support EnvVar("VAR_NAME", ..., default=...) function call syntax.
106
+
107
+ Examples:
108
+ EnvVar("DATABASE_URL", default="localhost")
109
+ EnvVar("API_KEY", "SECRET_KEY", default="fallback") # OR operation
110
+ EnvVar("PORT", dtype=int, default=8080) # With type conversion
77
111
 
78
112
  Args:
79
- template: Environment variable name or template string
80
- default: Default value if environment variable is not set
113
+ *templates: One or more environment variable names or template strings
114
+ default: Default value if no environment variable is set
115
+ dtype: Optional type to convert the value to
81
116
 
82
117
  Returns:
83
118
  New _EnvVar instance configured with the given parameters
84
119
  """
85
- return _EnvVar(template=template, default=default)
120
+ return _EnvVar(*templates, default=default, dtype=dtype)
86
121
 
87
- def get(self) -> Any:
122
+ def _resolve_single(self, name: str) -> tuple[Any, bool]:
88
123
  """
89
- Get the value from environment variable.
124
+ Resolve a single environment variable name/template.
125
+
126
+ Args:
127
+ name: Environment variable name or template string
90
128
 
91
129
  Returns:
92
- Value from environment or default
130
+ Tuple of (value, found) where found indicates if the env var was set
93
131
  """
94
- # Use only the explicitly set template
95
- # NO auto-inference for security reasons
96
- if not self.template:
97
- return self.default
98
-
99
- name = self.template
100
-
101
132
  # Handle template strings with $ prefix or ${} syntax
102
133
  if name.startswith("$") or "${" in name:
103
134
  # Parse and expand template
104
135
  vars_in_template = parse_env_template(name)
105
136
  if vars_in_template:
137
+ # Check if any variable in template is actually set
138
+ any_set = any(var in os.environ for var in vars_in_template)
139
+ if not any_set:
140
+ return None, False
141
+
106
142
  # Expansion for both $VAR and ${VAR} syntax
107
143
  expanded = name
108
144
  for var in vars_in_template:
@@ -110,14 +146,69 @@ class _EnvVar:
110
146
  # Replace both ${VAR} and $VAR forms
111
147
  expanded = expanded.replace(f"${{{var}}}", var_value)
112
148
  expanded = expanded.replace(f"${var}", var_value)
113
- return expanded if expanded != name else self.default
149
+ return expanded, True
114
150
 
115
151
  # Simple env var lookup
116
- return os.environ.get(name, self.default)
152
+ if name in os.environ:
153
+ return os.environ[name], True
154
+ return None, False
155
+
156
+ def get(self, *, lazy: bool = True) -> Any:
157
+ """
158
+ Get the value from environment variable(s).
159
+
160
+ When multiple templates are specified (OR operation), tries each in order
161
+ and returns the first one that is set in the environment.
162
+
163
+ Args:
164
+ lazy: If True (default), cache the result for subsequent calls.
165
+ Set to False to always re-read from environment.
166
+
167
+ Returns:
168
+ Value from environment or default, converted to dtype if specified
169
+ """
170
+ from params_proto.type_utils import _convert_type
171
+
172
+ # Return cached value if lazy loading is enabled and we have a cached value
173
+ if lazy and self._is_cached:
174
+ return self._cached_value
175
+
176
+ # No templates means return default
177
+ if not self.templates:
178
+ result = self.default
179
+ else:
180
+ # Try each template in order (OR operation)
181
+ result = None
182
+ for name in self.templates:
183
+ value, found = self._resolve_single(name)
184
+ if found:
185
+ result = value
186
+ break
187
+ else:
188
+ # No env var was found, use default
189
+ result = self.default
190
+
191
+ # Apply dtype conversion if specified and we have a value
192
+ if result is not None and self.dtype is not None:
193
+ result = _convert_type(result, self.dtype)
194
+
195
+ # Cache the result for lazy loading
196
+ if lazy:
197
+ self._cached_value = result
198
+ self._is_cached = True
199
+
200
+ return result
201
+
202
+ def invalidate_cache(self):
203
+ """Clear the cached value, forcing re-read from environment on next get()."""
204
+ self._cached_value = None
205
+ self._is_cached = False
117
206
 
118
207
  def __repr__(self):
119
- if self.template:
120
- return f"EnvVar({self.template!r}, default={self.default!r})"
208
+ if self.templates:
209
+ if len(self.templates) == 1:
210
+ return f"EnvVar({self.templates[0]!r}, default={self.default!r})"
211
+ return f"EnvVar({', '.join(repr(t) for t in self.templates)}, default={self.default!r})"
121
212
  return f"EnvVar(default={self.default!r})"
122
213
 
123
214
 
params_proto/proto.py CHANGED
@@ -1117,166 +1117,4 @@ def partial(config_class: Type, method: bool = False):
1117
1117
 
1118
1118
  proto.partial = partial
1119
1119
 
1120
-
1121
- class _EnvVar:
1122
- """
1123
- Environment variable reader that supports three syntaxes:
1124
-
1125
- 1. Matmul operator with env var name:
1126
- batch_size: int = EnvVar @ "BATCH_SIZE"
1127
-
1128
- 2. Matmul operator with pipe for default:
1129
- learning_rate: float = EnvVar @ "LR" | 0.001
1130
-
1131
- 3. Function call syntax:
1132
- db_url: str = EnvVar("DATABASE_URL", default="localhost")
1133
- data_dir: str = EnvVar("$DATA_DIR/models", default="/tmp/models")
1134
- port: int = EnvVar("PORT", dtype=int, default=8080)
1135
-
1136
- The pipe operator (|) allows clean chaining of env var name with fallback value.
1137
- """
1138
-
1139
- def __init__(self, template: str = None, *, default: Any = None, dtype: type = None):
1140
- """
1141
- Create an environment variable reader.
1142
-
1143
- Args:
1144
- template: Environment variable name or template string (e.g., "$VAR" or "VAR")
1145
- default: Default value if environment variable is not set
1146
- dtype: Optional type to convert the value to (overrides annotation inference)
1147
- """
1148
- self.template = template
1149
- self.default = default
1150
- self.dtype = dtype
1151
- self._env_name = None
1152
-
1153
- def __matmul__(self, other: Any):
1154
- """
1155
- Support EnvVar @ "VAR_NAME" or EnvVar @ default_value syntax.
1156
-
1157
- Args:
1158
- other: Either an environment variable name (str) or a default value
1159
-
1160
- Returns:
1161
- New _EnvVar instance configured with the given parameter
1162
- """
1163
- if isinstance(other, str):
1164
- # EnvVar @ "VAR_NAME" - treat as env var name
1165
- return _EnvVar(template=other, default=None)
1166
- else:
1167
- # EnvVar @ some_value - treat as default value
1168
- return _EnvVar(template=None, default=other)
1169
-
1170
- def __or__(self, other: Any):
1171
- """
1172
- Support chaining with | to specify default value.
1173
-
1174
- Syntax: EnvVar @ "VAR_NAME" | default_value
1175
-
1176
- Args:
1177
- other: Default value to use if env var is not set
1178
-
1179
- Returns:
1180
- New _EnvVar instance with both template and default
1181
- """
1182
- return _EnvVar(template=self.template, default=other, dtype=self.dtype)
1183
-
1184
- def __call__(self, template: str, *, default: Any = None, dtype: type = None):
1185
- """
1186
- Support EnvVar("VAR_NAME", default=..., dtype=...) function call syntax.
1187
-
1188
- Args:
1189
- template: Environment variable name or template string
1190
- default: Default value if environment variable is not set
1191
- dtype: Optional type to convert the value to
1192
-
1193
- Returns:
1194
- New _EnvVar instance configured with the given parameters
1195
- """
1196
- return _EnvVar(template=template, default=default, dtype=dtype)
1197
-
1198
- def get(self) -> Any:
1199
- """
1200
- Get the value from environment variable.
1201
-
1202
- Returns:
1203
- Value from environment or default, converted to dtype if specified
1204
- """
1205
- import os
1206
-
1207
- from params_proto.parse_env_template import parse_env_template
1208
- from params_proto.type_utils import _convert_type
1209
-
1210
- # Use only the explicitly set template
1211
- # NO auto-inference for security reasons
1212
- if not self.template:
1213
- return self.default
1214
-
1215
- name = self.template
1216
-
1217
- # Handle template strings with $ prefix or ${} syntax
1218
- if name.startswith("$") or "${" in name:
1219
- # Parse and expand template
1220
- vars_in_template = parse_env_template(name)
1221
- if vars_in_template:
1222
- # Expansion for both $VAR and ${VAR} syntax
1223
- expanded = name
1224
- for var in vars_in_template:
1225
- var_value = os.environ.get(var, "")
1226
- # Replace both ${VAR} and $VAR forms
1227
- expanded = expanded.replace(f"${{{var}}}", var_value)
1228
- expanded = expanded.replace(f"${var}", var_value)
1229
- value = expanded if expanded != name else self.default
1230
- # Apply dtype conversion if specified
1231
- if value is not None and self.dtype is not None:
1232
- return _convert_type(value, self.dtype)
1233
- return value
1234
-
1235
- # Simple env var lookup
1236
- value = os.environ.get(name)
1237
- if value is not None:
1238
- # Apply dtype conversion if specified
1239
- if self.dtype is not None:
1240
- return _convert_type(value, self.dtype)
1241
- return value
1242
- return self.default
1243
-
1244
- def __repr__(self):
1245
- if self.template:
1246
- return f"EnvVar({self.template!r}, default={self.default!r})"
1247
- return f"EnvVar(default={self.default!r})"
1248
-
1249
-
1250
- # Create singleton instance for @ syntax, but also keep class available
1251
- EnvVar = _EnvVar()
1252
-
1253
-
1254
- def get_var(default: Any = None, *, env: str = None):
1255
- """
1256
- Mark a field as an environment variable.
1257
-
1258
- Args:
1259
- default: Default value if env var not set
1260
- env: Environment variable name (defaults to field name)
1261
- """
1262
- import os
1263
-
1264
- def getter():
1265
- env_name = env or "FIELD_NAME" # Will be replaced at decoration time
1266
- return os.environ.get(env_name, default)
1267
-
1268
- return getter
1269
-
1270
-
1271
- def Field(fn: Callable):
1272
- """
1273
- Decorator to mark a method as a computed field.
1274
-
1275
- Args:
1276
- fn: The method to decorate
1277
-
1278
- Returns:
1279
- The decorated method
1280
- """
1281
- fn._is_field = True
1282
- return fn
1120
+ # EnvVar, get_var, and Field are now imported from params_proto.envvar
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: params-proto
3
- Version: 3.0.0rc26
3
+ Version: 3.0.0rc27
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
@@ -1,9 +1,9 @@
1
- params_proto/__init__.py,sha256=3TxfXQRi2wHNfNjD-pKcPO6xyAC5j2RY24VH3oul1hg,95
1
+ params_proto/__init__.py,sha256=JJfjHf6dpkNpLDGOyH0KBWFo44nQoxSIzmwfGZb36ZU,114
2
2
  params_proto/app.py,sha256=UySpd1op3M44Szk6Ekyn0fJcnZsQvMTMPdaEybwWsLE,19
3
3
  params_proto/documentation.py,sha256=mIqmcwGWo8tM1BuNzLIwVTzdbQ3qyPus7yWTaOce4dM,8091
4
- params_proto/envvar.py,sha256=Gui4AJGPiBSDSyIC3KjmXGmewFmgihAF0QEOm-ufFVo,4323
4
+ params_proto/envvar.py,sha256=PxSgNkk-GHksMiAYhRozSjCX3aCinfaPfCpuhuEbeho,7964
5
5
  params_proto/parse_env_template.py,sha256=mXTvKpNhT2jGr3HpwKw42shd18O0QACmSJn6yWMDdKA,1298
6
- params_proto/proto.py,sha256=Q3NSmjxKZC7yKGref5F3o7KJEnALaaHWjwd1kDJRDXQ,41914
6
+ params_proto/proto.py,sha256=h-igiykTm1AwJrwUFa6rJaLZ3u4-8p6fAE7k_4hTo5w,37117
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
@@ -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.0.0rc26.dist-info/METADATA,sha256=8k3wrzgdJnM0Oh_8lw9sBJmlmKusAVZgjg3kx_WhYIw,8995
24
- params_proto-3.0.0rc26.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
25
- params_proto-3.0.0rc26.dist-info/licenses/LICENSE.md,sha256=c2qSYi9tUMZtzj9SEsMeKhub5LJUmHwBtDLiIMM5b6U,1526
26
- params_proto-3.0.0rc26.dist-info/RECORD,,
23
+ params_proto-3.0.0rc27.dist-info/METADATA,sha256=LOsTpFkjezao5QzAyBeoo0uyHTbk9hcwbjudxu5-n5Q,8995
24
+ params_proto-3.0.0rc27.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
25
+ params_proto-3.0.0rc27.dist-info/licenses/LICENSE.md,sha256=c2qSYi9tUMZtzj9SEsMeKhub5LJUmHwBtDLiIMM5b6U,1526
26
+ params_proto-3.0.0rc27.dist-info/RECORD,,