omlish 0.0.0.dev160__py3-none-any.whl → 0.0.0.dev161__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.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev160'
2
- __revision__ = '7a7f89f635d413c2afb755bed939daa26703116f'
1
+ __version__ = '0.0.0.dev161'
2
+ __revision__ = '60fcd526dba581c88e3b27a1defac7fb75614a7b'
3
3
 
4
4
 
5
5
  #
omlish/os/atomics.py ADDED
@@ -0,0 +1,205 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import abc
4
+ import os
5
+ import shutil
6
+ import tempfile
7
+ import typing as ta
8
+
9
+ from omlish.lite.check import check
10
+ from omlish.lite.strings import attr_repr
11
+
12
+
13
+ AtomicPathSwapKind = ta.Literal['dir', 'file']
14
+ AtomicPathSwapState = ta.Literal['open', 'committed', 'aborted'] # ta.TypeAlias
15
+
16
+
17
+ ##
18
+
19
+
20
+ class AtomicPathSwap(abc.ABC):
21
+ def __init__(
22
+ self,
23
+ kind: AtomicPathSwapKind,
24
+ dst_path: str,
25
+ *,
26
+ auto_commit: bool = False,
27
+ ) -> None:
28
+ super().__init__()
29
+
30
+ self._kind = kind
31
+ self._dst_path = dst_path
32
+ self._auto_commit = auto_commit
33
+
34
+ self._state: AtomicPathSwapState = 'open'
35
+
36
+ def __repr__(self) -> str:
37
+ return attr_repr(self, 'kind', 'dst_path', 'tmp_path')
38
+
39
+ @property
40
+ def kind(self) -> AtomicPathSwapKind:
41
+ return self._kind
42
+
43
+ @property
44
+ def dst_path(self) -> str:
45
+ return self._dst_path
46
+
47
+ @property
48
+ @abc.abstractmethod
49
+ def tmp_path(self) -> str:
50
+ raise NotImplementedError
51
+
52
+ #
53
+
54
+ @property
55
+ def state(self) -> AtomicPathSwapState:
56
+ return self._state
57
+
58
+ def _check_state(self, *states: AtomicPathSwapState) -> None:
59
+ if self._state not in states:
60
+ raise RuntimeError(f'Atomic path swap not in correct state: {self._state}, {states}')
61
+
62
+ #
63
+
64
+ @abc.abstractmethod
65
+ def _commit(self) -> None:
66
+ raise NotImplementedError
67
+
68
+ def commit(self) -> None:
69
+ if self._state == 'committed':
70
+ return
71
+ self._check_state('open')
72
+ try:
73
+ self._commit()
74
+ except Exception: # noqa
75
+ self._abort()
76
+ raise
77
+ else:
78
+ self._state = 'committed'
79
+
80
+ #
81
+
82
+ @abc.abstractmethod
83
+ def _abort(self) -> None:
84
+ raise NotImplementedError
85
+
86
+ def abort(self) -> None:
87
+ if self._state == 'aborted':
88
+ return
89
+ self._abort()
90
+ self._state = 'aborted'
91
+
92
+ #
93
+
94
+ def __enter__(self) -> 'AtomicPathSwap':
95
+ return self
96
+
97
+ def __exit__(self, exc_type, exc_val, exc_tb):
98
+ if (
99
+ exc_type is None and
100
+ self._auto_commit and
101
+ self._state == 'open'
102
+ ):
103
+ self.commit()
104
+ else:
105
+ self.abort()
106
+
107
+
108
+ class AtomicPathSwapping(abc.ABC):
109
+ @abc.abstractmethod
110
+ def begin_atomic_path_swap(
111
+ self,
112
+ kind: AtomicPathSwapKind,
113
+ dst_path: str,
114
+ *,
115
+ name_hint: ta.Optional[str] = None,
116
+ make_dirs: bool = False,
117
+ **kwargs: ta.Any,
118
+ ) -> AtomicPathSwap:
119
+ raise NotImplementedError
120
+
121
+
122
+ ##
123
+
124
+
125
+ class OsRenameAtomicPathSwap(AtomicPathSwap):
126
+ def __init__(
127
+ self,
128
+ kind: AtomicPathSwapKind,
129
+ dst_path: str,
130
+ tmp_path: str,
131
+ **kwargs: ta.Any,
132
+ ) -> None:
133
+ if kind == 'dir':
134
+ check.state(os.path.isdir(tmp_path))
135
+ elif kind == 'file':
136
+ check.state(os.path.isfile(tmp_path))
137
+ else:
138
+ raise TypeError(kind)
139
+
140
+ super().__init__(
141
+ kind,
142
+ dst_path,
143
+ **kwargs,
144
+ )
145
+
146
+ self._tmp_path = tmp_path
147
+
148
+ @property
149
+ def tmp_path(self) -> str:
150
+ return self._tmp_path
151
+
152
+ def _commit(self) -> None:
153
+ os.rename(self._tmp_path, self._dst_path)
154
+
155
+ def _abort(self) -> None:
156
+ shutil.rmtree(self._tmp_path, ignore_errors=True)
157
+
158
+
159
+ class TempDirAtomicPathSwapping(AtomicPathSwapping):
160
+ def __init__(
161
+ self,
162
+ *,
163
+ temp_dir: ta.Optional[str] = None,
164
+ root_dir: ta.Optional[str] = None,
165
+ ) -> None:
166
+ super().__init__()
167
+
168
+ if root_dir is not None:
169
+ root_dir = os.path.abspath(root_dir)
170
+ self._root_dir = root_dir
171
+ self._temp_dir = temp_dir
172
+
173
+ def begin_atomic_path_swap(
174
+ self,
175
+ kind: AtomicPathSwapKind,
176
+ dst_path: str,
177
+ *,
178
+ name_hint: ta.Optional[str] = None,
179
+ make_dirs: bool = False,
180
+ **kwargs: ta.Any,
181
+ ) -> AtomicPathSwap:
182
+ dst_path = os.path.abspath(dst_path)
183
+ if self._root_dir is not None and not dst_path.startswith(check.non_empty_str(self._root_dir)):
184
+ raise RuntimeError(f'Atomic path swap dst must be in root dir: {dst_path}, {self._root_dir}')
185
+
186
+ dst_dir = os.path.dirname(dst_path)
187
+ if make_dirs:
188
+ os.makedirs(dst_dir, exist_ok=True)
189
+ if not os.path.isdir(dst_dir):
190
+ raise RuntimeError(f'Atomic path swap dst dir does not exist: {dst_dir}')
191
+
192
+ if kind == 'dir':
193
+ tmp_path = tempfile.mkdtemp(prefix=name_hint, dir=self._temp_dir)
194
+ elif kind == 'file':
195
+ fd, tmp_path = tempfile.mkstemp(prefix=name_hint, dir=self._temp_dir)
196
+ os.close(fd)
197
+ else:
198
+ raise TypeError(kind)
199
+
200
+ return OsRenameAtomicPathSwap(
201
+ kind,
202
+ dst_path,
203
+ tmp_path,
204
+ **kwargs,
205
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev160
3
+ Version: 0.0.0.dev161
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=RX24SRc6DCEg77PUVnaXOKCWa5TF_c9RQJdGIf7gl9c,1135
2
- omlish/__about__.py,sha256=YIDkVlJyMJUrEtEf00ng1bGGafE3fLQ7pZzCbPUNOjE,3409
2
+ omlish/__about__.py,sha256=mO53HhyG-ZTHxwvhTxgq__7mREyycsxsT-5hHzJF95Q,3409
3
3
  omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
4
4
  omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
5
5
  omlish/cached.py,sha256=UI-XTFBwA6YXWJJJeBn-WkwBkfzDjLBBaZf4nIJA9y0,510
@@ -403,6 +403,7 @@ omlish/math/bits.py,sha256=yip1l8agOYzT7bFyMGc0RR3XlnGCfHMpjw_SECLLh1I,3477
403
403
  omlish/math/floats.py,sha256=UimhOT7KRl8LXTzOI5cQWoX_9h6WNWe_3vcOuO7-h_8,327
404
404
  omlish/math/stats.py,sha256=MegzKVsmv2kra4jDWLOUgV0X7Ee2Tbl5u6ql1v4-dEY,10053
405
405
  omlish/os/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
406
+ omlish/os/atomics.py,sha256=gArGtyH5l3UjLKwKIqFJ5XkRnNouXntPEtkRbI0ch_0,5071
406
407
  omlish/os/deathsig.py,sha256=hk9Yq2kyDdI-cI7OQH7mOfpRbOKzY_TfPKEqgrjVYbA,641
407
408
  omlish/os/files.py,sha256=1tNy1z5I_CgYKA5c6lOfsXc-hknP4tQDbSShdz8HArw,1308
408
409
  omlish/os/journald.py,sha256=2nI8Res1poXkbLc31--MPUlzYMESnCcPUkIxDOCjZW0,3903
@@ -525,9 +526,9 @@ omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,329
525
526
  omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
526
527
  omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
527
528
  omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
528
- omlish-0.0.0.dev160.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
529
- omlish-0.0.0.dev160.dist-info/METADATA,sha256=CZH9L3qhAM-tYsJfEokB-s2fTLnRy-QMkOSnErJHumc,4264
530
- omlish-0.0.0.dev160.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
531
- omlish-0.0.0.dev160.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
532
- omlish-0.0.0.dev160.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
533
- omlish-0.0.0.dev160.dist-info/RECORD,,
529
+ omlish-0.0.0.dev161.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
530
+ omlish-0.0.0.dev161.dist-info/METADATA,sha256=FeXlao_p21yJqF4nL1hTicjLO3Ir0XMm9jii9k8-2ws,4264
531
+ omlish-0.0.0.dev161.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
532
+ omlish-0.0.0.dev161.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
533
+ omlish-0.0.0.dev161.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
534
+ omlish-0.0.0.dev161.dist-info/RECORD,,