dycw-utilities 0.108.1__py3-none-any.whl → 0.108.3__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.1.dist-info → dycw_utilities-0.108.3.dist-info}/METADATA +1 -1
- {dycw_utilities-0.108.1.dist-info → dycw_utilities-0.108.3.dist-info}/RECORD +10 -9
- utilities/__init__.py +1 -1
- utilities/functions.py +28 -0
- utilities/parse.py +129 -0
- utilities/python_dotenv.py +16 -159
- utilities/sentinel.py +28 -1
- utilities/text.py +22 -1
- {dycw_utilities-0.108.1.dist-info → dycw_utilities-0.108.3.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.108.1.dist-info → dycw_utilities-0.108.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=932kugeJpmqtWXsJGSf5NBQ-6ig0_Xf-TGDJSK9b28w,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
|
@@ -18,7 +18,7 @@ utilities/errors.py,sha256=BtSNP0JC3ik536ddPyTerLomCRJV9f6kdMe6POz0QHM,361
|
|
18
18
|
utilities/eventkit.py,sha256=bG2rjgQqPaaOEW879Pc8vOCX6zRAl1frIhB1Y6fqQXg,13149
|
19
19
|
utilities/fastapi.py,sha256=uwqOGbGwzIbP-lfm-ApG1ZEN3BA_TDsaiuTghhLmxb8,2413
|
20
20
|
utilities/fpdf2.py,sha256=zM3gwOYcAfv7P4qhbyvzPmRY4PPAiAQ-ZnPC6I9SZ1M,1832
|
21
|
-
utilities/functions.py,sha256=
|
21
|
+
utilities/functions.py,sha256=BH4F_X34tqHuk-BzG9lzooYIP1OmVKm6GQw51qqYShM,27461
|
22
22
|
utilities/functools.py,sha256=WrpHt7NLNWSUn9A1Q_ZIWlNaYZOEI4IFKyBG9HO3BC4,1643
|
23
23
|
utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
|
24
24
|
utilities/git.py,sha256=wpt5dZ5Oi5931pN24_VLZYaQOvmR0OcQuVtgHzFUN1k,2359
|
@@ -40,6 +40,7 @@ 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
44
|
utilities/pathlib.py,sha256=31WPMXdLIyXgYOMMl_HOI2wlo66MGSE-cgeelk-Lias,1410
|
44
45
|
utilities/period.py,sha256=ikHXsWtDLr553cfH6p9mMaiCnIAP69B7q84ckWV3HaA,10884
|
45
46
|
utilities/pickle.py,sha256=Bhvd7cZl-zQKQDFjUerqGuSKlHvnW1K2QXeU5UZibtg,657
|
@@ -52,14 +53,14 @@ utilities/pyinstrument.py,sha256=ROq2txPwbe2ZUuYJ2IDNbfT97lu2ca0v5_C_yn6sSlM,800
|
|
52
53
|
utilities/pyrsistent.py,sha256=TLJfiiKO4cKNU_pCoM3zDqmSM421qpuoaeaBNnyC_Ac,2489
|
53
54
|
utilities/pytest.py,sha256=85QUax4g2VBBAqAHtM9wekcSLB7_9O8AKFTaCshztL8,7989
|
54
55
|
utilities/pytest_regressions.py,sha256=Kp1NS_cyXvBFqyiF_oSzYmSJzIOdAZ0SFcSGmbL_UtI,5001
|
55
|
-
utilities/python_dotenv.py,sha256=
|
56
|
+
utilities/python_dotenv.py,sha256=7N4ZbBxXpPNttOTfg-hpaLFFWA5iJwF7tREBzUnbPOM,3415
|
56
57
|
utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
|
57
58
|
utilities/re.py,sha256=5J4d8VwIPFVrX2Eb8zfoxImDv7IwiN_U7mJ07wR2Wvs,3958
|
58
59
|
utilities/redis.py,sha256=CsDQqc9V6ASLzLQwtbQXZQEndyG9pJiCOhPlPeszt7Y,21203
|
59
60
|
utilities/reprlib.py,sha256=Re9bk3n-kC__9DxQmRlevqFA86pE6TtVfWjUgpbVOv0,1849
|
60
61
|
utilities/rich.py,sha256=t50MwwVBsoOLxzmeVFSVpjno4OW6Ufum32skXbV8-Bs,1911
|
61
62
|
utilities/scipy.py,sha256=X6ROnHwiUhAmPhM0jkfEh0-Fd9iRvwiqtCQMOLmOQF8,945
|
62
|
-
utilities/sentinel.py,sha256=
|
63
|
+
utilities/sentinel.py,sha256=0X1GWWcnPxGCo7wDgVTOfhU5iKlXdvVZudlMM0iezDw,1246
|
63
64
|
utilities/shelve.py,sha256=HZsMwK4tcIfg3sh0gApx4-yjQnrY4o3V3ZRimvRhoW0,738
|
64
65
|
utilities/slack_sdk.py,sha256=SeDNMh24IPiEBWoGMdgvrflUaFa9TGlTS03H9-NKaQw,4132
|
65
66
|
utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
|
@@ -69,7 +70,7 @@ utilities/streamlit.py,sha256=U9PJBaKP1IdSykKhPZhIzSPTZsmLsnwbEPZWzNhJPKk,2955
|
|
69
70
|
utilities/sys.py,sha256=h0Xr7Vj86wNalvwJVP1wj5Y0kD_VWm1vzuXZ_jw94mE,2743
|
70
71
|
utilities/tempfile.py,sha256=VqmZJAhTJ1OaVywFzk5eqROV8iJbW9XQ_QYAV0bpdRo,1384
|
71
72
|
utilities/tenacity.py,sha256=1PUvODiBVgeqIh7G5TRt5WWMSqjLYkEqP53itT97WQc,4914
|
72
|
-
utilities/text.py,sha256=
|
73
|
+
utilities/text.py,sha256=X_EjRQeV_PsG3oP7OiGYIyXGKWqciTnSwoKhM2tsy6M,3120
|
73
74
|
utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
74
75
|
utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
|
75
76
|
utilities/traceback.py,sha256=KwHPLdEbdj0fFhXo8MBfxcvem8A-VXYDwFMNJ6f0cTM,27328
|
@@ -83,7 +84,7 @@ utilities/warnings.py,sha256=yUgjnmkCRf6QhdyAXzl7u0qQFejhQG3PrjoSwxpbHrs,1819
|
|
83
84
|
utilities/whenever.py,sha256=5x2t47VJmJRWcd_NLFy54NkB3uom-XQYxEbLtEfL1bs,17775
|
84
85
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
85
86
|
utilities/zoneinfo.py,sha256=-DQz5a0Ikw9jfSZtL0BEQkXOMC9yGn_xiJYNCLMiqEc,1989
|
86
|
-
dycw_utilities-0.108.
|
87
|
-
dycw_utilities-0.108.
|
88
|
-
dycw_utilities-0.108.
|
89
|
-
dycw_utilities-0.108.
|
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,,
|
utilities/__init__.py
CHANGED
utilities/functions.py
CHANGED
@@ -5,6 +5,7 @@ from collections.abc import Callable, Iterable, Iterator, Sequence
|
|
5
5
|
from dataclasses import asdict, dataclass, is_dataclass
|
6
6
|
from functools import _lru_cache_wrapper, cached_property, partial, reduce, wraps
|
7
7
|
from inspect import getattr_static
|
8
|
+
from pathlib import Path
|
8
9
|
from re import findall
|
9
10
|
from types import (
|
10
11
|
BuiltinFunctionType,
|
@@ -405,6 +406,31 @@ class EnsureNumberError(Exception):
|
|
405
406
|
##
|
406
407
|
|
407
408
|
|
409
|
+
@overload
|
410
|
+
def ensure_path(obj: Any, /, *, nullable: bool) -> Path | None: ...
|
411
|
+
@overload
|
412
|
+
def ensure_path(obj: Any, /, *, nullable: Literal[False] = False) -> Path: ...
|
413
|
+
def ensure_path(obj: Any, /, *, nullable: bool = False) -> Path | None:
|
414
|
+
"""Ensure an object is a Path."""
|
415
|
+
try:
|
416
|
+
return ensure_class(obj, Path, nullable=nullable)
|
417
|
+
except EnsureClassError as error:
|
418
|
+
raise EnsurePathError(obj=error.obj, nullable=nullable) from None
|
419
|
+
|
420
|
+
|
421
|
+
@dataclass(kw_only=True, slots=True)
|
422
|
+
class EnsurePathError(Exception):
|
423
|
+
obj: Any
|
424
|
+
nullable: bool
|
425
|
+
|
426
|
+
@override
|
427
|
+
def __str__(self) -> str:
|
428
|
+
return _make_error_msg(self.obj, "a Path", nullable=self.nullable)
|
429
|
+
|
430
|
+
|
431
|
+
##
|
432
|
+
|
433
|
+
|
408
434
|
def ensure_sized(obj: Any, /) -> Sized:
|
409
435
|
"""Ensure an object is sized."""
|
410
436
|
if is_sized(obj):
|
@@ -985,6 +1011,7 @@ __all__ = [
|
|
985
1011
|
"EnsureMemberError",
|
986
1012
|
"EnsureNotNoneError",
|
987
1013
|
"EnsureNumberError",
|
1014
|
+
"EnsurePathError",
|
988
1015
|
"EnsureSizedError",
|
989
1016
|
"EnsureSizedNotStrError",
|
990
1017
|
"EnsureStrError",
|
@@ -1004,6 +1031,7 @@ __all__ = [
|
|
1004
1031
|
"ensure_member",
|
1005
1032
|
"ensure_not_none",
|
1006
1033
|
"ensure_number",
|
1034
|
+
"ensure_path",
|
1007
1035
|
"ensure_sized",
|
1008
1036
|
"ensure_sized_not_str",
|
1009
1037
|
"ensure_str",
|
utilities/parse.py
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import datetime as dt
|
4
|
+
from contextlib import suppress
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from enum import Enum
|
7
|
+
from pathlib import Path
|
8
|
+
from types import NoneType
|
9
|
+
from typing import Any, get_args, override
|
10
|
+
|
11
|
+
from utilities.datetime import is_subclass_date_not_datetime
|
12
|
+
from utilities.enum import ParseEnumError, parse_enum
|
13
|
+
from utilities.functions import is_subclass_int_not_bool
|
14
|
+
from utilities.iterables import one, one_str
|
15
|
+
from utilities.sentinel import ParseSentinelError, Sentinel, parse_sentinel
|
16
|
+
from utilities.text import ParseBoolError, ParseNoneError, parse_bool, parse_none
|
17
|
+
from utilities.typing import is_literal_type, is_optional_type
|
18
|
+
from utilities.version import ParseVersionError, Version, parse_version
|
19
|
+
|
20
|
+
|
21
|
+
def parse_text(
|
22
|
+
obj: Any, text: str, /, *, case_sensitive: bool = False, head: bool = False
|
23
|
+
) -> Any:
|
24
|
+
"""Parse text."""
|
25
|
+
if obj is None:
|
26
|
+
try:
|
27
|
+
return parse_none(text)
|
28
|
+
except ParseNoneError:
|
29
|
+
raise ParseTextError(obj=obj, text=text) from None
|
30
|
+
if isinstance(obj, type):
|
31
|
+
return _parse_text_type(obj, text, case_sensitive=case_sensitive)
|
32
|
+
if is_literal_type(obj):
|
33
|
+
return one_str(get_args(obj), text, head=head, case_sensitive=case_sensitive)
|
34
|
+
if is_optional_type(obj):
|
35
|
+
with suppress(ParseNoneError):
|
36
|
+
return parse_none(text)
|
37
|
+
inner = one(arg for arg in get_args(obj) if arg is not NoneType)
|
38
|
+
if isinstance(
|
39
|
+
inner := one(arg for arg in get_args(obj) if arg is not NoneType), type
|
40
|
+
):
|
41
|
+
try:
|
42
|
+
return _parse_text_type(inner, text, case_sensitive=case_sensitive)
|
43
|
+
except ParseTextError:
|
44
|
+
raise ParseTextError(obj=obj, text=text) from None
|
45
|
+
raise ParseTextError(obj=obj, text=text) from None
|
46
|
+
|
47
|
+
|
48
|
+
def _parse_text_type(
|
49
|
+
cls: type[Any], text: str, /, *, case_sensitive: bool = False
|
50
|
+
) -> Any:
|
51
|
+
"""Parse text."""
|
52
|
+
if issubclass(cls, NoneType):
|
53
|
+
try:
|
54
|
+
return parse_none(text)
|
55
|
+
except ParseNoneError:
|
56
|
+
raise ParseTextError(obj=cls, text=text) from None
|
57
|
+
if issubclass(cls, str):
|
58
|
+
return text
|
59
|
+
if issubclass(cls, bool):
|
60
|
+
try:
|
61
|
+
return parse_bool(text)
|
62
|
+
except ParseBoolError:
|
63
|
+
raise ParseTextError(obj=cls, text=text) from None
|
64
|
+
if is_subclass_int_not_bool(cls):
|
65
|
+
try:
|
66
|
+
return int(text)
|
67
|
+
except ValueError:
|
68
|
+
raise ParseTextError(obj=cls, text=text) from None
|
69
|
+
if issubclass(cls, float):
|
70
|
+
try:
|
71
|
+
return float(text)
|
72
|
+
except ValueError:
|
73
|
+
raise ParseTextError(obj=cls, text=text) from None
|
74
|
+
if issubclass(cls, Enum):
|
75
|
+
try:
|
76
|
+
return parse_enum(text, cls, case_sensitive=case_sensitive)
|
77
|
+
except ParseEnumError:
|
78
|
+
raise ParseTextError(obj=cls, text=text) from None
|
79
|
+
if issubclass(cls, Path):
|
80
|
+
return Path(text).expanduser()
|
81
|
+
if issubclass(cls, Sentinel):
|
82
|
+
try:
|
83
|
+
return parse_sentinel(text)
|
84
|
+
except ParseSentinelError:
|
85
|
+
raise ParseTextError(obj=cls, text=text) from None
|
86
|
+
if issubclass(cls, Version):
|
87
|
+
try:
|
88
|
+
return parse_version(text)
|
89
|
+
except ParseVersionError:
|
90
|
+
raise ParseTextError(obj=cls, text=text) from None
|
91
|
+
if is_subclass_date_not_datetime(cls):
|
92
|
+
from utilities.whenever import ParseDateError, parse_date
|
93
|
+
|
94
|
+
try:
|
95
|
+
return parse_date(text)
|
96
|
+
except ParseDateError:
|
97
|
+
raise ParseTextError(obj=cls, text=text) from None
|
98
|
+
if issubclass(cls, dt.datetime):
|
99
|
+
from utilities.whenever import ParseDateTimeError, parse_datetime
|
100
|
+
|
101
|
+
try:
|
102
|
+
return parse_datetime(text)
|
103
|
+
except ParseDateTimeError:
|
104
|
+
raise ParseTextError(obj=cls, text=text) from None
|
105
|
+
if issubclass(cls, dt.time):
|
106
|
+
from utilities.whenever import ParseTimeError, parse_time
|
107
|
+
|
108
|
+
try:
|
109
|
+
return parse_time(text)
|
110
|
+
except ParseTimeError:
|
111
|
+
raise ParseTextError(obj=cls, text=text) from None
|
112
|
+
if issubclass(cls, dt.timedelta):
|
113
|
+
from utilities.whenever import ParseTimedeltaError, parse_timedelta
|
114
|
+
|
115
|
+
try:
|
116
|
+
return parse_timedelta(text)
|
117
|
+
except ParseTimedeltaError:
|
118
|
+
raise ParseTextError(obj=cls, text=text) from None
|
119
|
+
raise ParseTextError(obj=cls, text=text) from None
|
120
|
+
|
121
|
+
|
122
|
+
@dataclass
|
123
|
+
class ParseTextError(Exception):
|
124
|
+
obj: Any
|
125
|
+
text: str
|
126
|
+
|
127
|
+
@override
|
128
|
+
def __str__(self) -> str:
|
129
|
+
return f"Unable to parse {self.obj!r}; got {self.text!r}"
|
utilities/python_dotenv.py
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import datetime as dt
|
4
3
|
from dataclasses import dataclass
|
5
|
-
from enum import Enum
|
6
4
|
from functools import partial
|
7
5
|
from os import environ
|
8
|
-
from pathlib import Path
|
9
|
-
from re import IGNORECASE, search
|
10
6
|
from typing import TYPE_CHECKING, Any, override
|
11
7
|
|
12
8
|
from dotenv import dotenv_values
|
@@ -16,16 +12,15 @@ from utilities.dataclasses import (
|
|
16
12
|
_YieldFieldsClass,
|
17
13
|
mapping_to_dataclass,
|
18
14
|
)
|
19
|
-
from utilities.enum import EnsureEnumError, ensure_enum
|
20
|
-
from utilities.functions import get_class_name
|
21
15
|
from utilities.git import get_repo_root
|
22
|
-
from utilities.iterables import MergeStrMappingsError, merge_str_mappings
|
16
|
+
from utilities.iterables import MergeStrMappingsError, merge_str_mappings
|
17
|
+
from utilities.parse import ParseTextError, parse_text
|
23
18
|
from utilities.pathlib import PWD
|
24
19
|
from utilities.reprlib import get_repr
|
25
|
-
from utilities.typing import get_args, is_literal_type, is_optional_type
|
26
20
|
|
27
21
|
if TYPE_CHECKING:
|
28
22
|
from collections.abc import Mapping
|
23
|
+
from pathlib import Path
|
29
24
|
|
30
25
|
from utilities.types import PathLike, StrMapping, TDataclass
|
31
26
|
|
@@ -65,72 +60,14 @@ def load_settings(
|
|
65
60
|
|
66
61
|
|
67
62
|
def _load_settings_post(
|
68
|
-
field: _YieldFieldsClass[Any],
|
63
|
+
field: _YieldFieldsClass[Any], text: str, /, *, path: Path, values: StrMapping
|
69
64
|
) -> Any:
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
if value == "1" or search("true", value, flags=IGNORECASE):
|
77
|
-
return True
|
78
|
-
raise _LoadSettingsInvalidBoolError(
|
79
|
-
path=path, values=values, field=field.name, value=value
|
80
|
-
)
|
81
|
-
if type_ is float:
|
82
|
-
try:
|
83
|
-
return float(value)
|
84
|
-
except ValueError:
|
85
|
-
raise _LoadSettingsInvalidFloatError(
|
86
|
-
path=path, values=values, field=field.name, value=value
|
87
|
-
) from None
|
88
|
-
if type_ is int:
|
89
|
-
try:
|
90
|
-
return int(value)
|
91
|
-
except ValueError:
|
92
|
-
raise _LoadSettingsInvalidIntError(
|
93
|
-
path=path, values=values, field=field.name, value=value
|
94
|
-
) from None
|
95
|
-
if type_ is Path:
|
96
|
-
return Path(value).expanduser()
|
97
|
-
if type_ is dt.date:
|
98
|
-
from utilities.whenever import ParseDateError, parse_date
|
99
|
-
|
100
|
-
try:
|
101
|
-
return parse_date(value)
|
102
|
-
except ParseDateError:
|
103
|
-
raise _LoadSettingsInvalidDateError(
|
104
|
-
path=path, values=values, field=field.name, value=value
|
105
|
-
) from None
|
106
|
-
if type_ is dt.timedelta:
|
107
|
-
from utilities.whenever import ParseTimedeltaError, parse_timedelta
|
108
|
-
|
109
|
-
try:
|
110
|
-
return parse_timedelta(value)
|
111
|
-
except ParseTimedeltaError:
|
112
|
-
raise _LoadSettingsInvalidTimeDeltaError(
|
113
|
-
path=path, values=values, field=field.name, value=value
|
114
|
-
) from None
|
115
|
-
if isinstance(type_, type) and issubclass(type_, Enum):
|
116
|
-
try:
|
117
|
-
return ensure_enum(value, type_)
|
118
|
-
except EnsureEnumError:
|
119
|
-
raise _LoadSettingsInvalidEnumError(
|
120
|
-
path=path, values=values, field=field.name, type_=type_, value=value
|
121
|
-
) from None
|
122
|
-
if is_literal_type(type_):
|
123
|
-
return one_str(get_args(type_), value)
|
124
|
-
if is_optional_type(type_) and (one(get_args(type_)) is int):
|
125
|
-
if (value is None) or (value == "") or search("none", value, flags=IGNORECASE):
|
126
|
-
return None
|
127
|
-
try:
|
128
|
-
return int(value)
|
129
|
-
except ValueError:
|
130
|
-
raise _LoadSettingsInvalidNullableIntError(
|
131
|
-
path=path, values=values, field=field.name, value=value
|
132
|
-
) from None
|
133
|
-
raise _LoadSettingsTypeError(path=path, field=field.name, type=type_)
|
65
|
+
try:
|
66
|
+
return parse_text(field.type_, text)
|
67
|
+
except ParseTextError:
|
68
|
+
raise _LoadSettingsParseTextError(
|
69
|
+
path=path, values=values, field=field, text=text
|
70
|
+
) from None
|
134
71
|
|
135
72
|
|
136
73
|
@dataclass(kw_only=True, slots=True)
|
@@ -138,13 +75,6 @@ class LoadSettingsError(Exception):
|
|
138
75
|
path: Path
|
139
76
|
|
140
77
|
|
141
|
-
@dataclass(kw_only=True, slots=True)
|
142
|
-
class _LoadSettingsFileNotFoundError(LoadSettingsError):
|
143
|
-
@override
|
144
|
-
def __str__(self) -> str:
|
145
|
-
return f"Path {str(self.path)!r} must exist"
|
146
|
-
|
147
|
-
|
148
78
|
@dataclass(kw_only=True, slots=True)
|
149
79
|
class _LoadSettingsDuplicateKeysError(LoadSettingsError):
|
150
80
|
values: StrMapping
|
@@ -166,94 +96,21 @@ class _LoadSettingsEmptyError(LoadSettingsError):
|
|
166
96
|
|
167
97
|
|
168
98
|
@dataclass(kw_only=True, slots=True)
|
169
|
-
class
|
170
|
-
values: StrMapping
|
171
|
-
field: str
|
172
|
-
value: str
|
173
|
-
|
174
|
-
@override
|
175
|
-
def __str__(self) -> str:
|
176
|
-
return f"Field {self.field!r} must contain a valid boolean; got {self.value!r}"
|
177
|
-
|
178
|
-
|
179
|
-
@dataclass(kw_only=True, slots=True)
|
180
|
-
class _LoadSettingsInvalidDateError(LoadSettingsError):
|
181
|
-
values: StrMapping
|
182
|
-
field: str
|
183
|
-
value: str
|
184
|
-
|
185
|
-
@override
|
186
|
-
def __str__(self) -> str:
|
187
|
-
return f"Field {self.field!r} must contain a valid date; got {self.value!r}"
|
188
|
-
|
189
|
-
|
190
|
-
@dataclass(kw_only=True, slots=True)
|
191
|
-
class _LoadSettingsInvalidEnumError(LoadSettingsError):
|
192
|
-
values: StrMapping
|
193
|
-
field: str
|
194
|
-
type_: type[Enum]
|
195
|
-
value: str
|
196
|
-
|
197
|
-
@override
|
198
|
-
def __str__(self) -> str:
|
199
|
-
type_ = get_class_name(self.type_)
|
200
|
-
return f"Field {self.field!r} must contain a valid member of {type_!r}; got {self.value!r}"
|
201
|
-
|
202
|
-
|
203
|
-
@dataclass(kw_only=True, slots=True)
|
204
|
-
class _LoadSettingsInvalidFloatError(LoadSettingsError):
|
205
|
-
values: StrMapping
|
206
|
-
field: str
|
207
|
-
value: str
|
208
|
-
|
209
|
-
@override
|
210
|
-
def __str__(self) -> str:
|
211
|
-
return f"Field {self.field!r} must contain a valid float; got {self.value!r}"
|
212
|
-
|
213
|
-
|
214
|
-
@dataclass(kw_only=True, slots=True)
|
215
|
-
class _LoadSettingsInvalidIntError(LoadSettingsError):
|
216
|
-
values: StrMapping
|
217
|
-
field: str
|
218
|
-
value: str
|
219
|
-
|
220
|
-
@override
|
221
|
-
def __str__(self) -> str:
|
222
|
-
return f"Field {self.field!r} must contain a valid integer; got {self.value!r}"
|
223
|
-
|
224
|
-
|
225
|
-
@dataclass(kw_only=True, slots=True)
|
226
|
-
class _LoadSettingsInvalidNullableIntError(LoadSettingsError):
|
227
|
-
values: StrMapping
|
228
|
-
field: str
|
229
|
-
value: str
|
230
|
-
|
99
|
+
class _LoadSettingsFileNotFoundError(LoadSettingsError):
|
231
100
|
@override
|
232
101
|
def __str__(self) -> str:
|
233
|
-
return f"
|
102
|
+
return f"Path {str(self.path)!r} must exist"
|
234
103
|
|
235
104
|
|
236
105
|
@dataclass(kw_only=True, slots=True)
|
237
|
-
class
|
106
|
+
class _LoadSettingsParseTextError(LoadSettingsError):
|
238
107
|
values: StrMapping
|
239
|
-
field:
|
240
|
-
|
241
|
-
|
242
|
-
@override
|
243
|
-
def __str__(self) -> str:
|
244
|
-
return (
|
245
|
-
f"Field {self.field!r} must contain a valid timedelta; got {self.value!r}"
|
246
|
-
)
|
247
|
-
|
248
|
-
|
249
|
-
@dataclass(kw_only=True, slots=True)
|
250
|
-
class _LoadSettingsTypeError(LoadSettingsError):
|
251
|
-
field: str
|
252
|
-
type: Any
|
108
|
+
field: _YieldFieldsClass[Any]
|
109
|
+
text: str
|
253
110
|
|
254
111
|
@override
|
255
112
|
def __str__(self) -> str:
|
256
|
-
return f"
|
113
|
+
return f"Unable to parse field {self.field.name!r} of type {self.field.type_!r}; got {self.text!r}"
|
257
114
|
|
258
115
|
|
259
116
|
__all__ = ["LoadSettingsError", "load_settings"]
|
utilities/sentinel.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from re import IGNORECASE, search
|
3
5
|
from typing import Any, override
|
4
6
|
|
5
7
|
|
@@ -33,4 +35,29 @@ class Sentinel(metaclass=_Meta):
|
|
33
35
|
sentinel = Sentinel()
|
34
36
|
|
35
37
|
|
36
|
-
|
38
|
+
##
|
39
|
+
|
40
|
+
|
41
|
+
def parse_sentinel(text: str, /) -> Sentinel:
|
42
|
+
"""Parse text into the Sentinel value."""
|
43
|
+
if text == "" or search("Sentinel", text, flags=IGNORECASE):
|
44
|
+
return sentinel
|
45
|
+
raise ParseSentinelError(text=text)
|
46
|
+
|
47
|
+
|
48
|
+
@dataclass(kw_only=True, slots=True)
|
49
|
+
class ParseSentinelError(Exception):
|
50
|
+
text: str
|
51
|
+
|
52
|
+
@override
|
53
|
+
def __str__(self) -> str:
|
54
|
+
return f"Unable to parse sentinel value; got {self.text!r}"
|
55
|
+
|
56
|
+
|
57
|
+
__all__ = [
|
58
|
+
"SENTINEL_REPR",
|
59
|
+
"ParseSentinelError",
|
60
|
+
"Sentinel",
|
61
|
+
"parse_sentinel",
|
62
|
+
"sentinel",
|
63
|
+
]
|
utilities/text.py
CHANGED
@@ -45,7 +45,26 @@ class ParseBoolError(Exception):
|
|
45
45
|
|
46
46
|
@override
|
47
47
|
def __str__(self) -> str:
|
48
|
-
return f"Unable to parse {self.text!r}
|
48
|
+
return f"Unable to parse boolean value; got {self.text!r}"
|
49
|
+
|
50
|
+
|
51
|
+
##
|
52
|
+
|
53
|
+
|
54
|
+
def parse_none(text: str, /) -> None:
|
55
|
+
"""Parse text into the None value."""
|
56
|
+
if text == "" or search("None", text, flags=IGNORECASE):
|
57
|
+
return
|
58
|
+
raise ParseNoneError(text=text)
|
59
|
+
|
60
|
+
|
61
|
+
@dataclass(kw_only=True, slots=True)
|
62
|
+
class ParseNoneError(Exception):
|
63
|
+
text: str
|
64
|
+
|
65
|
+
@override
|
66
|
+
def __str__(self) -> str:
|
67
|
+
return f"Unable to parse null value; got {self.text!r}"
|
49
68
|
|
50
69
|
|
51
70
|
##
|
@@ -108,8 +127,10 @@ def strip_and_dedent(text: str, /, *, trailing: bool = False) -> str:
|
|
108
127
|
|
109
128
|
__all__ = [
|
110
129
|
"ParseBoolError",
|
130
|
+
"ParseNoneError",
|
111
131
|
"join_strs",
|
112
132
|
"parse_bool",
|
133
|
+
"parse_none",
|
113
134
|
"repr_encode",
|
114
135
|
"snake_case",
|
115
136
|
"split_str",
|
File without changes
|
File without changes
|