omlish 0.0.0.dev160__py3-none-any.whl → 0.0.0.dev161__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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,,