dycw-utilities 0.149.10__py3-none-any.whl → 0.150.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.149.10
3
+ Version: 0.150.0
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,4 +1,4 @@
1
- utilities/__init__.py,sha256=loKT95lEsEfTUcmwQY8dkR_TL2ce2JNBkEhRMbcj5Ag,61
1
+ utilities/__init__.py,sha256=-FKXlj49n14usgRRgsXp-6U1_cXzP-blAYQWGuSGKL8,60
2
2
  utilities/altair.py,sha256=92E2lCdyHY4Zb-vCw6rEJIsWdKipuu-Tu2ab1ufUfAk,9079
3
3
  utilities/asyncio.py,sha256=2m2a2C-Qgc6OHTTHL332-t66A7xDITt_SORT7a1DJWo,16792
4
4
  utilities/atomicwrites.py,sha256=xcOWenTBRS0oat3kg7Sqe51AohNThMQ2ixPL7QCG8hw,5795
@@ -50,7 +50,7 @@ utilities/platform.py,sha256=Ue9LSxYvg9yUXGKuz5aZoy_qkUEXde-v6B09exgSctU,2813
50
50
  utilities/polars.py,sha256=BgiDryAVOapi41ddfJqN0wYh_sDj8BNEYtPB36LaHdo,71824
51
51
  utilities/polars_ols.py,sha256=Uc9V5kvlWZ5cU93lKZ-cfAKdVFFw81tqwLW9PxtUvMs,5618
52
52
  utilities/postgres.py,sha256=C3TxpQymM7S5ZivxIN_AFTM5g5TtENptzx8-XYf7pow,12182
53
- utilities/pottery.py,sha256=cdFBjU3Zn-nhlhk4p8IC-ybwFTzk0F2TE1xBV7z8V3c,6296
53
+ utilities/pottery.py,sha256=u0uvyGgYyujxftEMlsv6ppYTKQoVVjHt5jnVxxYz9s4,6596
54
54
  utilities/pqdm.py,sha256=BTsYPtbKQWwX-iXF4qCkfPG7DPxIB54J989n83bXrIo,3092
55
55
  utilities/psutil.py,sha256=KUlu4lrUw9Zg1V7ZGetpWpGb9DB8l_SSDWGbANFNCPU,2104
56
56
  utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -66,7 +66,7 @@ utilities/sentinel.py,sha256=3jIwgpMekWgDAxPDA_hXMP2St43cPhciKN3LWiZ7kv0,1248
66
66
  utilities/shelve.py,sha256=4OzjQI6kGuUbJciqf535rwnao-_IBv66gsT6tRGiUt0,759
67
67
  utilities/slack_sdk.py,sha256=ppFBvKgfg5IRWiIoKPtpTyzBtBF4XmwEvU3I5wLJikM,2140
68
68
  utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
69
- utilities/sqlalchemy.py,sha256=q2aYUDAC3SE88Lt6XaKa3CLzT_ePaWvQu_OuRk19x9g,35520
69
+ utilities/sqlalchemy.py,sha256=M__UwdIiXDc6W8oRHQ6u3h_zEb9uTKwfl506KJCrOl4,37231
70
70
  utilities/sqlalchemy_polars.py,sha256=18AoEbeNJUKF3-5hroNy9J5LQwS_QJAXbMfKc9sChtk,14250
71
71
  utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
72
72
  utilities/string.py,sha256=MB0X6UPTUc06JdAdj-PctZ238IXeCjE5dAJibNw6ZrU,587
@@ -89,8 +89,8 @@ utilities/zoneinfo.py,sha256=oEH-nL3t4h9uawyZqWDtNtDAl6M-CLpLYGI_nI6DulM,1971
89
89
  utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
90
90
  utilities/pytest_plugins/pytest_randomly.py,sha256=NXzCcGKbpgYouz5yehKb4jmxmi2SexKKpgF4M65bi10,414
91
91
  utilities/pytest_plugins/pytest_regressions.py,sha256=Iwhfv_OJH7UCPZCfoh7ugZ2Xjqjil-BBBsOb8sDwiGI,1471
92
- dycw_utilities-0.149.10.dist-info/METADATA,sha256=EbgjAbqUqMjs6OSUe3OOxvI9tsyWzQTysNvXw84cbYQ,1698
93
- dycw_utilities-0.149.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
94
- dycw_utilities-0.149.10.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
95
- dycw_utilities-0.149.10.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
96
- dycw_utilities-0.149.10.dist-info/RECORD,,
92
+ dycw_utilities-0.150.0.dist-info/METADATA,sha256=F2t8-Dhx3lTo-_ns45mIJ0vtLW9KTBUHrViS1bR94v0,1697
93
+ dycw_utilities-0.150.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
94
+ dycw_utilities-0.150.0.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
95
+ dycw_utilities-0.150.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
96
+ dycw_utilities-0.150.0.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.149.10"
3
+ __version__ = "0.150.0"
utilities/pottery.py CHANGED
@@ -12,12 +12,14 @@ from redis.asyncio import Redis
12
12
 
13
13
  from utilities.asyncio import loop_until_succeed, sleep_td, timeout_td
14
14
  from utilities.contextlib import enhanced_async_context_manager
15
+ from utilities.contextvars import yield_set_context
15
16
  from utilities.iterables import always_iterable
16
17
  from utilities.logging import get_logger
17
18
  from utilities.whenever import MILLISECOND, SECOND, to_seconds
18
19
 
19
20
  if TYPE_CHECKING:
20
21
  from collections.abc import AsyncIterator, Callable, Iterable
22
+ from contextvars import ContextVar
21
23
 
22
24
  from whenever import Delta
23
25
 
@@ -57,6 +59,7 @@ async def try_yield_coroutine_looper(
57
59
  throttle: Delta | None = None,
58
60
  logger: LoggerOrName | None = None,
59
61
  sleep_error: Delta | None = None,
62
+ context: ContextVar[bool] | None = None,
60
63
  ) -> AsyncIterator[CoroutineLooper | None]:
61
64
  """Try acquire access to a coroutine looper."""
62
65
  try: # skipif-ci-and-not-linux
@@ -70,7 +73,12 @@ async def try_yield_coroutine_looper(
70
73
  sleep=sleep_acquire,
71
74
  throttle=throttle,
72
75
  ) as lock:
73
- yield CoroutineLooper(lock=lock, logger=logger, sleep=sleep_error)
76
+ looper = CoroutineLooper(lock=lock, logger=logger, sleep=sleep_error)
77
+ if context is None:
78
+ yield looper
79
+ else:
80
+ with yield_set_context(context):
81
+ yield looper
74
82
  except _YieldAccessUnableToAcquireLockError as error: # skipif-ci-and-not-linux
75
83
  if logger is not None:
76
84
  get_logger(logger=logger).info("%s", error)
utilities/sqlalchemy.py CHANGED
@@ -18,8 +18,19 @@ from itertools import chain
18
18
  from math import floor
19
19
  from operator import ge, le
20
20
  from re import search
21
- from typing import TYPE_CHECKING, Any, Literal, TypeGuard, assert_never, cast, override
21
+ from typing import (
22
+ TYPE_CHECKING,
23
+ Any,
24
+ Literal,
25
+ TypeGuard,
26
+ assert_never,
27
+ cast,
28
+ overload,
29
+ override,
30
+ )
22
31
 
32
+ import sqlalchemy
33
+ import sqlalchemy.ext.asyncio
23
34
  from sqlalchemy import (
24
35
  URL,
25
36
  Column,
@@ -42,12 +53,12 @@ from sqlalchemy.dialects.postgresql import Insert as postgresql_Insert
42
53
  from sqlalchemy.dialects.postgresql import dialect as postgresql_dialect
43
54
  from sqlalchemy.dialects.postgresql import insert as postgresql_insert
44
55
  from sqlalchemy.dialects.postgresql.asyncpg import PGDialect_asyncpg
56
+ from sqlalchemy.dialects.postgresql.psycopg import PGDialect_psycopg
45
57
  from sqlalchemy.dialects.sqlite import Insert as sqlite_Insert
46
58
  from sqlalchemy.dialects.sqlite import dialect as sqlite_dialect
47
59
  from sqlalchemy.dialects.sqlite import insert as sqlite_insert
48
- from sqlalchemy.exc import ArgumentError, DatabaseError
60
+ from sqlalchemy.exc import ArgumentError, DatabaseError, OperationalError
49
61
  from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine
50
- from sqlalchemy.ext.asyncio import create_async_engine as _create_async_engine
51
62
  from sqlalchemy.orm import (
52
63
  DeclarativeBase,
53
64
  InstrumentedAttribute,
@@ -99,6 +110,18 @@ CHUNK_SIZE_FRAC = 0.8
99
110
  ##
100
111
 
101
112
 
113
+ def check_connect(engine: Engine, /) -> bool:
114
+ """Check if an engine can connect."""
115
+ try:
116
+ with engine.connect() as conn:
117
+ return bool(conn.execute(text("SELECT 1")).scalar_one())
118
+ except OperationalError:
119
+ return False
120
+
121
+
122
+ ##
123
+
124
+
102
125
  async def check_engine(
103
126
  engine: AsyncEngine,
104
127
  /,
@@ -187,7 +210,49 @@ def _columnwise_minmax(*columns: Any, op: Callable[[Any, Any], Any]) -> Any:
187
210
  ##
188
211
 
189
212
 
190
- def create_async_engine(
213
+ @overload
214
+ def create_engine(
215
+ drivername: str,
216
+ /,
217
+ *,
218
+ username: str | None = None,
219
+ password: str | None = None,
220
+ host: str | None = None,
221
+ port: int | None = None,
222
+ database: str | None = None,
223
+ query: StrMapping | None = None,
224
+ poolclass: type[Pool] | None = NullPool,
225
+ async_: Literal[True],
226
+ ) -> AsyncEngine: ...
227
+ @overload
228
+ def create_engine(
229
+ drivername: str,
230
+ /,
231
+ *,
232
+ username: str | None = None,
233
+ password: str | None = None,
234
+ host: str | None = None,
235
+ port: int | None = None,
236
+ database: str | None = None,
237
+ query: StrMapping | None = None,
238
+ poolclass: type[Pool] | None = NullPool,
239
+ async_: Literal[False] = False,
240
+ ) -> Engine: ...
241
+ @overload
242
+ def create_engine(
243
+ drivername: str,
244
+ /,
245
+ *,
246
+ username: str | None = None,
247
+ password: str | None = None,
248
+ host: str | None = None,
249
+ port: int | None = None,
250
+ database: str | None = None,
251
+ query: StrMapping | None = None,
252
+ poolclass: type[Pool] | None = NullPool,
253
+ async_: bool = False,
254
+ ) -> Engine | AsyncEngine: ...
255
+ def create_engine(
191
256
  drivername: str,
192
257
  /,
193
258
  *,
@@ -198,7 +263,8 @@ def create_async_engine(
198
263
  database: str | None = None,
199
264
  query: StrMapping | None = None,
200
265
  poolclass: type[Pool] | None = NullPool,
201
- ) -> AsyncEngine:
266
+ async_: bool = False,
267
+ ) -> Engine | AsyncEngine:
202
268
  """Create a SQLAlchemy engine."""
203
269
  if query is None:
204
270
  kwargs = {}
@@ -217,7 +283,13 @@ def create_async_engine(
217
283
  database=database,
218
284
  **kwargs,
219
285
  )
220
- return _create_async_engine(url, poolclass=poolclass)
286
+ match async_:
287
+ case False:
288
+ return sqlalchemy.create_engine(url, poolclass=poolclass)
289
+ case True:
290
+ return sqlalchemy.ext.asyncio.create_async_engine(url, poolclass=poolclass)
291
+ case _ as never:
292
+ assert_never(never)
221
293
 
222
294
 
223
295
  ##
@@ -825,7 +897,7 @@ def _get_dialect(engine_or_conn: EngineOrConnectionOrAsync, /) -> Dialect:
825
897
  if isinstance(dialect, oracle_dialect): # pragma: no cover
826
898
  return "oracle"
827
899
  if isinstance( # skipif-ci-and-not-linux
828
- dialect, postgresql_dialect | PGDialect_asyncpg
900
+ dialect, (postgresql_dialect, PGDialect_asyncpg, PGDialect_psycopg)
829
901
  ):
830
902
  return "postgresql"
831
903
  if isinstance(dialect, sqlite_dialect):
@@ -1107,10 +1179,11 @@ __all__ = [
1107
1179
  "InsertItemsError",
1108
1180
  "TablenameMixin",
1109
1181
  "UpsertItemsError",
1182
+ "check_connect",
1110
1183
  "check_engine",
1111
1184
  "columnwise_max",
1112
1185
  "columnwise_min",
1113
- "create_async_engine",
1186
+ "create_engine",
1114
1187
  "ensure_tables_created",
1115
1188
  "ensure_tables_dropped",
1116
1189
  "enum_name",