dycw-utilities 0.108.3__py3-none-any.whl → 0.109.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.
- {dycw_utilities-0.108.3.dist-info → dycw_utilities-0.109.0.dist-info}/METADATA +5 -5
- {dycw_utilities-0.108.3.dist-info → dycw_utilities-0.109.0.dist-info}/RECORD +8 -8
- utilities/__init__.py +1 -1
- utilities/dataclasses.py +166 -18
- utilities/parse.py +2 -2
- utilities/polars.py +0 -99
- {dycw_utilities-0.108.3.dist-info → dycw_utilities-0.109.0.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.108.3.dist-info → dycw_utilities-0.109.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: dycw-utilities
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.109.0
|
4
4
|
Author-email: Derek Wan <d.wan@icloud.com>
|
5
5
|
License-File: LICENSE
|
6
6
|
Requires-Python: >=3.12
|
@@ -82,7 +82,7 @@ Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-hypothesis'
|
|
82
82
|
Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-hypothesis'
|
83
83
|
Requires-Dist: hypothesis<6.132,>=6.131.6; extra == 'zzz-test-hypothesis'
|
84
84
|
Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-hypothesis'
|
85
|
-
Requires-Dist: numpy<2.3,>=2.2.
|
85
|
+
Requires-Dist: numpy<2.3,>=2.2.5; extra == 'zzz-test-hypothesis'
|
86
86
|
Requires-Dist: pathvalidate<3.3,>=3.2.3; extra == 'zzz-test-hypothesis'
|
87
87
|
Requires-Dist: redis<5.3,>=5.2.1; extra == 'zzz-test-hypothesis'
|
88
88
|
Requires-Dist: sqlalchemy<2.1,>=2.0.40; extra == 'zzz-test-hypothesis'
|
@@ -112,14 +112,14 @@ Provides-Extra: zzz-test-luigi
|
|
112
112
|
Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-luigi'
|
113
113
|
Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-luigi'
|
114
114
|
Provides-Extra: zzz-test-math
|
115
|
-
Requires-Dist: numpy<2.3,>=2.2.
|
115
|
+
Requires-Dist: numpy<2.3,>=2.2.5; extra == 'zzz-test-math'
|
116
116
|
Provides-Extra: zzz-test-memory-profiler
|
117
117
|
Requires-Dist: memory-profiler<0.62,>=0.61.0; extra == 'zzz-test-memory-profiler'
|
118
118
|
Provides-Extra: zzz-test-modules
|
119
119
|
Provides-Extra: zzz-test-more-itertools
|
120
120
|
Requires-Dist: more-itertools<10.7,>=10.6.0; extra == 'zzz-test-more-itertools'
|
121
121
|
Provides-Extra: zzz-test-numpy
|
122
|
-
Requires-Dist: numpy<2.3,>=2.2.
|
122
|
+
Requires-Dist: numpy<2.3,>=2.2.5; extra == 'zzz-test-numpy'
|
123
123
|
Provides-Extra: zzz-test-operator
|
124
124
|
Requires-Dist: polars-lts-cpu<1.28,>=1.27.1; extra == 'zzz-test-operator'
|
125
125
|
Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-operator'
|
@@ -177,7 +177,7 @@ Requires-Dist: scipy<1.16,>=1.15.2; extra == 'zzz-test-scipy'
|
|
177
177
|
Provides-Extra: zzz-test-sentinel
|
178
178
|
Provides-Extra: zzz-test-shelve
|
179
179
|
Provides-Extra: zzz-test-slack-sdk
|
180
|
-
Requires-Dist: aiohttp<3.12,>=3.11.
|
180
|
+
Requires-Dist: aiohttp<3.12,>=3.11.17; extra == 'zzz-test-slack-sdk'
|
181
181
|
Requires-Dist: slack-sdk<3.36,>=3.35.0; extra == 'zzz-test-slack-sdk'
|
182
182
|
Provides-Extra: zzz-test-socket
|
183
183
|
Provides-Extra: zzz-test-sqlalchemy
|
@@ -1,4 +1,4 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=us3aLi645m07ZoZciss9gQZW8TJND1cejqEh_im4aL8,60
|
2
2
|
utilities/altair.py,sha256=NSyDsm8QlkAGmsGdxVwCkHnPxt_35yJBa9Lg7bz9Ays,9054
|
3
3
|
utilities/astor.py,sha256=xuDUkjq0-b6fhtwjhbnebzbqQZAjMSHR1IIS5uOodVg,777
|
4
4
|
utilities/asyncio.py,sha256=41oQUurWMvadFK5gFnaG21hMM0Vmfn2WS6OpC0R9mas,14757
|
@@ -11,7 +11,7 @@ utilities/contextlib.py,sha256=OOIIEa5lXKGzFAnauaul40nlQnQko6Na4ryiMJcHkIg,478
|
|
11
11
|
utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
|
12
12
|
utilities/cryptography.py,sha256=HyOewI20cl3uRXsKivhIaeLVDInQdzgXZGaly7hS5dE,771
|
13
13
|
utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
|
14
|
-
utilities/dataclasses.py,sha256=
|
14
|
+
utilities/dataclasses.py,sha256=SOWaLVMK6p_HTMPhiXtgCk5F1KGiY8wNxQkYRBz1dVg,18683
|
15
15
|
utilities/datetime.py,sha256=GOs-MIEW_A49kzqa1yhIoeNeSqqPVgGO-h2AThtgTDk,37326
|
16
16
|
utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
|
17
17
|
utilities/errors.py,sha256=BtSNP0JC3ik536ddPyTerLomCRJV9f6kdMe6POz0QHM,361
|
@@ -40,12 +40,12 @@ utilities/operator.py,sha256=0M2yZJ0PODH47ogFEnkGMBe_cfxwZR02T_92LZVZvHo,3715
|
|
40
40
|
utilities/optuna.py,sha256=loyJGWTzljgdJaoLhP09PT8Jz6o_pwBOwehY33lHkhw,1923
|
41
41
|
utilities/orjson.py,sha256=DW5pOpMyrR5Q8caQYly9AqRPazDBqrWv5GRWfULqka4,36291
|
42
42
|
utilities/os.py,sha256=D_FyyT-6TtqiN9KSS7c9g1fnUtgxmyMtzAjmYLkk46A,3587
|
43
|
-
utilities/parse.py,sha256=
|
43
|
+
utilities/parse.py,sha256=yLLH51VNwmcWbEvwqh6M-weWt7NIayd7No67Oe80S3k,4585
|
44
44
|
utilities/pathlib.py,sha256=31WPMXdLIyXgYOMMl_HOI2wlo66MGSE-cgeelk-Lias,1410
|
45
45
|
utilities/period.py,sha256=ikHXsWtDLr553cfH6p9mMaiCnIAP69B7q84ckWV3HaA,10884
|
46
46
|
utilities/pickle.py,sha256=Bhvd7cZl-zQKQDFjUerqGuSKlHvnW1K2QXeU5UZibtg,657
|
47
47
|
utilities/platform.py,sha256=NU7ycTvAXAG-fdYmDXaM1m4EOml2cGiaYwaUzfzSqyU,1767
|
48
|
-
utilities/polars.py,sha256=
|
48
|
+
utilities/polars.py,sha256=ZXiHLkn6CbRh0_e0db5KRjHPU0LAedwzGno7k9fsiIo,48917
|
49
49
|
utilities/pqdm.py,sha256=foRytQybmOQ05pjt5LF7ANyzrIa--4ScDE3T2wd31a4,3118
|
50
50
|
utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
51
51
|
utilities/pydantic.py,sha256=f6qtR5mO2YMuyvNmbaEj5YeD9eGA4YYfb7Bjzh9jUs0,1845
|
@@ -84,7 +84,7 @@ utilities/warnings.py,sha256=yUgjnmkCRf6QhdyAXzl7u0qQFejhQG3PrjoSwxpbHrs,1819
|
|
84
84
|
utilities/whenever.py,sha256=5x2t47VJmJRWcd_NLFy54NkB3uom-XQYxEbLtEfL1bs,17775
|
85
85
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
86
86
|
utilities/zoneinfo.py,sha256=-DQz5a0Ikw9jfSZtL0BEQkXOMC9yGn_xiJYNCLMiqEc,1989
|
87
|
-
dycw_utilities-0.
|
88
|
-
dycw_utilities-0.
|
89
|
-
dycw_utilities-0.
|
90
|
-
dycw_utilities-0.
|
87
|
+
dycw_utilities-0.109.0.dist-info/METADATA,sha256=7Urd3hro439NaLJ2Kkzu7mJNlYt5qxb3HmbzrcZwbkQ,13004
|
88
|
+
dycw_utilities-0.109.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
89
|
+
dycw_utilities-0.109.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
90
|
+
dycw_utilities-0.109.0.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/dataclasses.py
CHANGED
@@ -1,7 +1,17 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
from collections.abc import Mapping
|
3
4
|
from dataclasses import MISSING, dataclass, field, fields, replace
|
4
|
-
from typing import
|
5
|
+
from typing import (
|
6
|
+
TYPE_CHECKING,
|
7
|
+
Any,
|
8
|
+
Generic,
|
9
|
+
Literal,
|
10
|
+
TypeVar,
|
11
|
+
assert_never,
|
12
|
+
overload,
|
13
|
+
override,
|
14
|
+
)
|
5
15
|
|
6
16
|
from utilities.errors import ImpossibleCaseError
|
7
17
|
from utilities.functions import (
|
@@ -11,14 +21,16 @@ from utilities.functions import (
|
|
11
21
|
)
|
12
22
|
from utilities.iterables import OneStrEmptyError, OneStrNonUniqueError, one_str
|
13
23
|
from utilities.operator import is_equal
|
24
|
+
from utilities.parse import ParseTextError, parse_text
|
14
25
|
from utilities.reprlib import get_repr
|
15
26
|
from utilities.sentinel import Sentinel, sentinel
|
27
|
+
from utilities.types import TDataclass
|
16
28
|
from utilities.typing import get_type_hints
|
17
29
|
|
18
30
|
if TYPE_CHECKING:
|
19
|
-
from collections.abc import Callable, Iterable, Iterator
|
31
|
+
from collections.abc import Callable, Iterable, Iterator
|
20
32
|
|
21
|
-
from utilities.types import Dataclass, StrMapping
|
33
|
+
from utilities.types import Dataclass, StrMapping
|
22
34
|
|
23
35
|
|
24
36
|
_T = TypeVar("_T")
|
@@ -118,7 +130,7 @@ def dataclass_to_dict(
|
|
118
130
|
recursive: bool = False,
|
119
131
|
) -> StrMapping:
|
120
132
|
"""Convert a dataclass to a dictionary."""
|
121
|
-
out:
|
133
|
+
out: StrMapping = {}
|
122
134
|
for fld in yield_fields(obj, globalns=globalns, localns=localns):
|
123
135
|
if fld.keep(
|
124
136
|
include=include,
|
@@ -233,9 +245,7 @@ class _MappingToDataclassEmptyError(MappingToDataclassError):
|
|
233
245
|
@override
|
234
246
|
def __str__(self) -> str:
|
235
247
|
desc = f"Mapping {get_repr(self.mapping)} does not contain {self.field!r}"
|
236
|
-
if
|
237
|
-
desc += " (modulo case)"
|
238
|
-
return desc
|
248
|
+
return desc if self.case_sensitive else f"{desc} (modulo case)"
|
239
249
|
|
240
250
|
|
241
251
|
@dataclass(kw_only=True, slots=True)
|
@@ -280,6 +290,142 @@ def replace_non_sentinel(
|
|
280
290
|
##
|
281
291
|
|
282
292
|
|
293
|
+
def text_to_dataclass(
|
294
|
+
text_or_mapping: str | Mapping[str, str],
|
295
|
+
cls: type[TDataclass],
|
296
|
+
/,
|
297
|
+
*,
|
298
|
+
globalns: StrMapping | None = None,
|
299
|
+
localns: StrMapping | None = None,
|
300
|
+
case_sensitive: bool = False,
|
301
|
+
) -> TDataclass:
|
302
|
+
"""Construct a dataclass from a string or a mapping or strings."""
|
303
|
+
fields = list(yield_fields(cls, globalns=globalns, localns=localns))
|
304
|
+
match text_or_mapping:
|
305
|
+
case str() as text:
|
306
|
+
text_mapping = _text_to_dataclass_split_text(text, cls)
|
307
|
+
case Mapping() as text_mapping:
|
308
|
+
...
|
309
|
+
case _ as never:
|
310
|
+
assert_never(never)
|
311
|
+
value_mapping = dict(
|
312
|
+
_text_to_dataclass_get_and_parse(
|
313
|
+
fields, key, value, cls, case_sensitive=case_sensitive
|
314
|
+
)
|
315
|
+
for key, value in text_mapping.items()
|
316
|
+
)
|
317
|
+
return mapping_to_dataclass(
|
318
|
+
cls,
|
319
|
+
value_mapping,
|
320
|
+
globalns=globalns,
|
321
|
+
localns=localns,
|
322
|
+
case_sensitive=case_sensitive,
|
323
|
+
)
|
324
|
+
|
325
|
+
|
326
|
+
def _text_to_dataclass_split_text(
|
327
|
+
text: str, cls: type[TDataclass], /
|
328
|
+
) -> Mapping[str, str]:
|
329
|
+
pairs = (t for t in text.split(",") if t != "")
|
330
|
+
return dict(_text_to_dataclass_split_key_value_pair(pair, cls) for pair in pairs)
|
331
|
+
|
332
|
+
|
333
|
+
def _text_to_dataclass_split_key_value_pair(
|
334
|
+
text: str, cls: type[Dataclass], /
|
335
|
+
) -> tuple[str, str]:
|
336
|
+
try:
|
337
|
+
key, value = text.split("=")
|
338
|
+
except ValueError:
|
339
|
+
raise _TextToDataClassSplitKeyValuePairError(cls=cls, text=text) from None
|
340
|
+
return key, value
|
341
|
+
|
342
|
+
|
343
|
+
def _text_to_dataclass_get_and_parse(
|
344
|
+
fields: Iterable[_YieldFieldsClass[Any]],
|
345
|
+
key: str,
|
346
|
+
value: str,
|
347
|
+
cls: type[Dataclass],
|
348
|
+
/,
|
349
|
+
*,
|
350
|
+
case_sensitive: bool = False,
|
351
|
+
) -> tuple[str, Any]:
|
352
|
+
mapping = {f.name: f for f in fields}
|
353
|
+
try:
|
354
|
+
name = one_str(mapping, key, head=True, case_sensitive=case_sensitive)
|
355
|
+
except OneStrEmptyError:
|
356
|
+
raise _TextToDataClassGetFieldEmptyError(
|
357
|
+
cls=cls, key=key, case_sensitive=case_sensitive
|
358
|
+
) from None
|
359
|
+
except OneStrNonUniqueError as error:
|
360
|
+
raise _TextToDataClassGetFieldNonUniqueError(
|
361
|
+
cls=cls,
|
362
|
+
key=key,
|
363
|
+
case_sensitive=case_sensitive,
|
364
|
+
first=error.first,
|
365
|
+
second=error.second,
|
366
|
+
) from None
|
367
|
+
field = mapping[name]
|
368
|
+
try:
|
369
|
+
parsed = parse_text(field.type_, value, case_sensitive=case_sensitive)
|
370
|
+
except ParseTextError:
|
371
|
+
raise _TextToDataClassParseValueError(
|
372
|
+
cls=cls, field=field, text=value
|
373
|
+
) from None
|
374
|
+
return key, parsed
|
375
|
+
|
376
|
+
|
377
|
+
@dataclass(kw_only=True, slots=True)
|
378
|
+
class TextToDataClassError(Exception, Generic[TDataclass]):
|
379
|
+
cls: type[TDataclass]
|
380
|
+
|
381
|
+
|
382
|
+
@dataclass(kw_only=True, slots=True)
|
383
|
+
class _TextToDataClassSplitKeyValuePairError(TextToDataClassError):
|
384
|
+
text: str
|
385
|
+
|
386
|
+
@override
|
387
|
+
def __str__(self) -> str:
|
388
|
+
return f"Unable to construct {get_class_name(self.cls)!r}; failed to split key-value pair {self.text!r}"
|
389
|
+
|
390
|
+
|
391
|
+
@dataclass(kw_only=True, slots=True)
|
392
|
+
class _TextToDataClassGetFieldEmptyError(TextToDataClassError[TDataclass]):
|
393
|
+
key: str
|
394
|
+
case_sensitive: bool = False
|
395
|
+
|
396
|
+
@override
|
397
|
+
def __str__(self) -> str:
|
398
|
+
desc = f"Dataclass {get_class_name(self.cls)!r} does not contain any field starting with {self.key!r}"
|
399
|
+
return desc if self.case_sensitive else f"{desc} (modulo case)"
|
400
|
+
|
401
|
+
|
402
|
+
@dataclass(kw_only=True, slots=True)
|
403
|
+
class _TextToDataClassGetFieldNonUniqueError(TextToDataClassError[TDataclass]):
|
404
|
+
key: str
|
405
|
+
case_sensitive: bool = False
|
406
|
+
first: str
|
407
|
+
second: str
|
408
|
+
|
409
|
+
@override
|
410
|
+
def __str__(self) -> str:
|
411
|
+
head = f"Dataclass {get_class_name(self.cls)!r} must contain exactly one field starting with {self.key!r}"
|
412
|
+
mid = "" if self.case_sensitive else " (modulo case)"
|
413
|
+
return f"{head}{mid}; got {self.first!r}, {self.second!r} and perhaps more"
|
414
|
+
|
415
|
+
|
416
|
+
@dataclass(kw_only=True, slots=True)
|
417
|
+
class _TextToDataClassParseValueError(TextToDataClassError[TDataclass]):
|
418
|
+
field: _YieldFieldsClass[Any]
|
419
|
+
text: str
|
420
|
+
|
421
|
+
@override
|
422
|
+
def __str__(self) -> str:
|
423
|
+
return f"Unable to construct {get_class_name(self.cls)!r}; unable to parse field {self.field.name!r} of type {self.field.type_!r}; got {self.text!r}"
|
424
|
+
|
425
|
+
|
426
|
+
##
|
427
|
+
|
428
|
+
|
283
429
|
@overload
|
284
430
|
def yield_fields(
|
285
431
|
obj: Dataclass,
|
@@ -346,18 +492,18 @@ def yield_fields(
|
|
346
492
|
raise YieldFieldsError(obj=obj)
|
347
493
|
|
348
494
|
|
349
|
-
@dataclass(kw_only=True, slots=True)
|
495
|
+
@dataclass(order=True, unsafe_hash=True, kw_only=True, slots=True)
|
350
496
|
class _YieldFieldsInstance(Generic[_T]):
|
351
497
|
name: str
|
352
|
-
value: _T
|
353
|
-
type_: Any
|
354
|
-
default: _T | Sentinel = sentinel
|
355
|
-
default_factory: Callable[[], _T] | Sentinel = sentinel
|
498
|
+
value: _T = field(hash=False)
|
499
|
+
type_: Any = field(hash=False)
|
500
|
+
default: _T | Sentinel = field(default=sentinel, hash=False)
|
501
|
+
default_factory: Callable[[], _T] | Sentinel = field(default=sentinel, hash=False)
|
356
502
|
repr: bool = True
|
357
503
|
hash_: bool | None = None
|
358
504
|
init: bool = True
|
359
505
|
compare: bool = True
|
360
|
-
metadata: StrMapping = field(default_factory=dict)
|
506
|
+
metadata: StrMapping = field(default_factory=dict, hash=False)
|
361
507
|
kw_only: bool | Sentinel = sentinel
|
362
508
|
|
363
509
|
def equals_default(
|
@@ -407,17 +553,17 @@ class _YieldFieldsInstance(Generic[_T]):
|
|
407
553
|
return (defaults and equal) or not equal
|
408
554
|
|
409
555
|
|
410
|
-
@dataclass(kw_only=True, slots=True)
|
556
|
+
@dataclass(order=True, unsafe_hash=True, kw_only=True, slots=True)
|
411
557
|
class _YieldFieldsClass(Generic[_T]):
|
412
558
|
name: str
|
413
|
-
type_: Any
|
414
|
-
default: _T | Sentinel = sentinel
|
415
|
-
default_factory: Callable[[], _T] | Sentinel = sentinel
|
559
|
+
type_: Any = field(hash=False)
|
560
|
+
default: _T | Sentinel = field(default=sentinel, hash=False)
|
561
|
+
default_factory: Callable[[], _T] | Sentinel = field(default=sentinel, hash=False)
|
416
562
|
repr: bool = True
|
417
563
|
hash_: bool | None = None
|
418
564
|
init: bool = True
|
419
565
|
compare: bool = True
|
420
|
-
metadata: StrMapping = field(default_factory=dict)
|
566
|
+
metadata: StrMapping = field(default_factory=dict, hash=False)
|
421
567
|
kw_only: bool | Sentinel = sentinel
|
422
568
|
|
423
569
|
|
@@ -434,10 +580,12 @@ class YieldFieldsError(Exception):
|
|
434
580
|
|
435
581
|
__all__ = [
|
436
582
|
"MappingToDataclassError",
|
583
|
+
"TextToDataClassError",
|
437
584
|
"YieldFieldsError",
|
438
585
|
"dataclass_repr",
|
439
586
|
"dataclass_to_dict",
|
440
587
|
"mapping_to_dataclass",
|
441
588
|
"replace_non_sentinel",
|
589
|
+
"text_to_dataclass",
|
442
590
|
"yield_fields",
|
443
591
|
]
|
utilities/parse.py
CHANGED
@@ -6,7 +6,7 @@ from dataclasses import dataclass
|
|
6
6
|
from enum import Enum
|
7
7
|
from pathlib import Path
|
8
8
|
from types import NoneType
|
9
|
-
from typing import Any,
|
9
|
+
from typing import Any, override
|
10
10
|
|
11
11
|
from utilities.datetime import is_subclass_date_not_datetime
|
12
12
|
from utilities.enum import ParseEnumError, parse_enum
|
@@ -14,7 +14,7 @@ from utilities.functions import is_subclass_int_not_bool
|
|
14
14
|
from utilities.iterables import one, one_str
|
15
15
|
from utilities.sentinel import ParseSentinelError, Sentinel, parse_sentinel
|
16
16
|
from utilities.text import ParseBoolError, ParseNoneError, parse_bool, parse_none
|
17
|
-
from utilities.typing import is_literal_type, is_optional_type
|
17
|
+
from utilities.typing import get_args, is_literal_type, is_optional_type
|
18
18
|
from utilities.version import ParseVersionError, Version, parse_version
|
19
19
|
|
20
20
|
|
utilities/polars.py
CHANGED
@@ -71,7 +71,6 @@ from utilities.iterables import (
|
|
71
71
|
CheckMappingsEqualError,
|
72
72
|
CheckSubSetError,
|
73
73
|
CheckSuperMappingError,
|
74
|
-
CheckSuperSetError,
|
75
74
|
OneEmptyError,
|
76
75
|
OneNonUniqueError,
|
77
76
|
always_iterable,
|
@@ -79,7 +78,6 @@ from utilities.iterables import (
|
|
79
78
|
check_mappings_equal,
|
80
79
|
check_subset,
|
81
80
|
check_supermapping,
|
82
|
-
check_superset,
|
83
81
|
is_iterable_not_str,
|
84
82
|
one,
|
85
83
|
)
|
@@ -92,7 +90,6 @@ from utilities.math import (
|
|
92
90
|
number_of_decimals,
|
93
91
|
)
|
94
92
|
from utilities.reprlib import get_repr
|
95
|
-
from utilities.sentinel import Sentinel
|
96
93
|
from utilities.types import MaybeStr, Number, WeekDay
|
97
94
|
from utilities.typing import (
|
98
95
|
get_args,
|
@@ -1484,100 +1481,6 @@ def week_num(column: IntoExprColumn, /, *, start: WeekDay = "mon") -> Expr | Ser
|
|
1484
1481
|
##
|
1485
1482
|
|
1486
1483
|
|
1487
|
-
def yield_rows_as_dataclasses(
|
1488
|
-
df: DataFrame,
|
1489
|
-
cls: type[TDataclass],
|
1490
|
-
/,
|
1491
|
-
*,
|
1492
|
-
globalns: StrMapping | None = None,
|
1493
|
-
localns: StrMapping | None = None,
|
1494
|
-
check_types: Literal["none", "first", "all"] = "first",
|
1495
|
-
) -> Iterator[TDataclass]:
|
1496
|
-
"""Yield the rows of a DataFrame as dataclasses."""
|
1497
|
-
from dacite import from_dict
|
1498
|
-
from dacite.exceptions import WrongTypeError
|
1499
|
-
|
1500
|
-
columns = df.columns
|
1501
|
-
required: set[str] = set()
|
1502
|
-
for field in yield_fields(cls, globalns=globalns, localns=localns):
|
1503
|
-
if isinstance(field.default, Sentinel) and isinstance(
|
1504
|
-
field.default_factory, Sentinel
|
1505
|
-
):
|
1506
|
-
required.add(field.name)
|
1507
|
-
try:
|
1508
|
-
check_superset(columns, required)
|
1509
|
-
except CheckSuperSetError as error:
|
1510
|
-
raise _YieldRowsAsDataClassesColumnsSuperSetError(
|
1511
|
-
df=df, cls=cls, left=error.left, right=error.right, extra=error.extra
|
1512
|
-
) from None
|
1513
|
-
rows = df.iter_rows(named=True)
|
1514
|
-
match check_types:
|
1515
|
-
case "none":
|
1516
|
-
yield from _yield_rows_as_dataclasses_no_check_types(rows, cls)
|
1517
|
-
case "first":
|
1518
|
-
try:
|
1519
|
-
first = next(rows)
|
1520
|
-
except StopIteration:
|
1521
|
-
return
|
1522
|
-
try:
|
1523
|
-
yield from_dict(cls, cast("Data", first))
|
1524
|
-
except WrongTypeError as error:
|
1525
|
-
raise _YieldRowsAsDataClassesWrongTypeError(
|
1526
|
-
df=df, cls=cls, msg=str(error)
|
1527
|
-
) from None
|
1528
|
-
yield from _yield_rows_as_dataclasses_no_check_types(rows, cls)
|
1529
|
-
case "all":
|
1530
|
-
try:
|
1531
|
-
for row in rows:
|
1532
|
-
yield from_dict(cls, cast("Data", row))
|
1533
|
-
except WrongTypeError as error:
|
1534
|
-
raise _YieldRowsAsDataClassesWrongTypeError(
|
1535
|
-
df=df, cls=cls, msg=str(error)
|
1536
|
-
) from None
|
1537
|
-
case _ as never:
|
1538
|
-
assert_never(never)
|
1539
|
-
|
1540
|
-
|
1541
|
-
def _yield_rows_as_dataclasses_no_check_types(
|
1542
|
-
rows: Iterator[dict[str, Any]], cls: type[TDataclass], /
|
1543
|
-
) -> Iterator[TDataclass]:
|
1544
|
-
"""Yield the rows of a DataFrame as dataclasses without type checking."""
|
1545
|
-
from dacite import Config, from_dict
|
1546
|
-
|
1547
|
-
config = Config(check_types=False)
|
1548
|
-
for row in rows:
|
1549
|
-
yield from_dict(cls, cast("Data", row), config=config)
|
1550
|
-
|
1551
|
-
|
1552
|
-
@dataclass(kw_only=True, slots=True)
|
1553
|
-
class YieldRowsAsDataClassesError(Exception):
|
1554
|
-
df: DataFrame
|
1555
|
-
cls: type[Dataclass]
|
1556
|
-
|
1557
|
-
|
1558
|
-
@dataclass(kw_only=True, slots=True)
|
1559
|
-
class _YieldRowsAsDataClassesColumnsSuperSetError(YieldRowsAsDataClassesError):
|
1560
|
-
left: AbstractSet[str]
|
1561
|
-
right: AbstractSet[str]
|
1562
|
-
extra: AbstractSet[str]
|
1563
|
-
|
1564
|
-
@override
|
1565
|
-
def __str__(self) -> str:
|
1566
|
-
return f"DataFrame columns {get_repr(self.left)} must be a superset of dataclass fields {get_repr(self.right)}; dataclass had extra fields {get_repr(self.extra)}."
|
1567
|
-
|
1568
|
-
|
1569
|
-
@dataclass(kw_only=True, slots=True)
|
1570
|
-
class _YieldRowsAsDataClassesWrongTypeError(YieldRowsAsDataClassesError):
|
1571
|
-
msg: str
|
1572
|
-
|
1573
|
-
@override
|
1574
|
-
def __str__(self) -> str:
|
1575
|
-
return self.msg
|
1576
|
-
|
1577
|
-
|
1578
|
-
##
|
1579
|
-
|
1580
|
-
|
1581
1484
|
@overload
|
1582
1485
|
def yield_struct_series_elements(
|
1583
1486
|
series: Series, /, *, strict: Literal[True]
|
@@ -1714,7 +1617,6 @@ __all__ = [
|
|
1714
1617
|
"IsNullStructSeriesError",
|
1715
1618
|
"SetFirstRowAsColumnsError",
|
1716
1619
|
"StructFromDataClassError",
|
1717
|
-
"YieldRowsAsDataClassesError",
|
1718
1620
|
"YieldStructSeriesElementsError",
|
1719
1621
|
"append_dataclass",
|
1720
1622
|
"are_frames_equal",
|
@@ -1749,7 +1651,6 @@ __all__ = [
|
|
1749
1651
|
"struct_from_dataclass",
|
1750
1652
|
"touch",
|
1751
1653
|
"unique_element",
|
1752
|
-
"yield_rows_as_dataclasses",
|
1753
1654
|
"yield_struct_series_dataclasses",
|
1754
1655
|
"yield_struct_series_elements",
|
1755
1656
|
"zoned_datetime",
|
File without changes
|
File without changes
|