dycw-utilities 0.175.17__py3-none-any.whl → 0.185.8__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.185.8.dist-info/METADATA +33 -0
- dycw_utilities-0.185.8.dist-info/RECORD +90 -0
- {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +2 -2
- utilities/__init__.py +1 -1
- utilities/altair.py +8 -6
- utilities/asyncio.py +40 -56
- utilities/atools.py +9 -11
- utilities/cachetools.py +8 -6
- utilities/click.py +4 -3
- utilities/concurrent.py +1 -1
- utilities/constants.py +492 -0
- utilities/contextlib.py +23 -30
- utilities/contextvars.py +1 -23
- utilities/core.py +2581 -0
- utilities/dataclasses.py +16 -119
- utilities/docker.py +139 -45
- utilities/enum.py +1 -1
- utilities/errors.py +2 -16
- utilities/fastapi.py +5 -5
- utilities/fpdf2.py +2 -1
- utilities/functions.py +33 -264
- utilities/http.py +2 -3
- utilities/hypothesis.py +48 -25
- utilities/iterables.py +39 -575
- utilities/jinja2.py +3 -6
- utilities/jupyter.py +5 -3
- utilities/libcst.py +1 -1
- utilities/lightweight_charts.py +4 -6
- utilities/logging.py +17 -15
- utilities/math.py +1 -36
- utilities/more_itertools.py +4 -6
- utilities/numpy.py +2 -1
- utilities/operator.py +2 -2
- utilities/orjson.py +24 -25
- utilities/os.py +4 -185
- utilities/packaging.py +129 -0
- utilities/parse.py +33 -13
- utilities/pathlib.py +2 -136
- utilities/platform.py +8 -90
- utilities/polars.py +34 -31
- utilities/postgres.py +9 -4
- utilities/pottery.py +20 -18
- utilities/pqdm.py +3 -4
- utilities/psutil.py +2 -3
- utilities/pydantic.py +18 -4
- utilities/pydantic_settings.py +7 -9
- utilities/pydantic_settings_sops.py +3 -3
- utilities/pyinstrument.py +4 -4
- utilities/pytest.py +49 -108
- utilities/pytest_plugins/pytest_regressions.py +2 -2
- utilities/pytest_regressions.py +8 -6
- utilities/random.py +2 -8
- utilities/redis.py +98 -94
- utilities/reprlib.py +11 -118
- utilities/shellingham.py +66 -0
- utilities/slack_sdk.py +13 -12
- utilities/sqlalchemy.py +42 -30
- utilities/sqlalchemy_polars.py +16 -25
- utilities/subprocess.py +1166 -148
- utilities/tabulate.py +32 -0
- utilities/testbook.py +8 -8
- utilities/text.py +24 -115
- utilities/throttle.py +159 -0
- utilities/time.py +18 -0
- utilities/timer.py +29 -12
- utilities/traceback.py +15 -22
- utilities/types.py +38 -3
- utilities/typing.py +18 -12
- utilities/uuid.py +1 -1
- utilities/version.py +202 -45
- utilities/whenever.py +22 -150
- dycw_utilities-0.175.17.dist-info/METADATA +0 -34
- dycw_utilities-0.175.17.dist-info/RECORD +0 -103
- utilities/atomicwrites.py +0 -182
- utilities/cryptography.py +0 -41
- utilities/getpass.py +0 -8
- utilities/git.py +0 -19
- utilities/grp.py +0 -28
- utilities/gzip.py +0 -31
- utilities/json.py +0 -70
- utilities/permissions.py +0 -298
- utilities/pickle.py +0 -25
- utilities/pwd.py +0 -28
- utilities/re.py +0 -156
- utilities/sentinel.py +0 -73
- utilities/socket.py +0 -8
- utilities/string.py +0 -20
- utilities/tempfile.py +0 -136
- utilities/tzdata.py +0 -11
- utilities/tzlocal.py +0 -28
- utilities/warnings.py +0 -65
- utilities/zipfile.py +0 -25
- utilities/zoneinfo.py +0 -133
- {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +0 -0
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
utilities/__init__.py,sha256=8_tM295sqZUhpgJO-NkFFdbuntYma_XUNSt0IM3-C_0,61
|
|
2
|
-
utilities/altair.py,sha256=TLfRFbG9HwG7SLXoJ-v0r-t49ZaGgTQZD82cpjVi4vs,9085
|
|
3
|
-
utilities/asyncio.py,sha256=aJySVxBY0gqsIYnoNmH7-1r8djKuf4vSsU69VCD08t8,16772
|
|
4
|
-
utilities/atomicwrites.py,sha256=tPo6r-Rypd9u99u66B9z86YBPpnLrlHtwox_8Z7T34Y,5790
|
|
5
|
-
utilities/atools.py,sha256=6neeCcgXxK2dlsc0xp15Za7nSucbCgFtAJepGI_-WXU,2549
|
|
6
|
-
utilities/cachetools.py,sha256=2S9LMHIunDYMIu8JGI7OLN04sQ7-xZGdEdP1Li0vksA,2775
|
|
7
|
-
utilities/click.py,sha256=ScLzBLoBp8Si5YjgB18A0IVMAR-r4sGUnVfJbAaru98,19191
|
|
8
|
-
utilities/concurrent.py,sha256=fHeW2SZ_TEMfFY0C8pyQI6aPlnecvx9x6SuUwBWj_JY,2853
|
|
9
|
-
utilities/contextlib.py,sha256=iP7R2tIm6ZsbfLD5ks6UKBYwj50e9gBI8AkpLN-chro,7476
|
|
10
|
-
utilities/contextvars.py,sha256=J8OhC7jqozAGYOCe2KUWysbPXNGe5JYz3HfaY_mIs08,883
|
|
11
|
-
utilities/cryptography.py,sha256=5PFrzsNUGHay91dFgYnDKwYprXxahrBqztmUqViRzBk,956
|
|
12
|
-
utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
|
|
13
|
-
utilities/dataclasses.py,sha256=xbU3QN1GFy7RC6hIJRZIeUZm7YRlodrgEWmahWG6k2g,32465
|
|
14
|
-
utilities/docker.py,sha256=N__PKd3cnSRsXNEMHMLdLneLdyzfbr2ESkElcwrovvQ,7940
|
|
15
|
-
utilities/enum.py,sha256=5l6pwZD1cjSlVW4ss-zBPspWvrbrYrdtJWcg6f5_J5w,5781
|
|
16
|
-
utilities/errors.py,sha256=mFlDGSM0LI1jZ1pbqwLAH3ttLZ2JVIxyZLojw8tGVZU,1479
|
|
17
|
-
utilities/fastapi.py,sha256=TqyKvBjiMS594sXPjrz-KRTLMb3l3D3rZ1zAYV7GfOk,1454
|
|
18
|
-
utilities/fpdf2.py,sha256=dSiYz0FJTD2sQuxpxqFWwwIe2-p6Y7oTB9Tv0Jajit0,1866
|
|
19
|
-
utilities/functions.py,sha256=18Zda7nTloARdcEudH8YJ4e13xAdWShAGhPNN4w2Gyc,21498
|
|
20
|
-
utilities/functools.py,sha256=I00ru2gQPakZw2SHVeKIKXfTv741655s6HI0lUoE0D4,1552
|
|
21
|
-
utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
|
|
22
|
-
utilities/git.py,sha256=U1RFvCTANGENgx9wVBDvllioqBQZM2ns12ivKhOsaO4,414
|
|
23
|
-
utilities/grp.py,sha256=1vV3gNR9dQsl1vtUtvC_2qgVdQzm7O8lLMSh56cTbeg,694
|
|
24
|
-
utilities/gzip.py,sha256=fkGP3KdsBfXlstodT4wtlp-PwNyUsogpbDCVVVGdsm4,781
|
|
25
|
-
utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
|
|
26
|
-
utilities/http.py,sha256=TsavEfHlRtlLaeV21Z6KZh0qbPw-kvD1zsQdZ7Kep5Q,977
|
|
27
|
-
utilities/hypothesis.py,sha256=NUu30pl5kjL3tzo-m8SMRwTqLAmTWK-_Sau2NemJcQo,46773
|
|
28
|
-
utilities/importlib.py,sha256=SkVVtIjVC7bjJ36doXnmnmFiYe5tLbip4YAfYJj8Ycg,892
|
|
29
|
-
utilities/inflect.py,sha256=v7YkOWSu8NAmVghPcf4F3YBZQoJCS47_DLf9jbfWIs0,581
|
|
30
|
-
utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
|
|
31
|
-
utilities/iterables.py,sha256=t2TsW-K3rVlS6y4_tqcc1fk9RwJV-bi7G_VwduMABK0,42558
|
|
32
|
-
utilities/jinja2.py,sha256=JpNHMcyMJDguX8rBN4wEz-v4En7w6WHXvYJr4Xw-F0o,4691
|
|
33
|
-
utilities/json.py,sha256=-WcGtSsCr9Y42wHZzAMnfvU6ihAfVftylFfRUORaDFo,2102
|
|
34
|
-
utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
|
|
35
|
-
utilities/libcst.py,sha256=ngD4wxnR3Kh-RBVmU5l5ST7cuZLhMZwyMDjHZe5mhTs,5581
|
|
36
|
-
utilities/lightweight_charts.py,sha256=YM3ojBvJxuCSUBu_KrhFBmaMCvRPvupKC3qkm-UVZq4,2751
|
|
37
|
-
utilities/logging.py,sha256=mckcX_0Q5njVgnhtIoePcDHjYjTVELw7ExutiiDnzsU,18724
|
|
38
|
-
utilities/math.py,sha256=cevB-YyEYAzJTWtkAr7qeeu-hbxorDI3gMznXlmNQkw,26897
|
|
39
|
-
utilities/memory_profiler.py,sha256=XzN56jDCa5aqXS_DxEjb_K4L6aIWh_5zyKi6OhcIxw0,853
|
|
40
|
-
utilities/modules.py,sha256=iuvLluJya-hvl1Q25-Jk3dLgx2Es3ck4SjJiEkAlVTs,3195
|
|
41
|
-
utilities/more_itertools.py,sha256=syfIPhQF_WS-YiicdGe2h5F1G-Ld12Q2XsVduL2hA40,10908
|
|
42
|
-
utilities/numpy.py,sha256=Xn23sA2ZbVNqwUYEgNJD3XBYH6IbCri_WkHSNhg3NkY,26122
|
|
43
|
-
utilities/operator.py,sha256=C3NylZWGTVWRpwYHOPVhaLgRhw0DfpS4_XQ8KfPhBLQ,3613
|
|
44
|
-
utilities/optuna.py,sha256=C-fhWYiXHVPo1l8QctYkFJ4DyhbSrGorzP1dJb_qvd8,1933
|
|
45
|
-
utilities/orjson.py,sha256=T_0SlK811ysg46d3orvIPY3JpBa4FRMpP2wlPQo7-gU,41854
|
|
46
|
-
utilities/os.py,sha256=kjKKSQfnRqFTTZ315iavaaGd3gGuYNoSWlxVLCJjyQs,4852
|
|
47
|
-
utilities/parse.py,sha256=g7Qm9eBOIeDId2tGA021CIaeF6jp1TI8rx4srdvlyoo,17937
|
|
48
|
-
utilities/pathlib.py,sha256=N4Ip8R9eCM-6GfvxUJ3T9oQIle2C2P52F-13BCFRdTg,9345
|
|
49
|
-
utilities/permissions.py,sha256=vLXlWztSVYffbrxptne7ksj6dU1HLekm4fEvS4ny_4Q,8944
|
|
50
|
-
utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
|
|
51
|
-
utilities/platform.py,sha256=R3ldt2-DlI7la9ng6Rxt1CThd2lL0Ai2tC0TbabtCC0,2800
|
|
52
|
-
utilities/polars.py,sha256=JPzN4UqQDC7R4IXsIuXEIXRiwHSrkiSZcD8UOfwGPuE,87535
|
|
53
|
-
utilities/polars_ols.py,sha256=LNTFNLPuYW7fcAHymlbnams_DhitToblYvib3mhKbwI,5615
|
|
54
|
-
utilities/postgres.py,sha256=g3tEwTI8TdmiCbRME61ffQ0xaibdpXPu8mJOOHvjPKc,12532
|
|
55
|
-
utilities/pottery.py,sha256=nA0SsF9irvfC0tk68YAr08tuL9lGRSlBKihSx7Ibk84,3963
|
|
56
|
-
utilities/pqdm.py,sha256=idv2seRVP2f6NeSfpeEnT5A-tQezaHZKDyeu16g2-0E,3091
|
|
57
|
-
utilities/psutil.py,sha256=KUlu4lrUw9Zg1V7ZGetpWpGb9DB8l_SSDWGbANFNCPU,2104
|
|
58
|
-
utilities/pwd.py,sha256=pzlLkBfSTZsCVkMrAxEmuEoNmm-6goklfAygVmOEZqs,707
|
|
59
|
-
utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
|
-
utilities/pydantic.py,sha256=z_PnYXN_UNNLQwo_XF7cr4GDcf5wicvscD6aFYVOEvA,238
|
|
61
|
-
utilities/pydantic_settings.py,sha256=53tQTpHFtA6UIuzHKzauZtW_bSRwL5lQnNwTWeO4Fjw,7608
|
|
62
|
-
utilities/pydantic_settings_sops.py,sha256=9Ou6Cx6PiYOU49vtkKqqW1Sdp_i3WlVyg8KkUUKNliM,2310
|
|
63
|
-
utilities/pyinstrument.py,sha256=hnXaL-4HE7wWBI5JKaPfYTpsrXe68YiuZKahHV0VJn8,841
|
|
64
|
-
utilities/pytest.py,sha256=9HHwYgZQe6CRF0ekHQEFH05gmoP4Ne0V54RrtUNDfi4,10524
|
|
65
|
-
utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
|
66
|
-
utilities/pytest_plugins/pytest_randomly.py,sha256=B1qYVlExGOxTywq2r1SMi5o7btHLk2PNdY_b1p98dkE,409
|
|
67
|
-
utilities/pytest_plugins/pytest_regressions.py,sha256=mnHYBfdprz50UGVkVzV1bZERZN5CFfoF8YbokGxdFwU,1639
|
|
68
|
-
utilities/pytest_regressions.py,sha256=tJxW38u-zpoyjW1N4zogBx4V_07r-ibDInddcEUyXmc,4763
|
|
69
|
-
utilities/random.py,sha256=hZlH4gnAtoaofWswuJYjcygejrY8db4CzP-z_adO2Mo,4165
|
|
70
|
-
utilities/re.py,sha256=S4h-DLL6ScMPqjboZ_uQ1BVTJajrqV06r_81D--_HCE,4573
|
|
71
|
-
utilities/redis.py,sha256=gybjqKea33Jy50n4dHTS14JdquqHaJqHF2dixQljYWQ,30172
|
|
72
|
-
utilities/reprlib.py,sha256=ssYTcBW-TeRh3fhCJv57sopTZHF5FrPyyUg9yp5XBlo,3953
|
|
73
|
-
utilities/scipy.py,sha256=wZJM7fEgBAkLSYYvSmsg5ac-QuwAI0BGqHVetw1_Hb0,947
|
|
74
|
-
utilities/sentinel.py,sha256=A_p5jX2K0Yc5XBfoYHyBLqHsEWzE1ByOdDuzzA2pZnE,1434
|
|
75
|
-
utilities/shelve.py,sha256=4OzjQI6kGuUbJciqf535rwnao-_IBv66gsT6tRGiUt0,759
|
|
76
|
-
utilities/shutil.py,sha256=knJ7hx42FtIfGByxQZMcOSgQCDlaSony325g505ps3A,480
|
|
77
|
-
utilities/slack_sdk.py,sha256=76-DYtcGiUhEvl-voMamc5OjfF7Y7nCq54Bys1arqzw,2233
|
|
78
|
-
utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
|
|
79
|
-
utilities/sqlalchemy.py,sha256=HQYpd7LFxdTF5WYVWYtCJeEBI71EJm7ytvCGyAH9B-U,37163
|
|
80
|
-
utilities/sqlalchemy_polars.py,sha256=JCGhB37raSR7fqeWV5dTsciRTMVzIdVT9YSqKT0piT0,13370
|
|
81
|
-
utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
|
|
82
|
-
utilities/string.py,sha256=shmBK87zZwzGyixuNuXCiUbqzfeZ9xlrFwz6JTaRvDk,582
|
|
83
|
-
utilities/subprocess.py,sha256=nFPIXVzXomI_Oby8PYF9HNWrsWtcSsCtgFnj_22QYlI,43753
|
|
84
|
-
utilities/tempfile.py,sha256=QyvIdfV4r4YZ0NeNYsg0tCijThLKa7Z32u5Kxy6ZsGo,3619
|
|
85
|
-
utilities/testbook.py,sha256=j1KmaVbrX9VrbeMgtPh5gk55myAsn3dyRUn7jGbPbRk,1294
|
|
86
|
-
utilities/text.py,sha256=7SvwcSR2l_5cOrm1samGnR4C-ZI6qyFLHLzSpO1zeHQ,13958
|
|
87
|
-
utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
|
88
|
-
utilities/timer.py,sha256=BGlwEVznx67scuLOUohyWJ4d5rTnwtk-IR4yLXFiNfo,2574
|
|
89
|
-
utilities/traceback.py,sha256=B_sc0TRUv-mGDnF-ek05nbqjmBiHr3-wvxliAqIF5hI,9608
|
|
90
|
-
utilities/types.py,sha256=UwxYajRnupKTCnzReaYWYqtKdpIPeSO-d97Wu4bLEq8,18878
|
|
91
|
-
utilities/typing.py,sha256=xuR8LxzjD-XlSftTM3TNvGVdQyV1mzpdwWdDMzWwCPE,25310
|
|
92
|
-
utilities/tzdata.py,sha256=fgNVj66yUbCSI_-vrRVzSD3gtf-L_8IEJEPjP_Jel5Y,266
|
|
93
|
-
utilities/tzlocal.py,sha256=KyCXEgCTjqGFx-389JdTuhMRUaT06U1RCMdWoED-qro,728
|
|
94
|
-
utilities/uuid.py,sha256=nQZs6tFX4mqtc2Ku3KqjloYCqwpTKeTj8eKwQwh3FQI,1572
|
|
95
|
-
utilities/version.py,sha256=ipBj5-WYY_nelp2uwFlApfWWCzTLzPwpovUi9x_OBMs,5085
|
|
96
|
-
utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
|
97
|
-
utilities/whenever.py,sha256=F4ek0-OBWxHYrZdmoZt76N2RnNyKY5KrEHt7rqO4AQE,60183
|
|
98
|
-
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
|
99
|
-
utilities/zoneinfo.py,sha256=tdIScrTB2-B-LH0ukb1HUXKooLknOfJNwHk10MuMYvA,3619
|
|
100
|
-
dycw_utilities-0.175.17.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
|
|
101
|
-
dycw_utilities-0.175.17.dist-info/entry_points.txt,sha256=cOGtKeJI0KXLSV7MJ8Dhc2G8jPgDcBDm53MVNJU4ycI,136
|
|
102
|
-
dycw_utilities-0.175.17.dist-info/METADATA,sha256=obLY4pbYFf5oCFvSPuNf1mXsv1VC5nCC7sGJtizR-AA,1443
|
|
103
|
-
dycw_utilities-0.175.17.dist-info/RECORD,,
|
utilities/atomicwrites.py
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import gzip
|
|
4
|
-
import shutil
|
|
5
|
-
from contextlib import ExitStack, contextmanager
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from shutil import copyfileobj, rmtree
|
|
9
|
-
from typing import TYPE_CHECKING, assert_never, override
|
|
10
|
-
|
|
11
|
-
from atomicwrites import replace_atomic
|
|
12
|
-
|
|
13
|
-
from utilities.errors import ImpossibleCaseError
|
|
14
|
-
from utilities.iterables import transpose
|
|
15
|
-
from utilities.tempfile import TemporaryDirectory
|
|
16
|
-
|
|
17
|
-
if TYPE_CHECKING:
|
|
18
|
-
from collections.abc import Iterator
|
|
19
|
-
|
|
20
|
-
from utilities.types import PathLike
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def move(
|
|
24
|
-
source: PathLike, destination: PathLike, /, *, overwrite: bool = False
|
|
25
|
-
) -> None:
|
|
26
|
-
"""Move/replace a file/directory atomically."""
|
|
27
|
-
source, destination = map(Path, [source, destination])
|
|
28
|
-
match (
|
|
29
|
-
source.is_file(),
|
|
30
|
-
source.is_dir(),
|
|
31
|
-
destination.is_file(),
|
|
32
|
-
destination.is_dir(),
|
|
33
|
-
overwrite,
|
|
34
|
-
):
|
|
35
|
-
case False, False, _, _, _:
|
|
36
|
-
raise _MoveSourceNotFoundError(source=source)
|
|
37
|
-
# files
|
|
38
|
-
case (True, False, True, False, False) | (True, False, False, True, False):
|
|
39
|
-
raise _MoveFileExistsError(source=source, destination=destination) from None
|
|
40
|
-
case True, False, False, True, _:
|
|
41
|
-
rmtree(destination, ignore_errors=True)
|
|
42
|
-
return replace_atomic(str(source), str(destination)) # must be `str`s
|
|
43
|
-
case True, False, _, _, _:
|
|
44
|
-
return replace_atomic(str(source), str(destination)) # must be `str`s
|
|
45
|
-
# directories
|
|
46
|
-
case (False, True, True, False, False) | (False, True, False, True, False):
|
|
47
|
-
raise _MoveDirectoryExistsError(source=source, destination=destination)
|
|
48
|
-
case False, True, False, True, _:
|
|
49
|
-
rmtree(destination, ignore_errors=True)
|
|
50
|
-
_ = shutil.move(source, destination)
|
|
51
|
-
return None
|
|
52
|
-
case False, True, _, _, _:
|
|
53
|
-
destination.unlink(missing_ok=True)
|
|
54
|
-
_ = shutil.move(source, destination)
|
|
55
|
-
return None
|
|
56
|
-
case True, True, _, _, _: # pragma: no cover
|
|
57
|
-
raise ImpossibleCaseError(
|
|
58
|
-
case=[f"{source.is_file()=}", f"{source.is_dir()=}"]
|
|
59
|
-
)
|
|
60
|
-
case never:
|
|
61
|
-
assert_never(never)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
@dataclass(kw_only=True, slots=True)
|
|
65
|
-
class MoveError(Exception): ...
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
@dataclass(kw_only=True, slots=True)
|
|
69
|
-
class _MoveSourceNotFoundError(MoveError):
|
|
70
|
-
source: Path
|
|
71
|
-
|
|
72
|
-
@override
|
|
73
|
-
def __str__(self) -> str:
|
|
74
|
-
return f"Source {str(self.source)!r} does not exist"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@dataclass(kw_only=True, slots=True)
|
|
78
|
-
class _MoveFileExistsError(MoveError):
|
|
79
|
-
source: Path
|
|
80
|
-
destination: Path
|
|
81
|
-
|
|
82
|
-
@override
|
|
83
|
-
def __str__(self) -> str:
|
|
84
|
-
return f"Cannot move file {str(self.source)!r} as destination {str(self.destination)!r} already exists"
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@dataclass(kw_only=True, slots=True)
|
|
88
|
-
class _MoveDirectoryExistsError(MoveError):
|
|
89
|
-
source: Path
|
|
90
|
-
destination: Path
|
|
91
|
-
|
|
92
|
-
@override
|
|
93
|
-
def __str__(self) -> str:
|
|
94
|
-
return f"Cannot move directory {str(self.source)!r} as destination {str(self.destination)!r} already exists"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
##
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def move_many(*paths: tuple[PathLike, PathLike], overwrite: bool = False) -> None:
|
|
101
|
-
"""Move a set of files concurrently."""
|
|
102
|
-
sources, destinations = transpose(paths)
|
|
103
|
-
with ExitStack() as stack:
|
|
104
|
-
temp_paths = [
|
|
105
|
-
stack.enter_context(writer(p, overwrite=overwrite)) for p in destinations
|
|
106
|
-
]
|
|
107
|
-
for source, temp_path in zip(sources, temp_paths, strict=True):
|
|
108
|
-
move(source, temp_path, overwrite=overwrite)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
##
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
@contextmanager
|
|
115
|
-
def writer(
|
|
116
|
-
path: PathLike, /, *, compress: bool = False, overwrite: bool = False
|
|
117
|
-
) -> Iterator[Path]:
|
|
118
|
-
"""Yield a path for atomically writing files to disk."""
|
|
119
|
-
path = Path(path)
|
|
120
|
-
parent = path.parent
|
|
121
|
-
parent.mkdir(parents=True, exist_ok=True)
|
|
122
|
-
name = path.name
|
|
123
|
-
with TemporaryDirectory(suffix=".tmp", prefix=name, dir=parent) as temp_dir:
|
|
124
|
-
temp_path1 = Path(temp_dir, name)
|
|
125
|
-
try:
|
|
126
|
-
yield temp_path1
|
|
127
|
-
except KeyboardInterrupt:
|
|
128
|
-
rmtree(temp_dir)
|
|
129
|
-
else:
|
|
130
|
-
if compress:
|
|
131
|
-
temp_path2 = Path(temp_dir, f"{name}.gz")
|
|
132
|
-
with (
|
|
133
|
-
temp_path1.open("rb") as source,
|
|
134
|
-
gzip.open(temp_path2, mode="wb") as dest,
|
|
135
|
-
):
|
|
136
|
-
copyfileobj(source, dest)
|
|
137
|
-
else:
|
|
138
|
-
temp_path2 = temp_path1
|
|
139
|
-
try:
|
|
140
|
-
move(temp_path2, path, overwrite=overwrite)
|
|
141
|
-
except _MoveSourceNotFoundError as error:
|
|
142
|
-
raise _WriterTemporaryPathEmptyError(temp_path=error.source) from None
|
|
143
|
-
except _MoveFileExistsError as error:
|
|
144
|
-
raise _WriterFileExistsError(destination=error.destination) from None
|
|
145
|
-
except _MoveDirectoryExistsError as error:
|
|
146
|
-
raise _WriterDirectoryExistsError(
|
|
147
|
-
destination=error.destination
|
|
148
|
-
) from None
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
@dataclass(kw_only=True, slots=True)
|
|
152
|
-
class WriterError(Exception): ...
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
@dataclass(kw_only=True, slots=True)
|
|
156
|
-
class _WriterTemporaryPathEmptyError(WriterError):
|
|
157
|
-
temp_path: Path
|
|
158
|
-
|
|
159
|
-
@override
|
|
160
|
-
def __str__(self) -> str:
|
|
161
|
-
return f"Temporary path {str(self.temp_path)!r} is empty"
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
@dataclass(kw_only=True, slots=True)
|
|
165
|
-
class _WriterFileExistsError(WriterError):
|
|
166
|
-
destination: Path
|
|
167
|
-
|
|
168
|
-
@override
|
|
169
|
-
def __str__(self) -> str:
|
|
170
|
-
return f"Cannot write to {str(self.destination)!r} as file already exists"
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
@dataclass(kw_only=True, slots=True)
|
|
174
|
-
class _WriterDirectoryExistsError(WriterError):
|
|
175
|
-
destination: Path
|
|
176
|
-
|
|
177
|
-
@override
|
|
178
|
-
def __str__(self) -> str:
|
|
179
|
-
return f"Cannot write to {str(self.destination)!r} as directory already exists"
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
__all__ = ["MoveError", "WriterError", "move", "move_many", "writer"]
|
utilities/cryptography.py
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from os import getenv
|
|
5
|
-
from typing import override
|
|
6
|
-
|
|
7
|
-
from cryptography.fernet import Fernet
|
|
8
|
-
|
|
9
|
-
_ENV_VAR = "FERNET_KEY"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def encrypt(text: str, /, *, env_var: str = _ENV_VAR) -> bytes:
|
|
13
|
-
"""Encrypt a string."""
|
|
14
|
-
return get_fernet(env_var).encrypt(text.encode())
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def decrypt(text: bytes, /, *, env_var: str = _ENV_VAR) -> str:
|
|
18
|
-
"""Encrypt a string."""
|
|
19
|
-
return get_fernet(env_var).decrypt(text).decode()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
##
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def get_fernet(env_var: str = _ENV_VAR, /) -> Fernet:
|
|
26
|
-
"""Get the Fernet key."""
|
|
27
|
-
if (key := getenv(env_var)) is None:
|
|
28
|
-
raise GetFernetError(env_var=env_var)
|
|
29
|
-
return Fernet(key.encode())
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@dataclass(kw_only=True, slots=True)
|
|
33
|
-
class GetFernetError(Exception):
|
|
34
|
-
env_var: str
|
|
35
|
-
|
|
36
|
-
@override
|
|
37
|
-
def __str__(self) -> str:
|
|
38
|
-
return f"Environment variable {self.env_var!r} is None"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
__all__ = ["GetFernetError", "decrypt", "encrypt", "get_fernet"]
|
utilities/getpass.py
DELETED
utilities/git.py
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import TYPE_CHECKING
|
|
5
|
-
|
|
6
|
-
from git import Repo
|
|
7
|
-
|
|
8
|
-
from utilities.pathlib import to_path
|
|
9
|
-
|
|
10
|
-
if TYPE_CHECKING:
|
|
11
|
-
from utilities.types import MaybeCallablePathLike
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def get_repo(path: MaybeCallablePathLike = Path.cwd, /) -> Repo:
|
|
15
|
-
"""Get the repo object."""
|
|
16
|
-
return Repo(to_path(path), search_parent_directories=True)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
__all__ = ["get_repo"]
|
utilities/grp.py
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import assert_never
|
|
4
|
-
|
|
5
|
-
from utilities.os import EFFECTIVE_GROUP_ID
|
|
6
|
-
from utilities.platform import SYSTEM
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def get_gid_name(gid: int, /) -> str | None:
|
|
10
|
-
"""Get the name of a group."""
|
|
11
|
-
match SYSTEM:
|
|
12
|
-
case "windows": # skipif-not-windows
|
|
13
|
-
return None
|
|
14
|
-
case "mac" | "linux":
|
|
15
|
-
from grp import getgrgid
|
|
16
|
-
|
|
17
|
-
return getgrgid(gid).gr_name
|
|
18
|
-
case never:
|
|
19
|
-
assert_never(never)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
ROOT_GROUP_NAME = get_gid_name(0)
|
|
23
|
-
EFFECTIVE_GROUP_NAME = (
|
|
24
|
-
None if EFFECTIVE_GROUP_ID is None else get_gid_name(EFFECTIVE_GROUP_ID)
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
__all__ = ["EFFECTIVE_GROUP_NAME", "ROOT_GROUP_NAME", "get_gid_name"]
|
utilities/gzip.py
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import gzip
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
|
-
|
|
7
|
-
from utilities.atomicwrites import writer
|
|
8
|
-
|
|
9
|
-
if TYPE_CHECKING:
|
|
10
|
-
from utilities.types import PathLike
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def read_binary(path: PathLike, /, *, decompress: bool = False) -> bytes:
|
|
14
|
-
"""Read a byte string from disk."""
|
|
15
|
-
path = Path(path)
|
|
16
|
-
if decompress:
|
|
17
|
-
with gzip.open(path) as gz:
|
|
18
|
-
return gz.read()
|
|
19
|
-
else:
|
|
20
|
-
return path.read_bytes()
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def write_binary(
|
|
24
|
-
data: bytes, path: PathLike, /, *, compress: bool = False, overwrite: bool = False
|
|
25
|
-
) -> None:
|
|
26
|
-
"""Write a byte string to disk."""
|
|
27
|
-
with writer(path, compress=compress, overwrite=overwrite) as temp:
|
|
28
|
-
_ = temp.write_bytes(data)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
__all__ = ["read_binary", "write_binary"]
|
utilities/json.py
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from contextlib import suppress
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from subprocess import check_output
|
|
7
|
-
from typing import TYPE_CHECKING, assert_never, overload, override
|
|
8
|
-
|
|
9
|
-
from utilities.atomicwrites import writer
|
|
10
|
-
from utilities.gzip import write_binary
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from utilities.types import PathLike
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
##
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@overload
|
|
20
|
-
def run_prettier(source: bytes, /) -> bytes: ...
|
|
21
|
-
@overload
|
|
22
|
-
def run_prettier(source: str, /) -> str: ...
|
|
23
|
-
@overload
|
|
24
|
-
def run_prettier(source: Path, /) -> None: ...
|
|
25
|
-
def run_prettier(source: bytes | str | Path, /) -> bytes | str | None:
|
|
26
|
-
"""Run `prettier` on a string/path."""
|
|
27
|
-
match source: # skipif-ci
|
|
28
|
-
case bytes() as data:
|
|
29
|
-
return _run_prettier_core(data, text=False)
|
|
30
|
-
case str() as text:
|
|
31
|
-
if (path := Path(text)).is_file():
|
|
32
|
-
return run_prettier(path)
|
|
33
|
-
return _run_prettier_core(text, text=True)
|
|
34
|
-
case Path() as path:
|
|
35
|
-
result = run_prettier(path.read_bytes())
|
|
36
|
-
with writer(path, overwrite=True) as temp:
|
|
37
|
-
_ = temp.write_bytes(result)
|
|
38
|
-
return None
|
|
39
|
-
case never:
|
|
40
|
-
assert_never(never)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _run_prettier_core(data: bytes | str, /, *, text: bool) -> bytes | str:
|
|
44
|
-
"""Run `prettier` on a string/path."""
|
|
45
|
-
try: # skipif-ci
|
|
46
|
-
return check_output(["prettier", "--parser=json"], input=data, text=text)
|
|
47
|
-
except FileNotFoundError: # pragma: no cover
|
|
48
|
-
raise RunPrettierError from None
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@dataclass(kw_only=True, slots=True)
|
|
52
|
-
class RunPrettierError(Exception):
|
|
53
|
-
@override
|
|
54
|
-
def __str__(self) -> str:
|
|
55
|
-
return "Unable to find 'prettier'" # pragma: no cover
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
##
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def write_formatted_json(
|
|
62
|
-
data: bytes, path: PathLike, /, *, compress: bool = False, overwrite: bool = False
|
|
63
|
-
) -> None:
|
|
64
|
-
"""Write a formatted byte string to disk."""
|
|
65
|
-
with suppress(RunPrettierError):
|
|
66
|
-
data = run_prettier(data)
|
|
67
|
-
write_binary(data, path, compress=compress, overwrite=overwrite)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
__all__ = ["RunPrettierError", "run_prettier", "write_formatted_json"]
|