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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.108.3
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.4; extra == 'zzz-test-hypothesis'
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.4; extra == 'zzz-test-math'
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.4; extra == 'zzz-test-numpy'
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.16; extra == 'zzz-test-slack-sdk'
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=932kugeJpmqtWXsJGSf5NBQ-6ig0_Xf-TGDJSK9b28w,60
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=SjDgGG6n9m5dn10KKTyC4In7mJ5pjoOqTKJ_xJi75Ss,14031
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=vKVWIqR5JykQzPSnMHQr7_h43M6TwfYEnPmjmbgSA-o,4585
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=ENkJunh7goUT0IOQ9LFbh-AFj7plC5ePKHBsnXk6slo,52122
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.108.3.dist-info/METADATA,sha256=A5j6OCjBswwlY0SpdfckG4fb3_nZCjlgIDluLCVAdVY,13004
88
- dycw_utilities-0.108.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
89
- dycw_utilities-0.108.3.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
90
- dycw_utilities-0.108.3.dist-info/RECORD,,
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
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.108.3"
3
+ __version__ = "0.109.0"
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 TYPE_CHECKING, Any, Generic, Literal, TypeVar, overload, override
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, Mapping
31
+ from collections.abc import Callable, Iterable, Iterator
20
32
 
21
- from utilities.types import Dataclass, StrMapping, TDataclass
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: dict[str, Any] = {}
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 not self.case_sensitive:
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, get_args, override
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",