dycw-utilities 0.135.2__py3-none-any.whl → 0.136.1__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.135.2
3
+ Version: 0.136.1
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -13,6 +13,7 @@ Requires-Dist: coloredlogs<15.1,>=15.0.1; extra == 'logging'
13
13
  Provides-Extra: test
14
14
  Requires-Dist: dycw-pytest-only<2.2,>=2.1.1; extra == 'test'
15
15
  Requires-Dist: hypothesis<6.136,>=6.135.2; extra == 'test'
16
+ Requires-Dist: pudb<2025.2,>=2025.1; extra == 'test'
16
17
  Requires-Dist: pytest-asyncio<1.1,>=1.0.0; extra == 'test'
17
18
  Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
18
19
  Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=dS9-gVIgJGzS5CZL-BTK1_FXZj4N7p0SWBqVdZrhTN4,60
1
+ utilities/__init__.py,sha256=ghJADNjYC0lnQw7wfkYTP1svwcTYMtjPxrfG-2Yp7II,60
2
2
  utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
3
3
  utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
4
4
  utilities/arq.py,sha256=S-sfBfY-E1ErRKf4sSXt2YyCjKvu-pBlOECDfjBebRA,6399
@@ -53,7 +53,6 @@ utilities/polars_ols.py,sha256=Uc9V5kvlWZ5cU93lKZ-cfAKdVFFw81tqwLW9PxtUvMs,5618
53
53
  utilities/pottery.py,sha256=RN3XwOEsVAPXvEfsRPmn3ZSKgTzK_c182PNrtksq-bg,3429
54
54
  utilities/pqdm.py,sha256=BTsYPtbKQWwX-iXF4qCkfPG7DPxIB54J989n83bXrIo,3092
55
55
  utilities/psutil.py,sha256=0j4YxtVb8VjaaKKiHg6UEK95SUPkEcENgPtLgPJsNv0,3760
56
- utilities/pudb.py,sha256=Dte7P9J0VWrWTNYvwxZX6kgaEtdTt5ZnnmOAUTjltqU,1771
57
56
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
57
  utilities/pydantic.py,sha256=CmxCi4sukeHM3JGjJ1Rbp8UAvcx4MZapLg10mFYJ-nk,1771
59
58
  utilities/pyinstrument.py,sha256=_Rfq6Gg4NKV2NF0dRYOpK2IRyyePxI7-3UmHIQLYrlQ,852
@@ -69,7 +68,7 @@ utilities/sentinel.py,sha256=3jIwgpMekWgDAxPDA_hXMP2St43cPhciKN3LWiZ7kv0,1248
69
68
  utilities/shelve.py,sha256=HZsMwK4tcIfg3sh0gApx4-yjQnrY4o3V3ZRimvRhoW0,738
70
69
  utilities/slack_sdk.py,sha256=RrB34gOj4TzBFh1vzMy6wL_ajzIG-2c9kiS6c6LzMFM,4233
71
70
  utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
72
- utilities/sqlalchemy.py,sha256=5Cx-ioYb099xDUDZuXrXp5tFU90HnCvu3jUu6wkGxrg,38001
71
+ utilities/sqlalchemy.py,sha256=NAJGgCqdLoqPZIne74M3Z_sOaNYVEXiIhl3-6p-4UZE,38219
73
72
  utilities/sqlalchemy_polars.py,sha256=jXO-yizGuX_mivaQg7pe17GldcTMpeEldkIL4SpMyuI,14294
74
73
  utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
75
74
  utilities/streamlit.py,sha256=U9PJBaKP1IdSykKhPZhIzSPTZsmLsnwbEPZWzNhJPKk,2955
@@ -78,7 +77,7 @@ utilities/tempfile.py,sha256=VqmZJAhTJ1OaVywFzk5eqROV8iJbW9XQ_QYAV0bpdRo,1384
78
77
  utilities/text.py,sha256=ymBFlP_cA8OgNnZRVNs7FAh7OG8HxE6YkiLEMZv5g_A,11297
79
78
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
80
79
  utilities/timer.py,sha256=oXfTii6ymu57niP0BDGZjFD55LEHi2a19kqZKiTgaFQ,2588
81
- utilities/traceback.py,sha256=TR-3XD40fVIfPT4TSX6E92BEl3xze03N_G4iEE8Qy-4,8837
80
+ utilities/traceback.py,sha256=nvnhlsxLiViFkGLj5zQFNMs2IOoBEOzMZYjsEW_ESQM,9091
82
81
  utilities/typed_settings.py,sha256=niNxgvVKrTy1c0j0D0Jp2tO3m231f4yJJ-38ni-xY3Q,3739
83
82
  utilities/types.py,sha256=DrFWZQqpkekHdnr2-G2eBaxGX8FoND6ON2XFRvhm8Jo,17177
84
83
  utilities/typing.py,sha256=Z-_XDaWyT_6wIo3qfNK-hvRlzxP2Jxa9PgXzm5rDYRA,13790
@@ -90,7 +89,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
90
89
  utilities/whenever.py,sha256=A-yoOqBqrcVD1yDINDsTFDw7dq9-zgUGn_f8CxVUQJs,23332
91
90
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
92
91
  utilities/zoneinfo.py,sha256=oEH-nL3t4h9uawyZqWDtNtDAl6M-CLpLYGI_nI6DulM,1971
93
- dycw_utilities-0.135.2.dist-info/METADATA,sha256=H4xe_yx3Eh17ngKfl6qkcPUlFUSeNYZmbfaf9IT1Kqs,1584
94
- dycw_utilities-0.135.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
95
- dycw_utilities-0.135.2.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
96
- dycw_utilities-0.135.2.dist-info/RECORD,,
92
+ dycw_utilities-0.136.1.dist-info/METADATA,sha256=V3lMerqiPJLZQv-NaDpV_MHwsBt6fkaoZRslaaGRS7w,1637
93
+ dycw_utilities-0.136.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
94
+ dycw_utilities-0.136.1.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
95
+ dycw_utilities-0.136.1.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.135.2"
3
+ __version__ = "0.136.1"
utilities/sqlalchemy.py CHANGED
@@ -66,6 +66,7 @@ from utilities.functions import (
66
66
  is_string_mapping,
67
67
  is_tuple,
68
68
  is_tuple_or_str_mapping,
69
+ yield_object_attributes,
69
70
  )
70
71
  from utilities.iterables import (
71
72
  CheckLengthError,
@@ -1036,21 +1037,31 @@ class _MapMappingToTableSnakeMapNonUniqueError(_MapMappingToTableError):
1036
1037
 
1037
1038
  def _orm_inst_to_dict(obj: DeclarativeBase, /) -> StrMapping:
1038
1039
  """Map an ORM instance to a dictionary."""
1039
- cls = type(obj)
1040
+ attrs = {
1041
+ k for k, _ in yield_object_attributes(obj, static_type=InstrumentedAttribute)
1042
+ }
1043
+ return {
1044
+ name: _orm_inst_to_dict_one(obj, attrs, name) for name in get_column_names(obj)
1045
+ }
1040
1046
 
1041
- def is_attr(attr: str, key: str, /) -> str | None:
1042
- if isinstance(value := getattr(cls, attr), InstrumentedAttribute) and (
1043
- value.name == key
1044
- ):
1045
- return attr
1046
- return None
1047
1047
 
1048
- def yield_items() -> Iterator[tuple[str, Any]]:
1049
- for key in get_column_names(cls):
1050
- attr = one(attr for attr in dir(cls) if is_attr(attr, key) is not None)
1051
- yield key, getattr(obj, attr)
1048
+ def _orm_inst_to_dict_one(
1049
+ obj: DeclarativeBase, attrs: AbstractSet[str], name: str, /
1050
+ ) -> Any:
1051
+ attr = one(
1052
+ attr for attr in attrs if _orm_inst_to_dict_predicate(type(obj), attr, name)
1053
+ )
1054
+ return getattr(obj, attr)
1055
+
1052
1056
 
1053
- return dict(yield_items())
1057
+ def _orm_inst_to_dict_predicate(
1058
+ cls: type[DeclarativeBase], attr: str, name: str, /
1059
+ ) -> bool:
1060
+ cls_attr = getattr(cls, attr)
1061
+ try:
1062
+ return cls_attr.name == name
1063
+ except AttributeError:
1064
+ return False
1054
1065
 
1055
1066
 
1056
1067
  ##
utilities/traceback.py CHANGED
@@ -11,7 +11,7 @@ from itertools import repeat
11
11
  from pathlib import Path
12
12
  from socket import gethostname
13
13
  from traceback import TracebackException
14
- from typing import TYPE_CHECKING, override
14
+ from typing import TYPE_CHECKING, assert_never, override
15
15
 
16
16
  from utilities.atomicwrites import writer
17
17
  from utilities.errors import repr_error
@@ -31,7 +31,7 @@ from utilities.version import get_version
31
31
  from utilities.whenever import format_compact, get_now, to_zoned_date_time
32
32
 
33
33
  if TYPE_CHECKING:
34
- from collections.abc import Callable, Iterator, Sequence
34
+ from collections.abc import Iterator, Sequence
35
35
  from traceback import FrameSummary
36
36
  from types import TracebackType
37
37
 
@@ -203,7 +203,7 @@ def make_except_hook(
203
203
  max_depth: int | None = RICH_MAX_DEPTH,
204
204
  expand_all: bool = RICH_EXPAND_ALL,
205
205
  slack_url: str | None = None,
206
- pudb_env_var: str | None = None,
206
+ pudb: str | None = None,
207
207
  ) -> Callable[
208
208
  [type[BaseException] | None, BaseException | None, TracebackType | None], None
209
209
  ]:
@@ -220,7 +220,7 @@ def make_except_hook(
220
220
  max_depth=max_depth,
221
221
  expand_all=expand_all,
222
222
  slack_url=slack_url,
223
- pudb_env_var=pudb_env_var,
223
+ pudb=pudb,
224
224
  )
225
225
 
226
226
 
@@ -240,7 +240,7 @@ def _make_except_hook_inner(
240
240
  max_depth: int | None = RICH_MAX_DEPTH,
241
241
  expand_all: bool = RICH_EXPAND_ALL,
242
242
  slack_url: str | None = None,
243
- pudb_env_var: str | None = None,
243
+ pudb: str | Callable[[], bool] | None = None,
244
244
  ) -> None:
245
245
  """Exception hook to log the traceback."""
246
246
  _ = (exc_type, traceback)
@@ -272,12 +272,18 @@ def _make_except_hook_inner(
272
272
 
273
273
  send = f"```{slim}```"
274
274
  run(send_to_slack(slack_url, send))
275
- if (pudb_env_var is not None) and ( # pragma: no cover
276
- get_env_var(pudb_env_var, nullable=True) is not None
277
- ):
278
- from pudb import post_mortem
279
-
280
- post_mortem()
275
+ if pudb is not None: # pragma: no cover
276
+ match pudb:
277
+ case str() as env_var:
278
+ call_pudb = get_env_var(env_var, nullable=True) is not None
279
+ case Callable() as func:
280
+ call_pudb = func()
281
+ case _ as never:
282
+ assert_never(never)
283
+ if call_pudb:
284
+ from pudb import post_mortem
285
+
286
+ post_mortem(tb=traceback, e_type=exc_type, e_value=exc_val)
281
287
 
282
288
 
283
289
  @dataclass(kw_only=True, slots=True)
utilities/pudb.py DELETED
@@ -1,62 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from asyncio import iscoroutinefunction
4
- from functools import partial, wraps
5
- from typing import TYPE_CHECKING, Any, NoReturn, cast, overload
6
-
7
- from utilities.os import GetEnvVarError, get_env_var
8
-
9
- if TYPE_CHECKING:
10
- from collections.abc import Callable
11
-
12
-
13
- _ENV_VAR = "DEBUG"
14
-
15
-
16
- @overload
17
- def call_pudb[F: Callable](func: F, /, *, env_var: str = _ENV_VAR) -> F: ...
18
- @overload
19
- def call_pudb[F: Callable](
20
- func: None = None, /, *, env_var: str = _ENV_VAR
21
- ) -> Callable[[F], F]: ...
22
- def call_pudb[F: Callable](
23
- func: F | None = None, /, *, env_var: str = _ENV_VAR
24
- ) -> F | Callable[[F], F]:
25
- """Call `pudb` upon failure, if the required environment variable is set."""
26
- if func is None:
27
- result = partial(call_pudb, env_var=env_var)
28
- return cast("Callable[[F], F]", result)
29
-
30
- if not iscoroutinefunction(func):
31
-
32
- @wraps(func)
33
- def wrapped_sync(*args: Any, **kwargs: Any) -> Any:
34
- try:
35
- return func(*args, **kwargs)
36
- except Exception as error: # noqa: BLE001
37
- _call_pudb(error, env_var=env_var)
38
-
39
- return cast("F", wrapped_sync)
40
-
41
- @wraps(func)
42
- async def wrapped_async(*args: Any, **kwargs: Any) -> Any:
43
- try:
44
- return await func(*args, **kwargs)
45
- except Exception as error: # noqa: BLE001
46
- _call_pudb(error, env_var=env_var)
47
-
48
- return cast("F", wrapped_async)
49
-
50
-
51
- def _call_pudb(error: Exception, /, *, env_var: str = _ENV_VAR) -> NoReturn:
52
- try:
53
- _ = get_env_var(env_var)
54
- except GetEnvVarError:
55
- raise error from None
56
- from pudb import post_mortem # pragma: no cover
57
-
58
- post_mortem() # pragma: no cover
59
- raise error # pragma: no cover
60
-
61
-
62
- __all__ = ["call_pudb"]