dycw-utilities 0.126.9__py3-none-any.whl → 0.126.11__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.126.9.dist-info → dycw_utilities-0.126.11.dist-info}/METADATA +3 -3
- {dycw_utilities-0.126.9.dist-info → dycw_utilities-0.126.11.dist-info}/RECORD +9 -9
- utilities/__init__.py +1 -1
- utilities/hypothesis.py +1 -19
- utilities/redis.py +115 -5
- utilities/sqlalchemy.py +65 -1
- utilities/text.py +17 -0
- {dycw_utilities-0.126.9.dist-info → dycw_utilities-0.126.11.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.126.9.dist-info → dycw_utilities-0.126.11.dist-info}/licenses/LICENSE +0 -0
@@ -1,13 +1,13 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: dycw-utilities
|
3
|
-
Version: 0.126.
|
3
|
+
Version: 0.126.11
|
4
4
|
Author-email: Derek Wan <d.wan@icloud.com>
|
5
5
|
License-File: LICENSE
|
6
6
|
Requires-Python: >=3.12
|
7
7
|
Requires-Dist: typing-extensions<4.14,>=4.13.1
|
8
8
|
Provides-Extra: test
|
9
9
|
Requires-Dist: dycw-pytest-only<2.2,>=2.1.1; extra == 'test'
|
10
|
-
Requires-Dist: hypothesis<6.
|
10
|
+
Requires-Dist: hypothesis<6.134,>=6.133.0; extra == 'test'
|
11
11
|
Requires-Dist: pytest-asyncio<1.1,>=1.0.0; extra == 'test'
|
12
12
|
Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
|
13
13
|
Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
|
@@ -77,7 +77,7 @@ Provides-Extra: zzz-test-hypothesis
|
|
77
77
|
Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-hypothesis'
|
78
78
|
Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-hypothesis'
|
79
79
|
Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-hypothesis'
|
80
|
-
Requires-Dist: hypothesis<6.
|
80
|
+
Requires-Dist: hypothesis<6.134,>=6.133.0; extra == 'zzz-test-hypothesis'
|
81
81
|
Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-hypothesis'
|
82
82
|
Requires-Dist: numpy<2.3,>=2.2.6; extra == 'zzz-test-hypothesis'
|
83
83
|
Requires-Dist: pathvalidate<3.3,>=3.2.3; extra == 'zzz-test-hypothesis'
|
@@ -1,4 +1,4 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=zZdDjXYw0iXgNb3iikpaOv2GNjXKtYX9dLFyhvGm1Bs,61
|
2
2
|
utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
|
3
3
|
utilities/asyncio.py,sha256=phbGti22VSe9cu-SwM1vP8kyUg8AUDHvvciMvE6JnCg,51842
|
4
4
|
utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
|
@@ -23,7 +23,7 @@ utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
|
|
23
23
|
utilities/git.py,sha256=wpt5dZ5Oi5931pN24_VLZYaQOvmR0OcQuVtgHzFUN1k,2359
|
24
24
|
utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
|
25
25
|
utilities/http.py,sha256=WcahTcKYRtZ04WXQoWt5EGCgFPcyHD3EJdlMfxvDt-0,946
|
26
|
-
utilities/hypothesis.py,sha256=
|
26
|
+
utilities/hypothesis.py,sha256=snJ35u9-dXKn3Unac4IPW2V4JtRUg5B7SsDBrQHIx9g,44834
|
27
27
|
utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
|
28
28
|
utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
|
29
29
|
utilities/iterables.py,sha256=mDqw2_0MUVp-P8FklgcaVTi2TXduH0MxbhTDzzhSBho,44915
|
@@ -61,7 +61,7 @@ utilities/pytest_regressions.py,sha256=-SVT9647Dg6-JcdsiaDKXe3NdOmmrvGevLKWwGjxq
|
|
61
61
|
utilities/python_dotenv.py,sha256=iWcnpXbH7S6RoXHiLlGgyuH6udCupAcPd_gQ0eAenQ0,3190
|
62
62
|
utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
|
63
63
|
utilities/re.py,sha256=5J4d8VwIPFVrX2Eb8zfoxImDv7IwiN_U7mJ07wR2Wvs,3958
|
64
|
-
utilities/redis.py,sha256=
|
64
|
+
utilities/redis.py,sha256=pMKJjNI5e0lG-FZh2_idMBBmfgNr53KIGjBquZQOLZc,37556
|
65
65
|
utilities/reprlib.py,sha256=Re9bk3n-kC__9DxQmRlevqFA86pE6TtVfWjUgpbVOv0,1849
|
66
66
|
utilities/rich.py,sha256=t50MwwVBsoOLxzmeVFSVpjno4OW6Ufum32skXbV8-Bs,1911
|
67
67
|
utilities/scipy.py,sha256=X6ROnHwiUhAmPhM0jkfEh0-Fd9iRvwiqtCQMOLmOQF8,945
|
@@ -69,7 +69,7 @@ utilities/sentinel.py,sha256=3jIwgpMekWgDAxPDA_hXMP2St43cPhciKN3LWiZ7kv0,1248
|
|
69
69
|
utilities/shelve.py,sha256=HZsMwK4tcIfg3sh0gApx4-yjQnrY4o3V3ZRimvRhoW0,738
|
70
70
|
utilities/slack_sdk.py,sha256=h2DiVkcFyYcT5zzZOAo6CSith5BBlHUbXeOJSL1neK8,5948
|
71
71
|
utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
|
72
|
-
utilities/sqlalchemy.py,sha256=
|
72
|
+
utilities/sqlalchemy.py,sha256=rHixzaYl_QHTR1dAhLj15ntiE5kwZWY4MKIr97crDts,39602
|
73
73
|
utilities/sqlalchemy_polars.py,sha256=s7hQNep2O5DTgIRXyN_JRQma7a4DAtNd25tshaZW8iw,15490
|
74
74
|
utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
|
75
75
|
utilities/streamlit.py,sha256=U9PJBaKP1IdSykKhPZhIzSPTZsmLsnwbEPZWzNhJPKk,2955
|
@@ -77,7 +77,7 @@ utilities/string.py,sha256=XmU-s04qIV_tODnKl2pQiwmHaxzgOqRKU-RyzdrfvSE,620
|
|
77
77
|
utilities/sys.py,sha256=h0Xr7Vj86wNalvwJVP1wj5Y0kD_VWm1vzuXZ_jw94mE,2743
|
78
78
|
utilities/tempfile.py,sha256=VqmZJAhTJ1OaVywFzk5eqROV8iJbW9XQ_QYAV0bpdRo,1384
|
79
79
|
utilities/tenacity.py,sha256=1PUvODiBVgeqIh7G5TRt5WWMSqjLYkEqP53itT97WQc,4914
|
80
|
-
utilities/text.py,sha256=
|
80
|
+
utilities/text.py,sha256=ymBFlP_cA8OgNnZRVNs7FAh7OG8HxE6YkiLEMZv5g_A,11297
|
81
81
|
utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
82
82
|
utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
|
83
83
|
utilities/traceback.py,sha256=p9WATV-e4_5AW6SvyRBiU-MY8XnEFcKgrFNUoFzalXI,27521
|
@@ -91,7 +91,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
|
91
91
|
utilities/whenever.py,sha256=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
|
92
92
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
93
93
|
utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
|
94
|
-
dycw_utilities-0.126.
|
95
|
-
dycw_utilities-0.126.
|
96
|
-
dycw_utilities-0.126.
|
97
|
-
dycw_utilities-0.126.
|
94
|
+
dycw_utilities-0.126.11.dist-info/METADATA,sha256=BpSldlx63eNjZ98CKmHpEi6Ee2Zm5HqWnYZrKbBOICI,12804
|
95
|
+
dycw_utilities-0.126.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
96
|
+
dycw_utilities-0.126.11.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
97
|
+
dycw_utilities-0.126.11.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/hypothesis.py
CHANGED
@@ -8,12 +8,11 @@ from datetime import timezone
|
|
8
8
|
from enum import Enum, auto
|
9
9
|
from functools import partial
|
10
10
|
from math import ceil, floor, inf, isclose, isfinite, nan
|
11
|
-
from os import environ
|
11
|
+
from os import environ
|
12
12
|
from pathlib import Path
|
13
13
|
from re import search
|
14
14
|
from string import ascii_letters, ascii_lowercase, ascii_uppercase, digits, printable
|
15
15
|
from subprocess import check_call
|
16
|
-
from threading import get_ident
|
17
16
|
from typing import (
|
18
17
|
TYPE_CHECKING,
|
19
18
|
Any,
|
@@ -24,7 +23,6 @@ from typing import (
|
|
24
23
|
overload,
|
25
24
|
override,
|
26
25
|
)
|
27
|
-
from uuid import uuid4
|
28
26
|
|
29
27
|
from hypothesis import HealthCheck, Phase, Verbosity, assume, settings
|
30
28
|
from hypothesis.errors import InvalidArgument
|
@@ -89,7 +87,6 @@ from utilities.pathlib import temp_cwd
|
|
89
87
|
from utilities.platform import IS_WINDOWS
|
90
88
|
from utilities.sentinel import Sentinel, sentinel
|
91
89
|
from utilities.tempfile import TEMP_DIR, TemporaryDirectory
|
92
|
-
from utilities.tzlocal import get_now_local
|
93
90
|
from utilities.version import Version
|
94
91
|
from utilities.zoneinfo import UTC
|
95
92
|
|
@@ -1411,20 +1408,6 @@ def uint64s(
|
|
1411
1408
|
##
|
1412
1409
|
|
1413
1410
|
|
1414
|
-
@composite
|
1415
|
-
def unique_strs(draw: DrawFn, /) -> str:
|
1416
|
-
"""Strategy for generating unique strings."""
|
1417
|
-
now = get_now_local()
|
1418
|
-
pid = getpid()
|
1419
|
-
ident = just(get_ident())
|
1420
|
-
key = str(uuid4()).replace("-", "")
|
1421
|
-
full = f"{now:%Y%m%d%H%M%S%f}_{pid}_{ident}_{key}"
|
1422
|
-
return draw(just(full))
|
1423
|
-
|
1424
|
-
|
1425
|
-
##
|
1426
|
-
|
1427
|
-
|
1428
1411
|
@composite
|
1429
1412
|
def versions(draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False) -> Version:
|
1430
1413
|
"""Strategy for generating versions."""
|
@@ -1553,7 +1536,6 @@ __all__ = [
|
|
1553
1536
|
"triples",
|
1554
1537
|
"uint32s",
|
1555
1538
|
"uint64s",
|
1556
|
-
"unique_strs",
|
1557
1539
|
"versions",
|
1558
1540
|
"zoned_datetimes",
|
1559
1541
|
]
|
utilities/redis.py
CHANGED
@@ -25,7 +25,14 @@ from typing import (
|
|
25
25
|
from redis.asyncio import Redis
|
26
26
|
from redis.typing import EncodableT
|
27
27
|
|
28
|
-
from utilities.asyncio import
|
28
|
+
from utilities.asyncio import (
|
29
|
+
EnhancedQueue,
|
30
|
+
InfiniteQueueLooper,
|
31
|
+
Looper,
|
32
|
+
LooperTimeoutError,
|
33
|
+
timeout_dur,
|
34
|
+
)
|
35
|
+
from utilities.contextlib import suppress_super_object_attribute_error
|
29
36
|
from utilities.datetime import (
|
30
37
|
MILLISECOND,
|
31
38
|
SECOND,
|
@@ -72,6 +79,9 @@ _V2 = TypeVar("_V2")
|
|
72
79
|
_V3 = TypeVar("_V3")
|
73
80
|
|
74
81
|
|
82
|
+
_PUBLISH_TIMEOUT: Duration = SECOND
|
83
|
+
|
84
|
+
|
75
85
|
##
|
76
86
|
|
77
87
|
|
@@ -548,9 +558,6 @@ def redis_key(
|
|
548
558
|
##
|
549
559
|
|
550
560
|
|
551
|
-
_PUBLISH_TIMEOUT: Duration = SECOND
|
552
|
-
|
553
|
-
|
554
561
|
@overload
|
555
562
|
async def publish(
|
556
563
|
redis: Redis,
|
@@ -666,7 +673,7 @@ class PublishService(Looper[tuple[str, _T]]):
|
|
666
673
|
# self
|
667
674
|
redis: Redis
|
668
675
|
serializer: Callable[[_T], EncodableT] = serialize
|
669
|
-
publish_timeout: Duration =
|
676
|
+
publish_timeout: Duration = _PUBLISH_TIMEOUT
|
670
677
|
|
671
678
|
@override
|
672
679
|
async def core(self) -> None:
|
@@ -685,6 +692,55 @@ class PublishService(Looper[tuple[str, _T]]):
|
|
685
692
|
##
|
686
693
|
|
687
694
|
|
695
|
+
@dataclass(kw_only=True)
|
696
|
+
class PublishServiceMixin(Generic[_T]):
|
697
|
+
"""Mix-in for the publish service."""
|
698
|
+
|
699
|
+
# base - looper
|
700
|
+
publish_service_freq: Duration = field(default=MILLISECOND, repr=False)
|
701
|
+
publish_service_backoff: Duration = field(default=SECOND, repr=False)
|
702
|
+
publish_service_empty_upon_exit: bool = field(default=False, repr=False)
|
703
|
+
publish_service_logger: str | None = field(default=None, repr=False)
|
704
|
+
publish_service_timeout: Duration | None = field(default=None, repr=False)
|
705
|
+
publish_service_timeout_error: type[Exception] = field(
|
706
|
+
default=LooperTimeoutError, repr=False
|
707
|
+
)
|
708
|
+
publish_service_debug: bool = field(default=False, repr=False)
|
709
|
+
_is_pending_restart: Event = field(default_factory=Event, init=False, repr=False)
|
710
|
+
# base - publish service
|
711
|
+
publish_service_redis: Redis
|
712
|
+
publish_service_serializer: Callable[[_T], EncodableT] = serialize
|
713
|
+
publish_service_publish_timeout: Duration = _PUBLISH_TIMEOUT
|
714
|
+
# self
|
715
|
+
_publish_service: PublishService[_T] = field(init=False, repr=False)
|
716
|
+
|
717
|
+
def __post_init__(self) -> None:
|
718
|
+
with suppress_super_object_attribute_error(): # skipif-ci-and-not-linux
|
719
|
+
super().__post_init__() # pyright: ignore[reportAttributeAccessIssue]
|
720
|
+
self._publish_service = PublishService( # skipif-ci-and-not-linux
|
721
|
+
# looper
|
722
|
+
freq=self.publish_service_freq,
|
723
|
+
backoff=self.publish_service_backoff,
|
724
|
+
empty_upon_exit=self.publish_service_empty_upon_exit,
|
725
|
+
logger=self.publish_service_logger,
|
726
|
+
timeout=self.publish_service_timeout,
|
727
|
+
timeout_error=self.publish_service_timeout_error,
|
728
|
+
_debug=self.publish_service_debug,
|
729
|
+
# publish service
|
730
|
+
redis=self.publish_service_redis,
|
731
|
+
serializer=self.publish_service_serializer,
|
732
|
+
publish_timeout=self.publish_service_publish_timeout,
|
733
|
+
)
|
734
|
+
|
735
|
+
def _yield_sub_loopers(self) -> Iterator[Looper[Any]]:
|
736
|
+
with suppress_super_object_attribute_error(): # skipif-ci-and-not-linux
|
737
|
+
yield from super()._yield_sub_loopers() # pyright: ignore[reportAttributeAccessIssue]
|
738
|
+
yield self._publish_service # skipif-ci-and-not-linux
|
739
|
+
|
740
|
+
|
741
|
+
##
|
742
|
+
|
743
|
+
|
688
744
|
_SUBSCRIBE_TIMEOUT: Duration = SECOND
|
689
745
|
_SUBSCRIBE_SLEEP: Duration = MILLISECOND
|
690
746
|
|
@@ -920,6 +976,58 @@ class SubscribeService(Looper[_T]):
|
|
920
976
|
##
|
921
977
|
|
922
978
|
|
979
|
+
@dataclass(kw_only=True)
|
980
|
+
class SubscribeServiceMixin(Generic[_T]):
|
981
|
+
"""Mix-in for the subscribe service."""
|
982
|
+
|
983
|
+
# base - looper
|
984
|
+
subscribe_service_freq: Duration = field(default=MILLISECOND, repr=False)
|
985
|
+
subscribe_service_backoff: Duration = field(default=SECOND, repr=False)
|
986
|
+
subscribe_service_empty_upon_exit: bool = field(default=False, repr=False)
|
987
|
+
subscribe_service_logger: str | None = field(default=None, repr=False)
|
988
|
+
subscribe_service_timeout: Duration | None = field(default=None, repr=False)
|
989
|
+
subscribe_service_timeout_error: type[Exception] = field(
|
990
|
+
default=LooperTimeoutError, repr=False
|
991
|
+
)
|
992
|
+
subscribe_service_debug: bool = field(default=False, repr=False)
|
993
|
+
# base - looper
|
994
|
+
subscribe_service_redis: Redis
|
995
|
+
subscribe_service_channel: str
|
996
|
+
subscribe_service_deserializer: Callable[[bytes], _T] = deserialize
|
997
|
+
subscribe_service_subscribe_sleep: Duration = _SUBSCRIBE_SLEEP
|
998
|
+
subscribe_service_subscribe_timeout: Duration | None = _SUBSCRIBE_TIMEOUT
|
999
|
+
# self
|
1000
|
+
_subscribe_service: SubscribeService[_T] = field(init=False, repr=False)
|
1001
|
+
|
1002
|
+
def __post_init__(self) -> None:
|
1003
|
+
with suppress_super_object_attribute_error(): # skipif-ci-and-not-linux
|
1004
|
+
super().__post_init__() # pyright: ignore[reportAttributeAccessIssue]
|
1005
|
+
self._subscribe_service = SubscribeService( # skipif-ci-and-not-linux
|
1006
|
+
# looper
|
1007
|
+
freq=self.subscribe_service_freq,
|
1008
|
+
backoff=self.subscribe_service_backoff,
|
1009
|
+
empty_upon_exit=self.subscribe_service_empty_upon_exit,
|
1010
|
+
logger=self.subscribe_service_logger,
|
1011
|
+
timeout=self.subscribe_service_timeout,
|
1012
|
+
timeout_error=self.subscribe_service_timeout_error,
|
1013
|
+
_debug=self.subscribe_service_debug,
|
1014
|
+
# subscribe service
|
1015
|
+
redis=self.subscribe_service_redis,
|
1016
|
+
channel=self.subscribe_service_channel,
|
1017
|
+
deserializer=self.subscribe_service_deserializer,
|
1018
|
+
subscribe_sleep=self.subscribe_service_subscribe_sleep,
|
1019
|
+
subscribe_timeout=self.subscribe_service_subscribe_timeout,
|
1020
|
+
)
|
1021
|
+
|
1022
|
+
def _yield_sub_loopers(self) -> Iterator[Looper[Any]]:
|
1023
|
+
with suppress_super_object_attribute_error(): # skipif-ci-and-not-linux
|
1024
|
+
yield from super()._yield_sub_loopers() # pyright: ignore[reportAttributeAccessIssue]
|
1025
|
+
yield self._subscribe_service # skipif-ci-and-not-linux
|
1026
|
+
|
1027
|
+
|
1028
|
+
##
|
1029
|
+
|
1030
|
+
|
923
1031
|
@asynccontextmanager
|
924
1032
|
async def yield_pubsub(
|
925
1033
|
redis: Redis, channels: MaybeIterable[str], /
|
@@ -1000,11 +1108,13 @@ def _deserialize(
|
|
1000
1108
|
|
1001
1109
|
__all__ = [
|
1002
1110
|
"PublishService",
|
1111
|
+
"PublishServiceMixin",
|
1003
1112
|
"Publisher",
|
1004
1113
|
"PublisherError",
|
1005
1114
|
"RedisHashMapKey",
|
1006
1115
|
"RedisKey",
|
1007
1116
|
"SubscribeService",
|
1117
|
+
"SubscribeServiceMixin",
|
1008
1118
|
"publish",
|
1009
1119
|
"redis_hash_map_key",
|
1010
1120
|
"redis_key",
|
utilities/sqlalchemy.py
CHANGED
@@ -57,7 +57,13 @@ from sqlalchemy.orm import (
|
|
57
57
|
from sqlalchemy.orm.exc import UnmappedClassError
|
58
58
|
from sqlalchemy.pool import NullPool, Pool
|
59
59
|
|
60
|
-
from utilities.asyncio import
|
60
|
+
from utilities.asyncio import (
|
61
|
+
InfiniteQueueLooper,
|
62
|
+
Looper,
|
63
|
+
LooperTimeoutError,
|
64
|
+
timeout_dur,
|
65
|
+
)
|
66
|
+
from utilities.contextlib import suppress_super_object_attribute_error
|
61
67
|
from utilities.datetime import SECOND
|
62
68
|
from utilities.functions import (
|
63
69
|
ensure_str,
|
@@ -691,6 +697,63 @@ class UpsertService(Looper[_InsertItem]):
|
|
691
697
|
)
|
692
698
|
|
693
699
|
|
700
|
+
@dataclass(kw_only=True)
|
701
|
+
class UpsertServiceMixin:
|
702
|
+
"""Mix-in for the upsert service."""
|
703
|
+
|
704
|
+
# base - looper
|
705
|
+
upsert_service_freq: Duration = field(default=SECOND, repr=False)
|
706
|
+
upsert_service_backoff: Duration = field(default=SECOND, repr=False)
|
707
|
+
upsert_service_empty_upon_exit: bool = field(default=False, repr=False)
|
708
|
+
upsert_service_logger: str | None = field(default=None, repr=False)
|
709
|
+
upsert_service_timeout: Duration | None = field(default=None, repr=False)
|
710
|
+
upsert_service_timeout_error: type[Exception] = field(
|
711
|
+
default=LooperTimeoutError, repr=False
|
712
|
+
)
|
713
|
+
upsert_service_debug: bool = field(default=False, repr=False)
|
714
|
+
# base - upsert service
|
715
|
+
upsert_service_database: AsyncEngine
|
716
|
+
upsert_service_snake: bool = False
|
717
|
+
upsert_service_selected_or_all: _SelectedOrAll = "selected"
|
718
|
+
upsert_service_chunk_size_frac: float = CHUNK_SIZE_FRAC
|
719
|
+
upsert_service_assume_tables_exist: bool = False
|
720
|
+
upsert_service_timeout_create: Duration | None = None
|
721
|
+
upsert_service_error_create: type[Exception] = TimeoutError
|
722
|
+
upsert_service_timeout_insert: Duration | None = None
|
723
|
+
upsert_service_error_insert: type[Exception] = TimeoutError
|
724
|
+
# self
|
725
|
+
_upsert_service: UpsertService = field(init=False, repr=False)
|
726
|
+
|
727
|
+
def __post_init__(self) -> None:
|
728
|
+
with suppress_super_object_attribute_error():
|
729
|
+
super().__post_init__() # pyright: ignore[reportAttributeAccessIssue]
|
730
|
+
self._upsert_service = UpsertService(
|
731
|
+
# looper
|
732
|
+
freq=self.upsert_service_freq,
|
733
|
+
backoff=self.upsert_service_backoff,
|
734
|
+
empty_upon_exit=self.upsert_service_empty_upon_exit,
|
735
|
+
logger=self.upsert_service_logger,
|
736
|
+
timeout=self.upsert_service_timeout,
|
737
|
+
timeout_error=self.upsert_service_timeout_error,
|
738
|
+
_debug=self.upsert_service_debug,
|
739
|
+
# upsert service
|
740
|
+
engine=self.upsert_service_database,
|
741
|
+
snake=self.upsert_service_snake,
|
742
|
+
selected_or_all=self.upsert_service_selected_or_all,
|
743
|
+
chunk_size_frac=self.upsert_service_chunk_size_frac,
|
744
|
+
assume_tables_exist=self.upsert_service_assume_tables_exist,
|
745
|
+
timeout_create=self.upsert_service_timeout_create,
|
746
|
+
error_create=self.upsert_service_error_create,
|
747
|
+
timeout_insert=self.upsert_service_timeout_insert,
|
748
|
+
error_insert=self.upsert_service_error_insert,
|
749
|
+
)
|
750
|
+
|
751
|
+
def _yield_sub_loopers(self) -> Iterator[Looper[Any]]:
|
752
|
+
with suppress_super_object_attribute_error():
|
753
|
+
yield from super()._yield_sub_loopers() # pyright: ignore[reportAttributeAccessIssue]
|
754
|
+
yield self._upsert_service
|
755
|
+
|
756
|
+
|
694
757
|
##
|
695
758
|
|
696
759
|
|
@@ -1147,6 +1210,7 @@ __all__ = [
|
|
1147
1210
|
"TablenameMixin",
|
1148
1211
|
"UpsertItemsError",
|
1149
1212
|
"UpsertService",
|
1213
|
+
"UpsertServiceMixin",
|
1150
1214
|
"Upserter",
|
1151
1215
|
"UpserterError",
|
1152
1216
|
"check_engine",
|
utilities/text.py
CHANGED
@@ -4,9 +4,13 @@ import re
|
|
4
4
|
from collections import deque
|
5
5
|
from dataclasses import dataclass
|
6
6
|
from itertools import chain
|
7
|
+
from os import getpid
|
7
8
|
from re import IGNORECASE, Match, escape, search
|
8
9
|
from textwrap import dedent
|
10
|
+
from threading import get_ident
|
11
|
+
from time import time_ns
|
9
12
|
from typing import TYPE_CHECKING, Any, Literal, overload, override
|
13
|
+
from uuid import uuid4
|
10
14
|
|
11
15
|
from utilities.iterables import CheckDuplicatesError, check_duplicates, transpose
|
12
16
|
from utilities.reprlib import get_repr
|
@@ -395,6 +399,18 @@ def strip_and_dedent(text: str, /, *, trailing: bool = False) -> str:
|
|
395
399
|
return f"{result}\n" if trailing else result
|
396
400
|
|
397
401
|
|
402
|
+
##
|
403
|
+
|
404
|
+
|
405
|
+
def unique_str() -> str:
|
406
|
+
"""Generate at unique string."""
|
407
|
+
now = time_ns()
|
408
|
+
pid = getpid()
|
409
|
+
ident = get_ident()
|
410
|
+
key = str(uuid4()).replace("-", "")
|
411
|
+
return f"{now}_{pid}_{ident}_{key}"
|
412
|
+
|
413
|
+
|
398
414
|
__all__ = [
|
399
415
|
"BRACKETS",
|
400
416
|
"DEFAULT_SEPARATOR",
|
@@ -413,4 +429,5 @@ __all__ = [
|
|
413
429
|
"split_str",
|
414
430
|
"str_encode",
|
415
431
|
"strip_and_dedent",
|
432
|
+
"unique_str",
|
416
433
|
]
|
File without changes
|
File without changes
|