ygg 0.1.30__py3-none-any.whl → 0.1.32__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.
- {ygg-0.1.30.dist-info → ygg-0.1.32.dist-info}/METADATA +1 -1
- ygg-0.1.32.dist-info/RECORD +60 -0
- yggdrasil/__init__.py +2 -0
- yggdrasil/databricks/__init__.py +2 -0
- yggdrasil/databricks/compute/__init__.py +2 -0
- yggdrasil/databricks/compute/cluster.py +241 -2
- yggdrasil/databricks/compute/execution_context.py +100 -11
- yggdrasil/databricks/compute/remote.py +16 -0
- yggdrasil/databricks/jobs/__init__.py +5 -0
- yggdrasil/databricks/jobs/config.py +31 -34
- yggdrasil/databricks/sql/__init__.py +2 -0
- yggdrasil/databricks/sql/engine.py +217 -36
- yggdrasil/databricks/sql/exceptions.py +1 -0
- yggdrasil/databricks/sql/statement_result.py +148 -1
- yggdrasil/databricks/sql/types.py +49 -1
- yggdrasil/databricks/workspaces/__init__.py +4 -1
- yggdrasil/databricks/workspaces/filesytem.py +344 -0
- yggdrasil/databricks/workspaces/io.py +1123 -0
- yggdrasil/databricks/workspaces/path.py +1415 -0
- yggdrasil/databricks/workspaces/path_kind.py +13 -0
- yggdrasil/databricks/workspaces/workspace.py +298 -154
- yggdrasil/dataclasses/__init__.py +2 -0
- yggdrasil/dataclasses/dataclass.py +42 -1
- yggdrasil/libs/__init__.py +2 -0
- yggdrasil/libs/databrickslib.py +9 -0
- yggdrasil/libs/extensions/__init__.py +2 -0
- yggdrasil/libs/extensions/polars_extensions.py +72 -0
- yggdrasil/libs/extensions/spark_extensions.py +116 -0
- yggdrasil/libs/pandaslib.py +7 -0
- yggdrasil/libs/polarslib.py +7 -0
- yggdrasil/libs/sparklib.py +41 -0
- yggdrasil/pyutils/__init__.py +4 -0
- yggdrasil/pyutils/callable_serde.py +106 -0
- yggdrasil/pyutils/exceptions.py +16 -0
- yggdrasil/pyutils/modules.py +44 -1
- yggdrasil/pyutils/parallel.py +29 -0
- yggdrasil/pyutils/python_env.py +301 -0
- yggdrasil/pyutils/retry.py +57 -0
- yggdrasil/requests/__init__.py +4 -0
- yggdrasil/requests/msal.py +124 -3
- yggdrasil/requests/session.py +18 -0
- yggdrasil/types/__init__.py +2 -0
- yggdrasil/types/cast/__init__.py +2 -1
- yggdrasil/types/cast/arrow_cast.py +131 -0
- yggdrasil/types/cast/cast_options.py +119 -1
- yggdrasil/types/cast/pandas_cast.py +29 -0
- yggdrasil/types/cast/polars_cast.py +47 -0
- yggdrasil/types/cast/polars_pandas_cast.py +29 -0
- yggdrasil/types/cast/registry.py +176 -0
- yggdrasil/types/cast/spark_cast.py +76 -0
- yggdrasil/types/cast/spark_pandas_cast.py +29 -0
- yggdrasil/types/cast/spark_polars_cast.py +28 -0
- yggdrasil/types/libs.py +2 -0
- yggdrasil/types/python_arrow.py +191 -0
- yggdrasil/types/python_defaults.py +73 -0
- yggdrasil/version.py +1 -0
- ygg-0.1.30.dist-info/RECORD +0 -56
- yggdrasil/databricks/workspaces/databricks_path.py +0 -784
- {ygg-0.1.30.dist-info → ygg-0.1.32.dist-info}/WHEEL +0 -0
- {ygg-0.1.30.dist-info → ygg-0.1.32.dist-info}/entry_points.txt +0 -0
- {ygg-0.1.30.dist-info → ygg-0.1.32.dist-info}/licenses/LICENSE +0 -0
- {ygg-0.1.30.dist-info → ygg-0.1.32.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Casting options for Arrow- and engine-aware conversions."""
|
|
2
|
+
|
|
1
3
|
import dataclasses
|
|
2
4
|
from typing import Optional, Union, List, Any
|
|
3
5
|
|
|
@@ -69,6 +71,22 @@ class CastOptions:
|
|
|
69
71
|
target_field: pa.Field | pa.Schema | pa.DataType | None = None,
|
|
70
72
|
**kwargs
|
|
71
73
|
):
|
|
74
|
+
"""Build a CastOptions instance with optional source/target fields.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
safe: Enable safe casting if True.
|
|
78
|
+
add_missing_columns: Add missing columns if True.
|
|
79
|
+
strict_match_names: Require exact field name matches if True.
|
|
80
|
+
allow_add_columns: Allow extra columns if True.
|
|
81
|
+
eager: Enable eager casting behavior if True.
|
|
82
|
+
datetime_patterns: Optional datetime parsing patterns.
|
|
83
|
+
source_field: Optional source Arrow field/schema/type.
|
|
84
|
+
target_field: Optional target Arrow field/schema/type.
|
|
85
|
+
**kwargs: Additional CastOptions fields.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
CastOptions instance.
|
|
89
|
+
"""
|
|
72
90
|
built = CastOptions(
|
|
73
91
|
safe=safe,
|
|
74
92
|
add_missing_columns=add_missing_columns,
|
|
@@ -169,6 +187,14 @@ class CastOptions:
|
|
|
169
187
|
return result
|
|
170
188
|
|
|
171
189
|
def check_source(self, obj: Any):
|
|
190
|
+
"""Set the source field if not already configured.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
obj: Source object to infer from.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Self.
|
|
197
|
+
"""
|
|
172
198
|
if self.source_field is not None or obj is None:
|
|
173
199
|
return self
|
|
174
200
|
|
|
@@ -177,6 +203,14 @@ class CastOptions:
|
|
|
177
203
|
return self
|
|
178
204
|
|
|
179
205
|
def need_arrow_type_cast(self, source_obj: Any):
|
|
206
|
+
"""Return True when Arrow type casting is required.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
source_obj: Source object to compare types against.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
True if Arrow type cast needed.
|
|
213
|
+
"""
|
|
180
214
|
if self.target_field is None:
|
|
181
215
|
return False
|
|
182
216
|
|
|
@@ -185,6 +219,14 @@ class CastOptions:
|
|
|
185
219
|
return self.source_field.type != self.target_field.type
|
|
186
220
|
|
|
187
221
|
def need_polars_type_cast(self, source_obj: Any):
|
|
222
|
+
"""Return True when Polars dtype casting is required.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
source_obj: Source object to compare types against.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
True if Polars type cast needed.
|
|
229
|
+
"""
|
|
188
230
|
if self.target_polars_field is None:
|
|
189
231
|
return False
|
|
190
232
|
|
|
@@ -193,6 +235,14 @@ class CastOptions:
|
|
|
193
235
|
return self.source_polars_field.dtype != self.target_polars_field.dtype
|
|
194
236
|
|
|
195
237
|
def need_spark_type_cast(self, source_obj: Any):
|
|
238
|
+
"""Return True when Spark datatype casting is required.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
source_obj: Source object to compare types against.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
True if Spark type cast needed.
|
|
245
|
+
"""
|
|
196
246
|
if self.target_spark_field is None:
|
|
197
247
|
return False
|
|
198
248
|
|
|
@@ -201,6 +251,14 @@ class CastOptions:
|
|
|
201
251
|
return self.source_spark_field.dataType != self.target_spark_field.dataType
|
|
202
252
|
|
|
203
253
|
def need_nullability_check(self, source_obj: Any):
|
|
254
|
+
"""Return True when nullability checks are required.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
source_obj: Source object to compare nullability against.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
True if nullability check needed.
|
|
261
|
+
"""
|
|
204
262
|
if self.target_field is None:
|
|
205
263
|
return False
|
|
206
264
|
|
|
@@ -213,6 +271,15 @@ class CastOptions:
|
|
|
213
271
|
arrow_field: pa.Field,
|
|
214
272
|
index: int
|
|
215
273
|
):
|
|
274
|
+
"""Return a child Arrow field by index for nested types.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
arrow_field: Parent Arrow field.
|
|
278
|
+
index: Child index.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Child Arrow field.
|
|
282
|
+
"""
|
|
216
283
|
source_type: Union[
|
|
217
284
|
pa.DataType, pa.ListType, pa.StructType, pa.MapType
|
|
218
285
|
] = arrow_field.type
|
|
@@ -235,6 +302,11 @@ class CastOptions:
|
|
|
235
302
|
|
|
236
303
|
@property
|
|
237
304
|
def source_field(self):
|
|
305
|
+
"""Return the configured source Arrow field.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Source Arrow field.
|
|
309
|
+
"""
|
|
238
310
|
return self.source_arrow_field
|
|
239
311
|
|
|
240
312
|
@source_field.setter
|
|
@@ -248,10 +320,23 @@ class CastOptions:
|
|
|
248
320
|
object.__setattr__(self, "source_arrow_field", value)
|
|
249
321
|
|
|
250
322
|
def source_child_arrow_field(self, index: int):
|
|
323
|
+
"""Return a child source Arrow field by index.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
index: Child index.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Child Arrow field.
|
|
330
|
+
"""
|
|
251
331
|
return self._child_arrow_field(self.source_arrow_field, index=index)
|
|
252
332
|
|
|
253
333
|
@property
|
|
254
334
|
def source_polars_field(self):
|
|
335
|
+
"""Return or compute the cached Polars field for the source.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Polars field or None.
|
|
339
|
+
"""
|
|
255
340
|
if self.source_arrow_field is not None and self._source_polars_field is None:
|
|
256
341
|
from ...types.cast.polars_cast import arrow_field_to_polars_field
|
|
257
342
|
|
|
@@ -260,6 +345,11 @@ class CastOptions:
|
|
|
260
345
|
|
|
261
346
|
@property
|
|
262
347
|
def source_spark_field(self):
|
|
348
|
+
"""Return or compute the cached Spark field for the source.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Spark field or None.
|
|
352
|
+
"""
|
|
263
353
|
if self.source_arrow_field is not None and self._source_spark_field is None:
|
|
264
354
|
from ...types.cast.spark_cast import arrow_field_to_spark_field
|
|
265
355
|
|
|
@@ -275,6 +365,11 @@ class CastOptions:
|
|
|
275
365
|
|
|
276
366
|
@property
|
|
277
367
|
def target_field_name(self):
|
|
368
|
+
"""Return the effective target field name.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
Target field name or None.
|
|
372
|
+
"""
|
|
278
373
|
if self.target_field is None:
|
|
279
374
|
if self.source_field is not None:
|
|
280
375
|
return self.source_field.name
|
|
@@ -295,10 +390,23 @@ class CastOptions:
|
|
|
295
390
|
object.__setattr__(self, "target_arrow_field", value)
|
|
296
391
|
|
|
297
392
|
def target_child_arrow_field(self, index: int):
|
|
393
|
+
"""Return a child target Arrow field by index.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
index: Child index.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
Child Arrow field.
|
|
400
|
+
"""
|
|
298
401
|
return self._child_arrow_field(self.target_arrow_field, index=index)
|
|
299
402
|
|
|
300
403
|
@property
|
|
301
404
|
def target_polars_field(self):
|
|
405
|
+
"""Return or compute the cached Polars field for the target.
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
Polars field or None.
|
|
409
|
+
"""
|
|
302
410
|
if self.target_arrow_field is not None and self._target_polars_field is None:
|
|
303
411
|
from ...types.cast.polars_cast import arrow_field_to_polars_field
|
|
304
412
|
|
|
@@ -307,6 +415,11 @@ class CastOptions:
|
|
|
307
415
|
|
|
308
416
|
@property
|
|
309
417
|
def target_spark_field(self):
|
|
418
|
+
"""Return or compute the cached Spark field for the target.
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
Spark field or None.
|
|
422
|
+
"""
|
|
310
423
|
if self.target_arrow_field is not None and self._target_spark_field is None:
|
|
311
424
|
from ...types.cast.spark_cast import arrow_field_to_spark_field
|
|
312
425
|
|
|
@@ -329,6 +442,11 @@ class CastOptions:
|
|
|
329
442
|
|
|
330
443
|
@property
|
|
331
444
|
def target_spark_schema(self) -> Optional["pyspark.sql.types.StructType"]:
|
|
445
|
+
"""Return a Spark schema view of the target Arrow schema.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Spark StructType schema or None.
|
|
449
|
+
"""
|
|
332
450
|
arrow_schema = self.target_arrow_schema
|
|
333
451
|
|
|
334
452
|
if arrow_schema is not None:
|
|
@@ -338,4 +456,4 @@ class CastOptions:
|
|
|
338
456
|
return arrow_schema
|
|
339
457
|
|
|
340
458
|
|
|
341
|
-
DEFAULT_INSTANCE = CastOptions()
|
|
459
|
+
DEFAULT_INSTANCE = CastOptions()
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Pandas <-> Arrow casting helpers and converters."""
|
|
2
|
+
|
|
1
3
|
from typing import Optional
|
|
2
4
|
|
|
3
5
|
import pyarrow as pa
|
|
@@ -33,18 +35,45 @@ if pandas is not None:
|
|
|
33
35
|
PandasDataFrame = pandas.DataFrame
|
|
34
36
|
|
|
35
37
|
def pandas_converter(*args, **kwargs):
|
|
38
|
+
"""Return a register_converter wrapper when pandas is available.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
*args: Converter registration args.
|
|
42
|
+
**kwargs: Converter registration kwargs.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Converter decorator.
|
|
46
|
+
"""
|
|
36
47
|
return register_converter(*args, **kwargs)
|
|
37
48
|
|
|
38
49
|
else:
|
|
39
50
|
# Dummy types so annotations/decorators don't explode without pandas
|
|
40
51
|
class _PandasDummy: # pragma: no cover - only used when pandas not installed
|
|
52
|
+
"""Placeholder type for pandas symbols when pandas is unavailable."""
|
|
41
53
|
pass
|
|
42
54
|
|
|
43
55
|
PandasSeries = _PandasDummy
|
|
44
56
|
PandasDataFrame = _PandasDummy
|
|
45
57
|
|
|
46
58
|
def pandas_converter(*_args, **_kwargs): # pragma: no cover - no-op decorator
|
|
59
|
+
"""Return a no-op decorator when pandas is unavailable.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
*_args: Ignored positional args.
|
|
63
|
+
**_kwargs: Ignored keyword args.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
No-op decorator.
|
|
67
|
+
"""
|
|
47
68
|
def _decorator(func):
|
|
69
|
+
"""Return the function unchanged.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
func: Callable to return.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Unchanged callable.
|
|
76
|
+
"""
|
|
48
77
|
return func
|
|
49
78
|
|
|
50
79
|
return _decorator
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Polars <-> Arrow casting helpers and converters."""
|
|
2
|
+
|
|
1
3
|
from typing import Optional, Tuple, Union, Dict, Any
|
|
2
4
|
|
|
3
5
|
import pyarrow as pa
|
|
@@ -79,12 +81,22 @@ if polars is not None:
|
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
def polars_converter(*args, **kwargs):
|
|
84
|
+
"""Return a register_converter wrapper when polars is available.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
*args: Converter registration args.
|
|
88
|
+
**kwargs: Converter registration kwargs.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Converter decorator.
|
|
92
|
+
"""
|
|
82
93
|
return register_converter(*args, **kwargs)
|
|
83
94
|
else:
|
|
84
95
|
ARROW_TO_POLARS = {}
|
|
85
96
|
|
|
86
97
|
# Dummy types so annotations/decorators don't explode without Polars
|
|
87
98
|
class _PolarsDummy: # pragma: no cover - only used when Polars not installed
|
|
99
|
+
"""Placeholder type for polars symbols when polars is unavailable."""
|
|
88
100
|
pass
|
|
89
101
|
|
|
90
102
|
PolarsSeries = _PolarsDummy
|
|
@@ -95,7 +107,24 @@ else:
|
|
|
95
107
|
PolarsDataType = _PolarsDummy
|
|
96
108
|
|
|
97
109
|
def polars_converter(*_args, **_kwargs): # pragma: no cover - no-op decorator
|
|
110
|
+
"""Return a no-op decorator when polars is unavailable.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
*_args: Ignored positional args.
|
|
114
|
+
**_kwargs: Ignored keyword args.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
No-op decorator.
|
|
118
|
+
"""
|
|
98
119
|
def _decorator(func):
|
|
120
|
+
"""Return the function unchanged.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
func: Callable to return.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Unchanged callable.
|
|
127
|
+
"""
|
|
99
128
|
return func
|
|
100
129
|
return _decorator
|
|
101
130
|
|
|
@@ -171,6 +200,15 @@ def cast_to_list_array(
|
|
|
171
200
|
array: PolarsSeries,
|
|
172
201
|
options: Optional["CastOptions"] = None,
|
|
173
202
|
) -> PolarsSeries:
|
|
203
|
+
"""Cast a Polars list series to a target list Arrow type.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
array: Polars Series with list dtype.
|
|
207
|
+
options: Optional cast options.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Casted Polars Series.
|
|
211
|
+
"""
|
|
174
212
|
options = CastOptions.check_arg(options)
|
|
175
213
|
|
|
176
214
|
if not options.need_polars_type_cast(source_obj=array):
|
|
@@ -796,6 +834,15 @@ def polars_array_to_arrow_field(
|
|
|
796
834
|
array: Union[PolarsSeries, PolarsExpr],
|
|
797
835
|
options: Optional[CastOptions] = None
|
|
798
836
|
) -> pa.Field:
|
|
837
|
+
"""Infer an Arrow field from a Polars Series or Expr.
|
|
838
|
+
|
|
839
|
+
Args:
|
|
840
|
+
array: Polars Series or Expr.
|
|
841
|
+
options: Optional cast options.
|
|
842
|
+
|
|
843
|
+
Returns:
|
|
844
|
+
Arrow field.
|
|
845
|
+
"""
|
|
799
846
|
options = CastOptions.check_arg(options)
|
|
800
847
|
|
|
801
848
|
if options.source_arrow_field:
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Polars <-> pandas conversion helpers via Arrow."""
|
|
2
|
+
|
|
1
3
|
from typing import Optional
|
|
2
4
|
|
|
3
5
|
from .arrow_cast import CastOptions
|
|
@@ -35,18 +37,45 @@ if polars is not None and pandas is not None:
|
|
|
35
37
|
PandasDataFrame = pandas.DataFrame
|
|
36
38
|
|
|
37
39
|
def polars_pandas_converter(*args, **kwargs):
|
|
40
|
+
"""Return a register_converter wrapper when both libs are available.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
*args: Converter registration args.
|
|
44
|
+
**kwargs: Converter registration kwargs.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Converter decorator.
|
|
48
|
+
"""
|
|
38
49
|
return register_converter(*args, **kwargs)
|
|
39
50
|
|
|
40
51
|
else:
|
|
41
52
|
# Dummy stand-ins so decorators/annotations don't explode if one lib is absent
|
|
42
53
|
class _Dummy: # pragma: no cover - only used when Polars or pandas not installed
|
|
54
|
+
"""Placeholder type when Polars or pandas are unavailable."""
|
|
43
55
|
pass
|
|
44
56
|
|
|
45
57
|
PolarsDataFrame = _Dummy
|
|
46
58
|
PandasDataFrame = _Dummy
|
|
47
59
|
|
|
48
60
|
def polars_pandas_converter(*_args, **_kwargs): # pragma: no cover - no-op decorator
|
|
61
|
+
"""Return a no-op decorator when dependencies are missing.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
*_args: Ignored positional args.
|
|
65
|
+
**_kwargs: Ignored keyword args.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
No-op decorator.
|
|
69
|
+
"""
|
|
49
70
|
def _decorator(func):
|
|
71
|
+
"""Return the function unchanged.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
func: Callable to return.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Unchanged callable.
|
|
78
|
+
"""
|
|
50
79
|
return func
|
|
51
80
|
|
|
52
81
|
return _decorator
|
yggdrasil/types/cast/registry.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Type conversion registry and default converters."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
import dataclasses as _dataclasses
|
|
@@ -31,6 +33,15 @@ __all__ = [
|
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
def _identity(x, opt):
|
|
36
|
+
"""Return the input value unchanged.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
x: Value to return.
|
|
40
|
+
opt: Unused options parameter.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
The input value.
|
|
44
|
+
"""
|
|
34
45
|
return x
|
|
35
46
|
|
|
36
47
|
ReturnType = TypeVar("ReturnType")
|
|
@@ -49,6 +60,14 @@ def register_converter(
|
|
|
49
60
|
"""
|
|
50
61
|
|
|
51
62
|
def decorator(func: Callable[..., ReturnType]) -> Converter:
|
|
63
|
+
"""Validate and register a converter function.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
func: Converter function to register.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Registered converter.
|
|
70
|
+
"""
|
|
52
71
|
sig = inspect.signature(func)
|
|
53
72
|
params = list(sig.parameters.values())
|
|
54
73
|
if any(
|
|
@@ -75,6 +94,14 @@ def register_converter(
|
|
|
75
94
|
|
|
76
95
|
|
|
77
96
|
def _unwrap_optional(hint: Any) -> Tuple[bool, Any]:
|
|
97
|
+
"""Return whether a hint is Optional and the underlying hint.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
hint: Type hint to inspect.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Tuple of (is_optional, base_hint).
|
|
104
|
+
"""
|
|
78
105
|
origin = get_origin(hint)
|
|
79
106
|
if origin in {Union, types.UnionType}:
|
|
80
107
|
args = get_args(hint)
|
|
@@ -114,6 +141,15 @@ def find_converter(
|
|
|
114
141
|
from_type: Any,
|
|
115
142
|
to_hint: Any
|
|
116
143
|
) -> Optional[Converter]:
|
|
144
|
+
"""Find a registered converter for the requested type pair.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
from_type: Source type.
|
|
148
|
+
to_hint: Target type hint.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Converter function or None.
|
|
152
|
+
"""
|
|
117
153
|
|
|
118
154
|
# 0) Fast path: exact key
|
|
119
155
|
conv = _registry.get((from_type, to_hint))
|
|
@@ -177,6 +213,17 @@ def find_converter(
|
|
|
177
213
|
|
|
178
214
|
# Build composite converter once we find the first viable chain.
|
|
179
215
|
def composed(value, options=None, _c1=conv1, _c2=conv2):
|
|
216
|
+
"""Compose two converters into one.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
value: Value to convert.
|
|
220
|
+
options: Cast options.
|
|
221
|
+
_c1: First converter.
|
|
222
|
+
_c2: Second converter.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Converted value.
|
|
226
|
+
"""
|
|
180
227
|
intermediate = _c1(value, options)
|
|
181
228
|
return _c2(intermediate, options)
|
|
182
229
|
|
|
@@ -190,6 +237,14 @@ def find_converter(
|
|
|
190
237
|
|
|
191
238
|
|
|
192
239
|
def _normalize_fractional_seconds(value: str) -> str:
|
|
240
|
+
"""Normalize fractional seconds to microsecond precision.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
value: Datetime string to normalize.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Normalized datetime string.
|
|
247
|
+
"""
|
|
193
248
|
match = re.search(r"(\.)(\d+)(?=(?:[+-]\d{2}:?\d{2})?$)", value)
|
|
194
249
|
if not match:
|
|
195
250
|
return value
|
|
@@ -201,6 +256,14 @@ def _normalize_fractional_seconds(value: str) -> str:
|
|
|
201
256
|
|
|
202
257
|
|
|
203
258
|
def is_runtime_value(x) -> bool:
|
|
259
|
+
"""Return True when x is a runtime value, not a type hint.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
x: Value or type hint to inspect.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
True if runtime value.
|
|
266
|
+
"""
|
|
204
267
|
# True for "42", [], MyClass(), etc.
|
|
205
268
|
# False for MyClass, list[int], dict[str, int], etc.
|
|
206
269
|
if inspect.isclass(x):
|
|
@@ -305,6 +368,16 @@ def convert_to_python_enum(
|
|
|
305
368
|
target_hint: type,
|
|
306
369
|
options: Optional[CastOptions] = None,
|
|
307
370
|
):
|
|
371
|
+
"""Convert values into a Python Enum member.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
value: Value to convert.
|
|
375
|
+
target_hint: Enum type.
|
|
376
|
+
options: Optional cast options.
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
Enum member.
|
|
380
|
+
"""
|
|
308
381
|
if isinstance(value, target_hint):
|
|
309
382
|
return value
|
|
310
383
|
|
|
@@ -343,6 +416,16 @@ def convert_to_python_dataclass(
|
|
|
343
416
|
target_hint: type,
|
|
344
417
|
options: Optional[CastOptions] = None,
|
|
345
418
|
):
|
|
419
|
+
"""Convert a mapping into a dataclass instance.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
value: Mapping of field values.
|
|
423
|
+
target_hint: Dataclass type.
|
|
424
|
+
options: Optional cast options.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Dataclass instance.
|
|
428
|
+
"""
|
|
346
429
|
from yggdrasil.types.python_defaults import default_scalar
|
|
347
430
|
|
|
348
431
|
if isinstance(value, target_hint):
|
|
@@ -385,6 +468,18 @@ def convert_to_python_iterable(
|
|
|
385
468
|
target_args,
|
|
386
469
|
options: Optional[CastOptions] = None,
|
|
387
470
|
):
|
|
471
|
+
"""Convert iterable-like values into typed Python collections.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
value: Iterable-like input value.
|
|
475
|
+
target_hint: Target type hint.
|
|
476
|
+
target_origin: Target container origin type.
|
|
477
|
+
target_args: Target type arguments.
|
|
478
|
+
options: Optional cast options.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
Converted iterable container.
|
|
482
|
+
"""
|
|
388
483
|
if isinstance(value, (str, bytes)):
|
|
389
484
|
raise TypeError(f"No converter registered for {type(value)} -> {target_hint}")
|
|
390
485
|
|
|
@@ -411,6 +506,15 @@ def convert_to_python_iterable(
|
|
|
411
506
|
|
|
412
507
|
@register_converter(str, int)
|
|
413
508
|
def _str_to_int(value: str, cast_options: Any) -> int:
|
|
509
|
+
"""Convert a string into an integer.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
value: String to convert.
|
|
513
|
+
cast_options: Cast options.
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
Integer value.
|
|
517
|
+
"""
|
|
414
518
|
if value == "":
|
|
415
519
|
return 0
|
|
416
520
|
return int(value)
|
|
@@ -418,6 +522,15 @@ def _str_to_int(value: str, cast_options: Any) -> int:
|
|
|
418
522
|
|
|
419
523
|
@register_converter(str, float)
|
|
420
524
|
def _str_to_float(value: str, cast_options: Any) -> float:
|
|
525
|
+
"""Convert a string into a float.
|
|
526
|
+
|
|
527
|
+
Args:
|
|
528
|
+
value: String to convert.
|
|
529
|
+
cast_options: Cast options.
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
Float value.
|
|
533
|
+
"""
|
|
421
534
|
default_value = getattr(cast_options, "default_value", None)
|
|
422
535
|
if value == "" and default_value is not None:
|
|
423
536
|
return default_value
|
|
@@ -426,6 +539,15 @@ def _str_to_float(value: str, cast_options: Any) -> float:
|
|
|
426
539
|
|
|
427
540
|
@register_converter(str, bool)
|
|
428
541
|
def _str_to_bool(value: str, cast_options: Any) -> bool:
|
|
542
|
+
"""Convert a string into a boolean.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
value: String to convert.
|
|
546
|
+
cast_options: Cast options.
|
|
547
|
+
|
|
548
|
+
Returns:
|
|
549
|
+
Boolean value.
|
|
550
|
+
"""
|
|
429
551
|
default_value = getattr(cast_options, "default_value", None)
|
|
430
552
|
if value == "" and default_value is not None:
|
|
431
553
|
return default_value
|
|
@@ -441,11 +563,29 @@ def _str_to_bool(value: str, cast_options: Any) -> bool:
|
|
|
441
563
|
|
|
442
564
|
@register_converter(str, _datetime.date)
|
|
443
565
|
def _str_to_date(value: str, cast_options: Any) -> _datetime.date:
|
|
566
|
+
"""Convert a string into a date.
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
value: String to convert.
|
|
570
|
+
cast_options: Cast options.
|
|
571
|
+
|
|
572
|
+
Returns:
|
|
573
|
+
Date value.
|
|
574
|
+
"""
|
|
444
575
|
return _str_to_datetime(value, cast_options).date()
|
|
445
576
|
|
|
446
577
|
|
|
447
578
|
@register_converter(str, _datetime.datetime)
|
|
448
579
|
def _str_to_datetime(value: str, cast_options: Any) -> _datetime.datetime:
|
|
580
|
+
"""Convert a string into a datetime.
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
value: String to convert.
|
|
584
|
+
cast_options: Cast options.
|
|
585
|
+
|
|
586
|
+
Returns:
|
|
587
|
+
Datetime value.
|
|
588
|
+
"""
|
|
449
589
|
default_value = getattr(cast_options, "default_value", None)
|
|
450
590
|
if value == "" and default_value is not None:
|
|
451
591
|
return default_value
|
|
@@ -488,6 +628,15 @@ def _str_to_datetime(value: str, cast_options: Any) -> _datetime.datetime:
|
|
|
488
628
|
|
|
489
629
|
@register_converter(str, _datetime.timedelta)
|
|
490
630
|
def _str_to_timedelta(value: str, cast_options: Any) -> _datetime.timedelta:
|
|
631
|
+
"""Convert a string into a timedelta.
|
|
632
|
+
|
|
633
|
+
Args:
|
|
634
|
+
value: String to convert.
|
|
635
|
+
cast_options: Cast options.
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
Timedelta value.
|
|
639
|
+
"""
|
|
491
640
|
default_value = getattr(cast_options, "default_value", None)
|
|
492
641
|
stripped = value.strip()
|
|
493
642
|
|
|
@@ -545,6 +694,15 @@ def _str_to_timedelta(value: str, cast_options: Any) -> _datetime.timedelta:
|
|
|
545
694
|
|
|
546
695
|
@register_converter(str, _datetime.time)
|
|
547
696
|
def _str_to_time(value: str, cast_options: Any) -> _datetime.time:
|
|
697
|
+
"""Convert a string into a time.
|
|
698
|
+
|
|
699
|
+
Args:
|
|
700
|
+
value: String to convert.
|
|
701
|
+
cast_options: Cast options.
|
|
702
|
+
|
|
703
|
+
Returns:
|
|
704
|
+
Time value.
|
|
705
|
+
"""
|
|
548
706
|
default_value = getattr(cast_options, "default_value", None)
|
|
549
707
|
if value == "" and default_value is not None:
|
|
550
708
|
return default_value
|
|
@@ -553,9 +711,27 @@ def _str_to_time(value: str, cast_options: Any) -> _datetime.time:
|
|
|
553
711
|
|
|
554
712
|
@register_converter(_datetime.datetime, _datetime.date)
|
|
555
713
|
def _datetime_to_date(value: _datetime.datetime, cast_options: Any) -> _datetime.date:
|
|
714
|
+
"""Convert a datetime into a date.
|
|
715
|
+
|
|
716
|
+
Args:
|
|
717
|
+
value: Datetime value.
|
|
718
|
+
cast_options: Cast options.
|
|
719
|
+
|
|
720
|
+
Returns:
|
|
721
|
+
Date value.
|
|
722
|
+
"""
|
|
556
723
|
return value.date()
|
|
557
724
|
|
|
558
725
|
|
|
559
726
|
@register_converter(int, str)
|
|
560
727
|
def _int_to_str(value: int, cast_options: Any) -> str:
|
|
728
|
+
"""Convert an integer into a string.
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
value: Integer to convert.
|
|
732
|
+
cast_options: Cast options.
|
|
733
|
+
|
|
734
|
+
Returns:
|
|
735
|
+
String value.
|
|
736
|
+
"""
|
|
561
737
|
return str(value)
|