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.
Files changed (94) hide show
  1. dycw_utilities-0.185.8.dist-info/METADATA +33 -0
  2. dycw_utilities-0.185.8.dist-info/RECORD +90 -0
  3. {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +2 -2
  4. utilities/__init__.py +1 -1
  5. utilities/altair.py +8 -6
  6. utilities/asyncio.py +40 -56
  7. utilities/atools.py +9 -11
  8. utilities/cachetools.py +8 -6
  9. utilities/click.py +4 -3
  10. utilities/concurrent.py +1 -1
  11. utilities/constants.py +492 -0
  12. utilities/contextlib.py +23 -30
  13. utilities/contextvars.py +1 -23
  14. utilities/core.py +2581 -0
  15. utilities/dataclasses.py +16 -119
  16. utilities/docker.py +139 -45
  17. utilities/enum.py +1 -1
  18. utilities/errors.py +2 -16
  19. utilities/fastapi.py +5 -5
  20. utilities/fpdf2.py +2 -1
  21. utilities/functions.py +33 -264
  22. utilities/http.py +2 -3
  23. utilities/hypothesis.py +48 -25
  24. utilities/iterables.py +39 -575
  25. utilities/jinja2.py +3 -6
  26. utilities/jupyter.py +5 -3
  27. utilities/libcst.py +1 -1
  28. utilities/lightweight_charts.py +4 -6
  29. utilities/logging.py +17 -15
  30. utilities/math.py +1 -36
  31. utilities/more_itertools.py +4 -6
  32. utilities/numpy.py +2 -1
  33. utilities/operator.py +2 -2
  34. utilities/orjson.py +24 -25
  35. utilities/os.py +4 -185
  36. utilities/packaging.py +129 -0
  37. utilities/parse.py +33 -13
  38. utilities/pathlib.py +2 -136
  39. utilities/platform.py +8 -90
  40. utilities/polars.py +34 -31
  41. utilities/postgres.py +9 -4
  42. utilities/pottery.py +20 -18
  43. utilities/pqdm.py +3 -4
  44. utilities/psutil.py +2 -3
  45. utilities/pydantic.py +18 -4
  46. utilities/pydantic_settings.py +7 -9
  47. utilities/pydantic_settings_sops.py +3 -3
  48. utilities/pyinstrument.py +4 -4
  49. utilities/pytest.py +49 -108
  50. utilities/pytest_plugins/pytest_regressions.py +2 -2
  51. utilities/pytest_regressions.py +8 -6
  52. utilities/random.py +2 -8
  53. utilities/redis.py +98 -94
  54. utilities/reprlib.py +11 -118
  55. utilities/shellingham.py +66 -0
  56. utilities/slack_sdk.py +13 -12
  57. utilities/sqlalchemy.py +42 -30
  58. utilities/sqlalchemy_polars.py +16 -25
  59. utilities/subprocess.py +1166 -148
  60. utilities/tabulate.py +32 -0
  61. utilities/testbook.py +8 -8
  62. utilities/text.py +24 -115
  63. utilities/throttle.py +159 -0
  64. utilities/time.py +18 -0
  65. utilities/timer.py +29 -12
  66. utilities/traceback.py +15 -22
  67. utilities/types.py +38 -3
  68. utilities/typing.py +18 -12
  69. utilities/uuid.py +1 -1
  70. utilities/version.py +202 -45
  71. utilities/whenever.py +22 -150
  72. dycw_utilities-0.175.17.dist-info/METADATA +0 -34
  73. dycw_utilities-0.175.17.dist-info/RECORD +0 -103
  74. utilities/atomicwrites.py +0 -182
  75. utilities/cryptography.py +0 -41
  76. utilities/getpass.py +0 -8
  77. utilities/git.py +0 -19
  78. utilities/grp.py +0 -28
  79. utilities/gzip.py +0 -31
  80. utilities/json.py +0 -70
  81. utilities/permissions.py +0 -298
  82. utilities/pickle.py +0 -25
  83. utilities/pwd.py +0 -28
  84. utilities/re.py +0 -156
  85. utilities/sentinel.py +0 -73
  86. utilities/socket.py +0 -8
  87. utilities/string.py +0 -20
  88. utilities/tempfile.py +0 -136
  89. utilities/tzdata.py +0 -11
  90. utilities/tzlocal.py +0 -28
  91. utilities/warnings.py +0 -65
  92. utilities/zipfile.py +0 -25
  93. utilities/zoneinfo.py +0 -133
  94. {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
@@ -1,8 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from getpass import getuser
4
-
5
- USER = getuser()
6
-
7
-
8
- __all__ = ["USER"]
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"]