provide-foundation 0.0.0.dev0__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.
- provide/__init__.py +15 -0
- provide/foundation/__init__.py +155 -0
- provide/foundation/_version.py +58 -0
- provide/foundation/cli/__init__.py +67 -0
- provide/foundation/cli/commands/__init__.py +3 -0
- provide/foundation/cli/commands/deps.py +71 -0
- provide/foundation/cli/commands/logs/__init__.py +63 -0
- provide/foundation/cli/commands/logs/generate.py +357 -0
- provide/foundation/cli/commands/logs/generate_old.py +569 -0
- provide/foundation/cli/commands/logs/query.py +174 -0
- provide/foundation/cli/commands/logs/send.py +166 -0
- provide/foundation/cli/commands/logs/tail.py +112 -0
- provide/foundation/cli/decorators.py +262 -0
- provide/foundation/cli/main.py +65 -0
- provide/foundation/cli/testing.py +220 -0
- provide/foundation/cli/utils.py +210 -0
- provide/foundation/config/__init__.py +106 -0
- provide/foundation/config/base.py +295 -0
- provide/foundation/config/env.py +369 -0
- provide/foundation/config/loader.py +311 -0
- provide/foundation/config/manager.py +387 -0
- provide/foundation/config/schema.py +284 -0
- provide/foundation/config/sync.py +281 -0
- provide/foundation/config/types.py +78 -0
- provide/foundation/config/validators.py +80 -0
- provide/foundation/console/__init__.py +29 -0
- provide/foundation/console/input.py +364 -0
- provide/foundation/console/output.py +178 -0
- provide/foundation/context/__init__.py +12 -0
- provide/foundation/context/core.py +356 -0
- provide/foundation/core.py +20 -0
- provide/foundation/crypto/__init__.py +182 -0
- provide/foundation/crypto/algorithms.py +111 -0
- provide/foundation/crypto/certificates.py +896 -0
- provide/foundation/crypto/checksums.py +301 -0
- provide/foundation/crypto/constants.py +57 -0
- provide/foundation/crypto/hashing.py +265 -0
- provide/foundation/crypto/keys.py +188 -0
- provide/foundation/crypto/signatures.py +144 -0
- provide/foundation/crypto/utils.py +164 -0
- provide/foundation/errors/__init__.py +96 -0
- provide/foundation/errors/auth.py +73 -0
- provide/foundation/errors/base.py +81 -0
- provide/foundation/errors/config.py +103 -0
- provide/foundation/errors/context.py +299 -0
- provide/foundation/errors/decorators.py +484 -0
- provide/foundation/errors/handlers.py +360 -0
- provide/foundation/errors/integration.py +105 -0
- provide/foundation/errors/platform.py +37 -0
- provide/foundation/errors/process.py +140 -0
- provide/foundation/errors/resources.py +133 -0
- provide/foundation/errors/runtime.py +160 -0
- provide/foundation/errors/safe_decorators.py +133 -0
- provide/foundation/errors/types.py +276 -0
- provide/foundation/file/__init__.py +79 -0
- provide/foundation/file/atomic.py +157 -0
- provide/foundation/file/directory.py +134 -0
- provide/foundation/file/formats.py +236 -0
- provide/foundation/file/lock.py +175 -0
- provide/foundation/file/safe.py +179 -0
- provide/foundation/file/utils.py +170 -0
- provide/foundation/hub/__init__.py +88 -0
- provide/foundation/hub/click_builder.py +310 -0
- provide/foundation/hub/commands.py +42 -0
- provide/foundation/hub/components.py +640 -0
- provide/foundation/hub/decorators.py +244 -0
- provide/foundation/hub/info.py +32 -0
- provide/foundation/hub/manager.py +446 -0
- provide/foundation/hub/registry.py +279 -0
- provide/foundation/hub/type_mapping.py +54 -0
- provide/foundation/hub/types.py +28 -0
- provide/foundation/logger/__init__.py +41 -0
- provide/foundation/logger/base.py +22 -0
- provide/foundation/logger/config/__init__.py +16 -0
- provide/foundation/logger/config/base.py +40 -0
- provide/foundation/logger/config/logging.py +394 -0
- provide/foundation/logger/config/telemetry.py +188 -0
- provide/foundation/logger/core.py +239 -0
- provide/foundation/logger/custom_processors.py +172 -0
- provide/foundation/logger/emoji/__init__.py +44 -0
- provide/foundation/logger/emoji/matrix.py +209 -0
- provide/foundation/logger/emoji/sets.py +458 -0
- provide/foundation/logger/emoji/types.py +56 -0
- provide/foundation/logger/factories.py +56 -0
- provide/foundation/logger/processors/__init__.py +13 -0
- provide/foundation/logger/processors/main.py +254 -0
- provide/foundation/logger/processors/trace.py +113 -0
- provide/foundation/logger/ratelimit/__init__.py +31 -0
- provide/foundation/logger/ratelimit/limiters.py +294 -0
- provide/foundation/logger/ratelimit/processor.py +203 -0
- provide/foundation/logger/ratelimit/queue_limiter.py +305 -0
- provide/foundation/logger/setup/__init__.py +29 -0
- provide/foundation/logger/setup/coordinator.py +138 -0
- provide/foundation/logger/setup/emoji_resolver.py +64 -0
- provide/foundation/logger/setup/processors.py +85 -0
- provide/foundation/logger/setup/testing.py +39 -0
- provide/foundation/logger/trace.py +38 -0
- provide/foundation/metrics/__init__.py +119 -0
- provide/foundation/metrics/otel.py +122 -0
- provide/foundation/metrics/simple.py +165 -0
- provide/foundation/observability/__init__.py +53 -0
- provide/foundation/observability/openobserve/__init__.py +79 -0
- provide/foundation/observability/openobserve/auth.py +72 -0
- provide/foundation/observability/openobserve/client.py +307 -0
- provide/foundation/observability/openobserve/commands.py +357 -0
- provide/foundation/observability/openobserve/exceptions.py +41 -0
- provide/foundation/observability/openobserve/formatters.py +298 -0
- provide/foundation/observability/openobserve/models.py +134 -0
- provide/foundation/observability/openobserve/otlp.py +320 -0
- provide/foundation/observability/openobserve/search.py +222 -0
- provide/foundation/observability/openobserve/streaming.py +235 -0
- provide/foundation/platform/__init__.py +44 -0
- provide/foundation/platform/detection.py +193 -0
- provide/foundation/platform/info.py +157 -0
- provide/foundation/process/__init__.py +39 -0
- provide/foundation/process/async_runner.py +373 -0
- provide/foundation/process/lifecycle.py +406 -0
- provide/foundation/process/runner.py +390 -0
- provide/foundation/setup/__init__.py +101 -0
- provide/foundation/streams/__init__.py +44 -0
- provide/foundation/streams/console.py +57 -0
- provide/foundation/streams/core.py +65 -0
- provide/foundation/streams/file.py +104 -0
- provide/foundation/testing/__init__.py +166 -0
- provide/foundation/testing/cli.py +227 -0
- provide/foundation/testing/crypto.py +163 -0
- provide/foundation/testing/fixtures.py +49 -0
- provide/foundation/testing/hub.py +23 -0
- provide/foundation/testing/logger.py +106 -0
- provide/foundation/testing/streams.py +54 -0
- provide/foundation/tracer/__init__.py +49 -0
- provide/foundation/tracer/context.py +115 -0
- provide/foundation/tracer/otel.py +135 -0
- provide/foundation/tracer/spans.py +174 -0
- provide/foundation/types.py +32 -0
- provide/foundation/utils/__init__.py +97 -0
- provide/foundation/utils/deps.py +195 -0
- provide/foundation/utils/env.py +491 -0
- provide/foundation/utils/formatting.py +483 -0
- provide/foundation/utils/parsing.py +235 -0
- provide/foundation/utils/rate_limiting.py +112 -0
- provide/foundation/utils/streams.py +67 -0
- provide/foundation/utils/timing.py +93 -0
- provide_foundation-0.0.0.dev0.dist-info/METADATA +469 -0
- provide_foundation-0.0.0.dev0.dist-info/RECORD +149 -0
- provide_foundation-0.0.0.dev0.dist-info/WHEEL +5 -0
- provide_foundation-0.0.0.dev0.dist-info/entry_points.txt +2 -0
- provide_foundation-0.0.0.dev0.dist-info/licenses/LICENSE +201 -0
- provide_foundation-0.0.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,491 @@
|
|
1
|
+
"""Environment variable utilities with type coercion and prefix support.
|
2
|
+
|
3
|
+
Provides utilities for safely reading and parsing environment variables with
|
4
|
+
automatic type detection, prefix-based namespacing, and default value handling.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import os
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Any, TypeVar, get_origin
|
10
|
+
|
11
|
+
from provide.foundation.errors.config import ValidationError
|
12
|
+
from provide.foundation.utils.parsing import parse_bool, parse_dict, parse_list
|
13
|
+
|
14
|
+
|
15
|
+
def _get_logger():
|
16
|
+
"""Get logger instance lazily to avoid circular imports."""
|
17
|
+
from provide.foundation.logger import get_logger
|
18
|
+
|
19
|
+
return get_logger(__name__)
|
20
|
+
|
21
|
+
|
22
|
+
T = TypeVar("T")
|
23
|
+
|
24
|
+
|
25
|
+
def get_bool(name: str, default: bool | None = None) -> bool | None:
|
26
|
+
"""Get boolean environment variable.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
name: Environment variable name
|
30
|
+
default: Default value if not set
|
31
|
+
|
32
|
+
Returns:
|
33
|
+
Boolean value or default
|
34
|
+
|
35
|
+
Examples:
|
36
|
+
>>> os.environ['DEBUG'] = 'true'
|
37
|
+
>>> get_bool('DEBUG')
|
38
|
+
True
|
39
|
+
>>> get_bool('MISSING', False)
|
40
|
+
False
|
41
|
+
"""
|
42
|
+
value = os.environ.get(name)
|
43
|
+
if value is None:
|
44
|
+
return default
|
45
|
+
|
46
|
+
try:
|
47
|
+
return parse_bool(value)
|
48
|
+
except ValueError as e:
|
49
|
+
raise ValidationError(
|
50
|
+
f"Invalid boolean value for {name}: {value}",
|
51
|
+
field=name,
|
52
|
+
value=value,
|
53
|
+
rule="boolean",
|
54
|
+
) from e
|
55
|
+
|
56
|
+
|
57
|
+
def get_int(name: str, default: int | None = None) -> int | None:
|
58
|
+
"""Get integer environment variable.
|
59
|
+
|
60
|
+
Args:
|
61
|
+
name: Environment variable name
|
62
|
+
default: Default value if not set
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
Integer value or default
|
66
|
+
|
67
|
+
Raises:
|
68
|
+
ValidationError: If value cannot be parsed as integer
|
69
|
+
"""
|
70
|
+
value = os.environ.get(name)
|
71
|
+
if value is None:
|
72
|
+
return default
|
73
|
+
|
74
|
+
try:
|
75
|
+
return int(value)
|
76
|
+
except ValueError as e:
|
77
|
+
raise ValidationError(
|
78
|
+
f"Invalid integer value for {name}: {value}",
|
79
|
+
field=name,
|
80
|
+
value=value,
|
81
|
+
rule="integer",
|
82
|
+
) from e
|
83
|
+
|
84
|
+
|
85
|
+
def get_float(name: str, default: float | None = None) -> float | None:
|
86
|
+
"""Get float environment variable.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
name: Environment variable name
|
90
|
+
default: Default value if not set
|
91
|
+
|
92
|
+
Returns:
|
93
|
+
Float value or default
|
94
|
+
|
95
|
+
Raises:
|
96
|
+
ValidationError: If value cannot be parsed as float
|
97
|
+
"""
|
98
|
+
value = os.environ.get(name)
|
99
|
+
if value is None:
|
100
|
+
return default
|
101
|
+
|
102
|
+
try:
|
103
|
+
return float(value)
|
104
|
+
except ValueError as e:
|
105
|
+
raise ValidationError(
|
106
|
+
f"Invalid float value for {name}: {value}",
|
107
|
+
field=name,
|
108
|
+
value=value,
|
109
|
+
rule="float",
|
110
|
+
) from e
|
111
|
+
|
112
|
+
|
113
|
+
def get_str(name: str, default: str | None = None) -> str | None:
|
114
|
+
"""Get string environment variable.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
name: Environment variable name
|
118
|
+
default: Default value if not set
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
String value or default
|
122
|
+
"""
|
123
|
+
return os.environ.get(name, default)
|
124
|
+
|
125
|
+
|
126
|
+
def get_path(name: str, default: Path | str | None = None) -> Path | None:
|
127
|
+
"""Get path environment variable.
|
128
|
+
|
129
|
+
Args:
|
130
|
+
name: Environment variable name
|
131
|
+
default: Default path if not set
|
132
|
+
|
133
|
+
Returns:
|
134
|
+
Path object or None
|
135
|
+
"""
|
136
|
+
value = os.environ.get(name)
|
137
|
+
if value is None:
|
138
|
+
if default is None:
|
139
|
+
return None
|
140
|
+
return Path(default) if not isinstance(default, Path) else default
|
141
|
+
|
142
|
+
# Expand user and environment variables
|
143
|
+
expanded = os.path.expanduser(os.path.expandvars(value))
|
144
|
+
return Path(expanded)
|
145
|
+
|
146
|
+
|
147
|
+
def get_list(
|
148
|
+
name: str, default: list[str] | None = None, separator: str = ","
|
149
|
+
) -> list[str]:
|
150
|
+
"""Get list from environment variable.
|
151
|
+
|
152
|
+
Args:
|
153
|
+
name: Environment variable name
|
154
|
+
default: Default list if not set
|
155
|
+
separator: String separator (default: comma)
|
156
|
+
|
157
|
+
Returns:
|
158
|
+
List of strings
|
159
|
+
|
160
|
+
Examples:
|
161
|
+
>>> os.environ['ITEMS'] = 'a,b,c'
|
162
|
+
>>> get_list('ITEMS')
|
163
|
+
['a', 'b', 'c']
|
164
|
+
"""
|
165
|
+
value = os.environ.get(name)
|
166
|
+
if value is None:
|
167
|
+
return default or []
|
168
|
+
|
169
|
+
# Use existing parse_list which handles empty strings and stripping
|
170
|
+
items = parse_list(value, separator=separator, strip=True)
|
171
|
+
# Filter empty strings (parse_list doesn't do this by default)
|
172
|
+
return [item for item in items if item]
|
173
|
+
|
174
|
+
|
175
|
+
def get_dict(
|
176
|
+
name: str,
|
177
|
+
default: dict[str, str] | None = None,
|
178
|
+
item_separator: str = ",",
|
179
|
+
key_value_separator: str = "=",
|
180
|
+
) -> dict[str, str]:
|
181
|
+
"""Get dictionary from environment variable.
|
182
|
+
|
183
|
+
Args:
|
184
|
+
name: Environment variable name
|
185
|
+
default: Default dict if not set
|
186
|
+
item_separator: Separator between items
|
187
|
+
key_value_separator: Separator between key and value
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
Dictionary of string key-value pairs
|
191
|
+
|
192
|
+
Examples:
|
193
|
+
>>> os.environ['CONFIG'] = 'key1=val1,key2=val2'
|
194
|
+
>>> get_dict('CONFIG')
|
195
|
+
{'key1': 'val1', 'key2': 'val2'}
|
196
|
+
"""
|
197
|
+
value = os.environ.get(name)
|
198
|
+
if value is None:
|
199
|
+
return default or {}
|
200
|
+
|
201
|
+
try:
|
202
|
+
return parse_dict(
|
203
|
+
value,
|
204
|
+
item_separator=item_separator,
|
205
|
+
key_separator=key_value_separator,
|
206
|
+
strip=True,
|
207
|
+
)
|
208
|
+
except ValueError as e:
|
209
|
+
# parse_dict raises on invalid format, log warning and return partial result
|
210
|
+
_get_logger().warning(
|
211
|
+
"Invalid dictionary format in environment variable",
|
212
|
+
var=name,
|
213
|
+
value=value,
|
214
|
+
error=str(e),
|
215
|
+
)
|
216
|
+
# Try to parse what we can, skipping invalid items
|
217
|
+
result = {}
|
218
|
+
items = value.split(item_separator)
|
219
|
+
for item in items:
|
220
|
+
item = item.strip()
|
221
|
+
if not item:
|
222
|
+
continue
|
223
|
+
if key_value_separator not in item:
|
224
|
+
continue
|
225
|
+
key, val = item.split(key_value_separator, 1)
|
226
|
+
result[key.strip()] = val.strip()
|
227
|
+
return result
|
228
|
+
|
229
|
+
|
230
|
+
def require(name: str, type_hint: type[T] | None = None) -> Any:
|
231
|
+
"""Require an environment variable to be set.
|
232
|
+
|
233
|
+
Args:
|
234
|
+
name: Environment variable name
|
235
|
+
type_hint: Optional type hint for parsing
|
236
|
+
|
237
|
+
Returns:
|
238
|
+
Parsed value
|
239
|
+
|
240
|
+
Raises:
|
241
|
+
ValidationError: If variable is not set
|
242
|
+
"""
|
243
|
+
if name not in os.environ:
|
244
|
+
raise ValidationError(
|
245
|
+
f"Required environment variable not set: {name}",
|
246
|
+
field=name,
|
247
|
+
rule="required",
|
248
|
+
)
|
249
|
+
|
250
|
+
if type_hint is None:
|
251
|
+
return os.environ[name]
|
252
|
+
|
253
|
+
# Parse based on type hint
|
254
|
+
origin = get_origin(type_hint)
|
255
|
+
if origin is None:
|
256
|
+
# Simple type
|
257
|
+
if type_hint is bool:
|
258
|
+
return get_bool(name)
|
259
|
+
elif type_hint is int:
|
260
|
+
return get_int(name)
|
261
|
+
elif type_hint is float:
|
262
|
+
return get_float(name)
|
263
|
+
elif type_hint is str:
|
264
|
+
return get_str(name)
|
265
|
+
elif type_hint is Path:
|
266
|
+
return get_path(name)
|
267
|
+
elif origin is list:
|
268
|
+
return get_list(name)
|
269
|
+
elif origin is dict:
|
270
|
+
return get_dict(name)
|
271
|
+
|
272
|
+
# Fallback to string
|
273
|
+
return os.environ[name]
|
274
|
+
|
275
|
+
|
276
|
+
class EnvPrefix:
|
277
|
+
"""Environment variable reader with prefix support.
|
278
|
+
|
279
|
+
Provides convenient access to environment variables with a common prefix,
|
280
|
+
useful for application-specific configuration namespacing.
|
281
|
+
|
282
|
+
Examples:
|
283
|
+
>>> app_env = EnvPrefix('MYAPP')
|
284
|
+
>>> app_env.get_bool('DEBUG') # Reads MYAPP_DEBUG
|
285
|
+
>>> app_env['database_url'] # Reads MYAPP_DATABASE_URL
|
286
|
+
"""
|
287
|
+
|
288
|
+
def __init__(self, prefix: str, separator: str = "_") -> None:
|
289
|
+
"""Initialize with prefix.
|
290
|
+
|
291
|
+
Args:
|
292
|
+
prefix: Prefix for all environment variables
|
293
|
+
separator: Separator between prefix and variable name
|
294
|
+
"""
|
295
|
+
self.prefix = prefix.upper()
|
296
|
+
self.separator = separator
|
297
|
+
|
298
|
+
def _make_name(self, name: str) -> str:
|
299
|
+
"""Create full environment variable name."""
|
300
|
+
# Convert to uppercase and replace common separators
|
301
|
+
name = name.upper().replace("-", "_").replace(".", "_")
|
302
|
+
return f"{self.prefix}{self.separator}{name}"
|
303
|
+
|
304
|
+
def get_bool(self, name: str, default: bool | None = None) -> bool | None:
|
305
|
+
"""Get boolean with prefix."""
|
306
|
+
return get_bool(self._make_name(name), default)
|
307
|
+
|
308
|
+
def get_int(self, name: str, default: int | None = None) -> int | None:
|
309
|
+
"""Get integer with prefix."""
|
310
|
+
return get_int(self._make_name(name), default)
|
311
|
+
|
312
|
+
def get_float(self, name: str, default: float | None = None) -> float | None:
|
313
|
+
"""Get float with prefix."""
|
314
|
+
return get_float(self._make_name(name), default)
|
315
|
+
|
316
|
+
def get_str(self, name: str, default: str | None = None) -> str | None:
|
317
|
+
"""Get string with prefix."""
|
318
|
+
return get_str(self._make_name(name), default)
|
319
|
+
|
320
|
+
def get_path(self, name: str, default: Path | str | None = None) -> Path | None:
|
321
|
+
"""Get path with prefix."""
|
322
|
+
return get_path(self._make_name(name), default)
|
323
|
+
|
324
|
+
def get_list(
|
325
|
+
self, name: str, default: list[str] | None = None, separator: str = ","
|
326
|
+
) -> list[str]:
|
327
|
+
"""Get list with prefix."""
|
328
|
+
return get_list(self._make_name(name), default, separator)
|
329
|
+
|
330
|
+
def get_dict(
|
331
|
+
self,
|
332
|
+
name: str,
|
333
|
+
default: dict[str, str] | None = None,
|
334
|
+
item_separator: str = ",",
|
335
|
+
key_value_separator: str = "=",
|
336
|
+
) -> dict[str, str]:
|
337
|
+
"""Get dictionary with prefix."""
|
338
|
+
return get_dict(
|
339
|
+
self._make_name(name), default, item_separator, key_value_separator
|
340
|
+
)
|
341
|
+
|
342
|
+
def require(self, name: str, type_hint: type[T] | None = None) -> Any:
|
343
|
+
"""Require variable with prefix."""
|
344
|
+
return require(self._make_name(name), type_hint)
|
345
|
+
|
346
|
+
def __getitem__(self, name: str) -> str | None:
|
347
|
+
"""Get environment variable using subscript notation."""
|
348
|
+
return self.get_str(name)
|
349
|
+
|
350
|
+
def __contains__(self, name: str) -> bool:
|
351
|
+
"""Check if environment variable exists."""
|
352
|
+
return self._make_name(name) in os.environ
|
353
|
+
|
354
|
+
def all_with_prefix(self) -> dict[str, str]:
|
355
|
+
"""Get all environment variables with this prefix.
|
356
|
+
|
357
|
+
Returns:
|
358
|
+
Dictionary of variable names (without prefix) to values
|
359
|
+
"""
|
360
|
+
result = {}
|
361
|
+
prefix_with_sep = f"{self.prefix}{self.separator}"
|
362
|
+
|
363
|
+
for key, value in os.environ.items():
|
364
|
+
if key.startswith(prefix_with_sep):
|
365
|
+
# Remove prefix and add to result
|
366
|
+
var_name = key[len(prefix_with_sep) :]
|
367
|
+
result[var_name] = value
|
368
|
+
|
369
|
+
return result
|
370
|
+
|
371
|
+
|
372
|
+
def parse_duration(value: str) -> int:
|
373
|
+
"""Parse duration string to seconds.
|
374
|
+
|
375
|
+
Supports formats like: 30s, 5m, 2h, 1d, 1h30m, etc.
|
376
|
+
|
377
|
+
Args:
|
378
|
+
value: Duration string
|
379
|
+
|
380
|
+
Returns:
|
381
|
+
Duration in seconds
|
382
|
+
|
383
|
+
Examples:
|
384
|
+
>>> parse_duration('30s')
|
385
|
+
30
|
386
|
+
>>> parse_duration('1h30m')
|
387
|
+
5400
|
388
|
+
>>> parse_duration('2d')
|
389
|
+
172800
|
390
|
+
"""
|
391
|
+
import re
|
392
|
+
|
393
|
+
if value.isdigit():
|
394
|
+
return int(value)
|
395
|
+
|
396
|
+
total_seconds = 0
|
397
|
+
|
398
|
+
# Pattern for duration components
|
399
|
+
pattern = r"(\d+)([dhms])"
|
400
|
+
matches = re.findall(pattern, value.lower())
|
401
|
+
|
402
|
+
if not matches:
|
403
|
+
raise ValidationError(
|
404
|
+
f"Invalid duration format: {value}", value=value, rule="duration"
|
405
|
+
)
|
406
|
+
|
407
|
+
units = {
|
408
|
+
"s": 1,
|
409
|
+
"m": 60,
|
410
|
+
"h": 3600,
|
411
|
+
"d": 86400,
|
412
|
+
}
|
413
|
+
|
414
|
+
for amount, unit in matches:
|
415
|
+
if unit in units:
|
416
|
+
total_seconds += int(amount) * units[unit]
|
417
|
+
else:
|
418
|
+
raise ValidationError(
|
419
|
+
f"Unknown duration unit: {unit}", value=value, rule="duration_unit"
|
420
|
+
)
|
421
|
+
|
422
|
+
return total_seconds
|
423
|
+
|
424
|
+
|
425
|
+
def parse_size(value: str) -> int:
|
426
|
+
"""Parse size string to bytes.
|
427
|
+
|
428
|
+
Supports formats like: 1024, 1KB, 10MB, 1.5GB, etc.
|
429
|
+
|
430
|
+
Args:
|
431
|
+
value: Size string
|
432
|
+
|
433
|
+
Returns:
|
434
|
+
Size in bytes
|
435
|
+
|
436
|
+
Examples:
|
437
|
+
>>> parse_size('1024')
|
438
|
+
1024
|
439
|
+
>>> parse_size('10MB')
|
440
|
+
10485760
|
441
|
+
>>> parse_size('1.5GB')
|
442
|
+
1610612736
|
443
|
+
"""
|
444
|
+
import re
|
445
|
+
|
446
|
+
if value.isdigit():
|
447
|
+
return int(value)
|
448
|
+
|
449
|
+
# Pattern for size with unit
|
450
|
+
pattern = r"^(\d+(?:\.\d+)?)\s*([KMGT]?B?)$"
|
451
|
+
match = re.match(pattern, value.upper())
|
452
|
+
|
453
|
+
if not match:
|
454
|
+
raise ValidationError(f"Invalid size format: {value}", value=value, rule="size")
|
455
|
+
|
456
|
+
amount = float(match.group(1))
|
457
|
+
unit = match.group(2) or "B"
|
458
|
+
|
459
|
+
units = {
|
460
|
+
"B": 1,
|
461
|
+
"KB": 1024,
|
462
|
+
"K": 1024,
|
463
|
+
"MB": 1024**2,
|
464
|
+
"M": 1024**2,
|
465
|
+
"GB": 1024**3,
|
466
|
+
"G": 1024**3,
|
467
|
+
"TB": 1024**4,
|
468
|
+
"T": 1024**4,
|
469
|
+
}
|
470
|
+
|
471
|
+
if unit not in units:
|
472
|
+
raise ValidationError(
|
473
|
+
f"Unknown size unit: {unit}", value=value, rule="size_unit"
|
474
|
+
)
|
475
|
+
|
476
|
+
return int(amount * units[unit])
|
477
|
+
|
478
|
+
|
479
|
+
__all__ = [
|
480
|
+
"EnvPrefix",
|
481
|
+
"get_bool",
|
482
|
+
"get_dict",
|
483
|
+
"get_float",
|
484
|
+
"get_int",
|
485
|
+
"get_list",
|
486
|
+
"get_path",
|
487
|
+
"get_str",
|
488
|
+
"parse_duration",
|
489
|
+
"parse_size",
|
490
|
+
"require",
|
491
|
+
]
|