foamlib 0.6.10__py3-none-any.whl → 0.6.12__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.
- foamlib/__init__.py +1 -1
- foamlib/_cases/_async.py +23 -23
- foamlib/_cases/_base.py +25 -26
- foamlib/_cases/_run.py +52 -58
- foamlib/_cases/_slurm.py +7 -5
- foamlib/_cases/_subprocess.py +15 -13
- foamlib/_cases/_sync.py +25 -27
- foamlib/_cases/_util.py +13 -10
- foamlib/_files/_base.py +37 -27
- foamlib/_files/_files.py +143 -159
- foamlib/_files/_io.py +32 -43
- foamlib/_files/_parsing.py +68 -25
- foamlib/_files/_serialization.py +20 -12
- foamlib/_files/_util.py +8 -3
- {foamlib-0.6.10.dist-info → foamlib-0.6.12.dist-info}/METADATA +1 -1
- foamlib-0.6.12.dist-info/RECORD +22 -0
- {foamlib-0.6.10.dist-info → foamlib-0.6.12.dist-info}/WHEEL +1 -1
- foamlib-0.6.10.dist-info/RECORD +0 -22
- {foamlib-0.6.10.dist-info → foamlib-0.6.12.dist-info}/LICENSE.txt +0 -0
- {foamlib-0.6.10.dist-info → foamlib-0.6.12.dist-info}/top_level.txt +0 -0
foamlib/_files/_files.py
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import sys
|
2
|
-
from
|
4
|
+
from copy import deepcopy
|
5
|
+
from typing import Any, Optional, Tuple, Union, cast
|
3
6
|
|
4
7
|
if sys.version_info >= (3, 8):
|
5
8
|
from typing import Literal
|
@@ -16,15 +19,12 @@ from ._io import FoamFileIO
|
|
16
19
|
from ._serialization import Kind, dumps
|
17
20
|
from ._util import is_sequence
|
18
21
|
|
19
|
-
if TYPE_CHECKING:
|
20
|
-
import numpy as np
|
21
|
-
|
22
22
|
|
23
23
|
class FoamFile(
|
24
24
|
FoamFileBase,
|
25
25
|
MutableMapping[
|
26
26
|
Optional[Union[str, Tuple[str, ...]]],
|
27
|
-
|
27
|
+
FoamFileBase._MutableData,
|
28
28
|
],
|
29
29
|
FoamFileIO,
|
30
30
|
):
|
@@ -37,23 +37,23 @@ class FoamFile(
|
|
37
37
|
"""
|
38
38
|
|
39
39
|
class SubDict(
|
40
|
-
MutableMapping[str,
|
40
|
+
MutableMapping[str, FoamFileBase._MutableData],
|
41
41
|
):
|
42
42
|
"""An OpenFOAM dictionary within a file as a mutable mapping."""
|
43
43
|
|
44
|
-
def __init__(self, _file:
|
44
|
+
def __init__(self, _file: FoamFile, _keywords: tuple[str, ...]) -> None:
|
45
45
|
self._file = _file
|
46
46
|
self._keywords = _keywords
|
47
47
|
|
48
48
|
def __getitem__(
|
49
49
|
self, keyword: str
|
50
|
-
) ->
|
50
|
+
) -> FoamFileBase._DataEntry | FoamFile.SubDict:
|
51
51
|
return self._file[(*self._keywords, keyword)]
|
52
52
|
|
53
53
|
def __setitem__(
|
54
54
|
self,
|
55
55
|
keyword: str,
|
56
|
-
data:
|
56
|
+
data: FoamFileBase.Data,
|
57
57
|
) -> None:
|
58
58
|
self._file[(*self._keywords, keyword)] = data
|
59
59
|
|
@@ -98,8 +98,9 @@ class FoamFile(
|
|
98
98
|
def version(self) -> float:
|
99
99
|
"""Alias of `self["FoamFile", "version"]`."""
|
100
100
|
ret = self["FoamFile", "version"]
|
101
|
-
if not isinstance(ret, float):
|
102
|
-
|
101
|
+
if not isinstance(ret, (int, float)):
|
102
|
+
msg = "version is not a number"
|
103
|
+
raise TypeError(msg)
|
103
104
|
return ret
|
104
105
|
|
105
106
|
@version.setter
|
@@ -111,9 +112,11 @@ class FoamFile(
|
|
111
112
|
"""Alias of `self["FoamFile", "format"]`."""
|
112
113
|
ret = self["FoamFile", "format"]
|
113
114
|
if not isinstance(ret, str):
|
114
|
-
|
115
|
+
msg = "format is not a string"
|
116
|
+
raise TypeError(msg)
|
115
117
|
if ret not in ("ascii", "binary"):
|
116
|
-
|
118
|
+
msg = "format is not 'ascii' or 'binary'"
|
119
|
+
raise ValueError(msg)
|
117
120
|
return cast(Literal["ascii", "binary"], ret)
|
118
121
|
|
119
122
|
@format.setter
|
@@ -125,7 +128,8 @@ class FoamFile(
|
|
125
128
|
"""Alias of `self["FoamFile", "class"]`."""
|
126
129
|
ret = self["FoamFile", "class"]
|
127
130
|
if not isinstance(ret, str):
|
128
|
-
|
131
|
+
msg = "class is not a string"
|
132
|
+
raise TypeError(msg)
|
129
133
|
return ret
|
130
134
|
|
131
135
|
@class_.setter
|
@@ -137,7 +141,8 @@ class FoamFile(
|
|
137
141
|
"""Alias of `self["FoamFile", "location"]`."""
|
138
142
|
ret = self["FoamFile", "location"]
|
139
143
|
if not isinstance(ret, str):
|
140
|
-
|
144
|
+
msg = "location is not a string"
|
145
|
+
raise TypeError(msg)
|
141
146
|
return ret
|
142
147
|
|
143
148
|
@location.setter
|
@@ -149,7 +154,8 @@ class FoamFile(
|
|
149
154
|
"""Alias of `self["FoamFile", "object"]`."""
|
150
155
|
ret = self["FoamFile", "object"]
|
151
156
|
if not isinstance(ret, str):
|
152
|
-
|
157
|
+
msg = "object is not a string"
|
158
|
+
raise TypeError(msg)
|
153
159
|
return ret
|
154
160
|
|
155
161
|
@object_.setter
|
@@ -157,30 +163,32 @@ class FoamFile(
|
|
157
163
|
self["FoamFile", "object"] = value
|
158
164
|
|
159
165
|
def __getitem__(
|
160
|
-
self, keywords:
|
161
|
-
) ->
|
166
|
+
self, keywords: str | tuple[str, ...] | None
|
167
|
+
) -> FoamFileBase._DataEntry | FoamFile.SubDict:
|
162
168
|
if not keywords:
|
163
169
|
keywords = ()
|
164
170
|
elif not isinstance(keywords, tuple):
|
165
171
|
keywords = (keywords,)
|
166
172
|
|
167
|
-
|
173
|
+
parsed = self._get_parsed()
|
168
174
|
|
169
175
|
value = parsed[keywords]
|
170
176
|
|
177
|
+
assert not isinstance(value, Mapping)
|
178
|
+
|
171
179
|
if value is ...:
|
172
180
|
return FoamFile.SubDict(self, keywords)
|
173
|
-
return value
|
181
|
+
return deepcopy(value)
|
174
182
|
|
175
183
|
def __setitem__(
|
176
|
-
self, keywords:
|
184
|
+
self, keywords: str | tuple[str, ...] | None, data: FoamFileBase.Data
|
177
185
|
) -> None:
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
keywords = (keywords,)
|
186
|
+
if not keywords:
|
187
|
+
keywords = ()
|
188
|
+
elif not isinstance(keywords, tuple):
|
189
|
+
keywords = (keywords,)
|
183
190
|
|
191
|
+
with self:
|
184
192
|
try:
|
185
193
|
write_header = (
|
186
194
|
not self and "FoamFile" not in self and keywords != ("FoamFile",)
|
@@ -213,91 +221,104 @@ class FoamFile(
|
|
213
221
|
kind = Kind.DIMENSIONS
|
214
222
|
|
215
223
|
if (
|
216
|
-
kind
|
224
|
+
kind in (Kind.FIELD, Kind.BINARY_FIELD)
|
217
225
|
) and self.class_ == "dictionary":
|
218
|
-
if
|
219
|
-
class_ = "volScalarField"
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
+
if isinstance(data, (int, float)):
|
227
|
+
self.class_ = "volScalarField"
|
228
|
+
|
229
|
+
elif is_sequence(data) and data:
|
230
|
+
if isinstance(data[0], (int, float)):
|
231
|
+
if len(data) == 3:
|
232
|
+
self.class_ = "volVectorField"
|
233
|
+
elif len(data) == 6:
|
234
|
+
self.class_ = "volSymmTensorField"
|
235
|
+
elif len(data) == 9:
|
236
|
+
self.class_ = "volTensorField"
|
237
|
+
elif (
|
238
|
+
is_sequence(data[0])
|
239
|
+
and data[0]
|
240
|
+
and isinstance(data[0][0], (int, float))
|
241
|
+
):
|
242
|
+
if len(data[0]) == 3:
|
243
|
+
self.class_ = "volVectorField"
|
244
|
+
elif len(data[0]) == 6:
|
245
|
+
self.class_ = "volSymmTensorField"
|
246
|
+
elif len(data[0]) == 9:
|
247
|
+
self.class_ = "volTensorField"
|
248
|
+
|
249
|
+
parsed = self._get_parsed(missing_ok=True)
|
250
|
+
|
251
|
+
start, end = parsed.entry_location(keywords, missing_ok=True)
|
252
|
+
|
253
|
+
if start and not parsed.contents[:start].endswith(b"\n\n"):
|
254
|
+
if parsed.contents[:start].endswith(b"\n"):
|
255
|
+
before = b"\n" if len(keywords) <= 1 else b""
|
226
256
|
else:
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
self[keywords] = data
|
257
|
+
before = b"\n\n" if len(keywords) <= 1 else b"\n"
|
258
|
+
else:
|
259
|
+
before = b""
|
231
260
|
|
261
|
+
if not parsed.contents[end:].strip() or parsed.contents[end:].startswith(
|
262
|
+
b"}"
|
263
|
+
):
|
264
|
+
after = b"\n" + b" " * (len(keywords) - 2)
|
232
265
|
else:
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
if
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
self._write(
|
256
|
-
before
|
257
|
-
+ indentation
|
258
|
-
+ dumps(keywords[-1])
|
259
|
-
+ b"\n"
|
260
|
-
+ indentation
|
261
|
-
+ b"{\n"
|
262
|
-
+ indentation
|
263
|
-
+ b"}"
|
264
|
-
+ after
|
265
|
-
)
|
266
|
-
|
267
|
-
for k, v in data.items():
|
268
|
-
self[(*keywords, k)] = v
|
269
|
-
|
270
|
-
elif keywords:
|
271
|
-
self._write(
|
272
|
-
before
|
273
|
-
+ indentation
|
274
|
-
+ dumps(keywords[-1])
|
275
|
-
+ b" "
|
276
|
-
+ dumps(data, kind=kind)
|
277
|
-
+ b";"
|
278
|
-
+ after
|
279
|
-
)
|
266
|
+
after = b""
|
267
|
+
|
268
|
+
indentation = b" " * (len(keywords) - 1)
|
269
|
+
|
270
|
+
if isinstance(data, Mapping):
|
271
|
+
if isinstance(data, (FoamFile, FoamFile.SubDict)):
|
272
|
+
data = data.as_dict()
|
273
|
+
|
274
|
+
parsed.put(
|
275
|
+
keywords,
|
276
|
+
...,
|
277
|
+
before
|
278
|
+
+ indentation
|
279
|
+
+ dumps(keywords[-1])
|
280
|
+
+ b"\n"
|
281
|
+
+ indentation
|
282
|
+
+ b"{\n"
|
283
|
+
+ indentation
|
284
|
+
+ b"}"
|
285
|
+
+ after,
|
286
|
+
)
|
280
287
|
|
281
|
-
|
282
|
-
self
|
288
|
+
for k, v in data.items():
|
289
|
+
self[(*keywords, k)] = v
|
290
|
+
|
291
|
+
elif keywords:
|
292
|
+
parsed.put(
|
293
|
+
keywords,
|
294
|
+
deepcopy(data),
|
295
|
+
before
|
296
|
+
+ indentation
|
297
|
+
+ dumps(keywords[-1])
|
298
|
+
+ b" "
|
299
|
+
+ dumps(data, kind=kind)
|
300
|
+
+ b";"
|
301
|
+
+ after,
|
302
|
+
)
|
303
|
+
|
304
|
+
else:
|
305
|
+
parsed.put((), deepcopy(data), before + dumps(data, kind=kind) + after)
|
283
306
|
|
284
|
-
def __delitem__(self, keywords:
|
307
|
+
def __delitem__(self, keywords: str | tuple[str, ...] | None) -> None:
|
285
308
|
if not keywords:
|
286
309
|
keywords = ()
|
287
310
|
elif not isinstance(keywords, tuple):
|
288
311
|
keywords = (keywords,)
|
289
312
|
|
290
|
-
|
291
|
-
|
292
|
-
start, end = parsed.entry_location(keywords)
|
293
|
-
|
294
|
-
self._write(contents[:start] + contents[end:])
|
313
|
+
with self:
|
314
|
+
del self._get_parsed()[keywords]
|
295
315
|
|
296
|
-
def _iter(self, keywords:
|
297
|
-
|
298
|
-
|
316
|
+
def _iter(self, keywords: tuple[str, ...] = ()) -> Iterator[str | None]:
|
317
|
+
yield from (
|
318
|
+
k[-1] if k else None for k in self._get_parsed() if k[:-1] == keywords
|
319
|
+
)
|
299
320
|
|
300
|
-
def __iter__(self) -> Iterator[
|
321
|
+
def __iter__(self) -> Iterator[str | None]:
|
301
322
|
yield from (k for k in self._iter() if k != "FoamFile")
|
302
323
|
|
303
324
|
def __contains__(self, keywords: object) -> bool:
|
@@ -306,9 +327,7 @@ class FoamFile(
|
|
306
327
|
elif not isinstance(keywords, tuple):
|
307
328
|
keywords = (keywords,)
|
308
329
|
|
309
|
-
|
310
|
-
|
311
|
-
return keywords in parsed
|
330
|
+
return keywords in self._get_parsed()
|
312
331
|
|
313
332
|
def __len__(self) -> int:
|
314
333
|
return len(list(iter(self)))
|
@@ -330,22 +349,22 @@ class FoamFile(
|
|
330
349
|
|
331
350
|
:param include_header: Whether to include the "FoamFile" header in the output.
|
332
351
|
"""
|
333
|
-
|
334
|
-
d = parsed.as_dict()
|
352
|
+
d = self._get_parsed().as_dict()
|
335
353
|
if not include_header:
|
336
354
|
d.pop("FoamFile", None)
|
337
|
-
return d
|
355
|
+
return deepcopy(d)
|
338
356
|
|
339
357
|
|
340
358
|
class FoamFieldFile(FoamFile):
|
341
359
|
"""An OpenFOAM dictionary file representing a field as a mutable mapping."""
|
342
360
|
|
343
361
|
class BoundariesSubDict(FoamFile.SubDict):
|
344
|
-
def __getitem__(self, keyword: str) ->
|
362
|
+
def __getitem__(self, keyword: str) -> FoamFieldFile.BoundarySubDict:
|
345
363
|
value = super().__getitem__(keyword)
|
346
364
|
if not isinstance(value, FoamFieldFile.BoundarySubDict):
|
347
365
|
assert not isinstance(value, FoamFile.SubDict)
|
348
|
-
|
366
|
+
msg = f"boundary {keyword} is not a dictionary"
|
367
|
+
raise TypeError(msg)
|
349
368
|
return value
|
350
369
|
|
351
370
|
class BoundarySubDict(FoamFile.SubDict):
|
@@ -356,7 +375,8 @@ class FoamFieldFile(FoamFile):
|
|
356
375
|
"""Alias of `self["type"]`."""
|
357
376
|
ret = self["type"]
|
358
377
|
if not isinstance(ret, str):
|
359
|
-
|
378
|
+
msg = "type is not a string"
|
379
|
+
raise TypeError(msg)
|
360
380
|
return ret
|
361
381
|
|
362
382
|
@type.setter
|
@@ -366,36 +386,17 @@ class FoamFieldFile(FoamFile):
|
|
366
386
|
@property
|
367
387
|
def value(
|
368
388
|
self,
|
369
|
-
) ->
|
370
|
-
int,
|
371
|
-
float,
|
372
|
-
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
373
|
-
"np.ndarray[Tuple[()], np.dtype[np.generic]]",
|
374
|
-
"np.ndarray[Tuple[int], np.dtype[np.generic]]",
|
375
|
-
"np.ndarray[Tuple[int, int], np.dtype[np.generic]]",
|
376
|
-
]:
|
389
|
+
) -> FoamFile._Field:
|
377
390
|
"""Alias of `self["value"]`."""
|
378
|
-
ret = self["value"]
|
379
|
-
if not isinstance(ret, (int, float, Sequence)):
|
380
|
-
raise TypeError("value is not a field")
|
381
391
|
return cast(
|
382
|
-
|
383
|
-
|
384
|
-
],
|
385
|
-
ret,
|
392
|
+
FoamFile._Field,
|
393
|
+
self["value"],
|
386
394
|
)
|
387
395
|
|
388
396
|
@value.setter
|
389
397
|
def value(
|
390
398
|
self,
|
391
|
-
value:
|
392
|
-
int,
|
393
|
-
float,
|
394
|
-
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
395
|
-
"np.ndarray[Tuple[()], np.dtype[np.generic]]",
|
396
|
-
"np.ndarray[Tuple[int], np.dtype[np.generic]]",
|
397
|
-
"np.ndarray[Tuple[int, int], np.dtype[np.generic]]",
|
398
|
-
],
|
399
|
+
value: FoamFile._Field,
|
399
400
|
) -> None:
|
400
401
|
self["value"] = value
|
401
402
|
|
@@ -404,8 +405,8 @@ class FoamFieldFile(FoamFile):
|
|
404
405
|
del self["value"]
|
405
406
|
|
406
407
|
def __getitem__(
|
407
|
-
self, keywords:
|
408
|
-
) ->
|
408
|
+
self, keywords: str | tuple[str, ...] | None
|
409
|
+
) -> FoamFileBase._DataEntry | FoamFile.SubDict:
|
409
410
|
if not keywords:
|
410
411
|
keywords = ()
|
411
412
|
elif not isinstance(keywords, tuple):
|
@@ -420,57 +421,40 @@ class FoamFieldFile(FoamFile):
|
|
420
421
|
return ret
|
421
422
|
|
422
423
|
@property
|
423
|
-
def dimensions(self) ->
|
424
|
+
def dimensions(self) -> FoamFile.DimensionSet | Sequence[float]:
|
424
425
|
"""Alias of `self["dimensions"]`."""
|
425
426
|
ret = self["dimensions"]
|
426
427
|
if not isinstance(ret, FoamFile.DimensionSet):
|
427
|
-
|
428
|
+
msg = "dimensions is not a DimensionSet"
|
429
|
+
raise TypeError(msg)
|
428
430
|
return ret
|
429
431
|
|
430
432
|
@dimensions.setter
|
431
|
-
def dimensions(
|
432
|
-
self, value: Union[FoamFile.DimensionSet, Sequence[Union[int, float]]]
|
433
|
-
) -> None:
|
433
|
+
def dimensions(self, value: FoamFile.DimensionSet | Sequence[float]) -> None:
|
434
434
|
self["dimensions"] = value
|
435
435
|
|
436
436
|
@property
|
437
437
|
def internal_field(
|
438
438
|
self,
|
439
|
-
) ->
|
440
|
-
int,
|
441
|
-
float,
|
442
|
-
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
443
|
-
"np.ndarray[Tuple[()], np.dtype[np.generic]]",
|
444
|
-
"np.ndarray[Tuple[int], np.dtype[np.generic]]",
|
445
|
-
"np.ndarray[Tuple[int, int], np.dtype[np.generic]]",
|
446
|
-
]:
|
439
|
+
) -> FoamFile._Field:
|
447
440
|
"""Alias of `self["internalField"]`."""
|
448
|
-
|
449
|
-
if not isinstance(ret, (int, float, Sequence)):
|
450
|
-
raise TypeError("internalField is not a field")
|
451
|
-
return cast(Union[int, float, Sequence[Union[int, float]]], ret)
|
441
|
+
return cast(FoamFile._Field, self["internalField"])
|
452
442
|
|
453
443
|
@internal_field.setter
|
454
444
|
def internal_field(
|
455
445
|
self,
|
456
|
-
value:
|
457
|
-
int,
|
458
|
-
float,
|
459
|
-
Sequence[Union[int, float, Sequence[Union[int, float]]]],
|
460
|
-
"np.ndarray[Tuple[()], np.dtype[np.generic]]",
|
461
|
-
"np.ndarray[Tuple[int], np.dtype[np.generic]]",
|
462
|
-
"np.ndarray[Tuple[int, int], np.dtype[np.generic]]",
|
463
|
-
],
|
446
|
+
value: FoamFile._Field,
|
464
447
|
) -> None:
|
465
448
|
self["internalField"] = value
|
466
449
|
|
467
450
|
@property
|
468
|
-
def boundary_field(self) ->
|
451
|
+
def boundary_field(self) -> FoamFieldFile.BoundariesSubDict:
|
469
452
|
"""Alias of `self["boundaryField"]`."""
|
470
453
|
ret = self["boundaryField"]
|
471
454
|
if not isinstance(ret, FoamFieldFile.BoundariesSubDict):
|
472
455
|
assert not isinstance(ret, FoamFile.SubDict)
|
473
|
-
|
456
|
+
msg = "boundaryField is not a dictionary"
|
457
|
+
raise TypeError(msg)
|
474
458
|
return ret
|
475
459
|
|
476
460
|
@boundary_field.setter
|
foamlib/_files/_io.py
CHANGED
@@ -1,14 +1,10 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import gzip
|
2
4
|
import sys
|
3
|
-
from copy import deepcopy
|
4
5
|
from pathlib import Path
|
5
|
-
from types import TracebackType
|
6
6
|
from typing import (
|
7
7
|
TYPE_CHECKING,
|
8
|
-
Optional,
|
9
|
-
Tuple,
|
10
|
-
Type,
|
11
|
-
Union,
|
12
8
|
)
|
13
9
|
|
14
10
|
if sys.version_info >= (3, 11):
|
@@ -20,71 +16,64 @@ from ._parsing import Parsed
|
|
20
16
|
|
21
17
|
if TYPE_CHECKING:
|
22
18
|
import os
|
19
|
+
from types import TracebackType
|
23
20
|
|
24
21
|
|
25
22
|
class FoamFileIO:
|
26
|
-
def __init__(self, path:
|
23
|
+
def __init__(self, path: os.PathLike[str] | str) -> None:
|
27
24
|
self.path = Path(path).absolute()
|
28
25
|
|
29
|
-
self.
|
30
|
-
self.
|
26
|
+
self.__parsed: Parsed | None = None
|
27
|
+
self.__missing: bool | None = None
|
31
28
|
self.__defer_io = 0
|
32
|
-
self.__dirty = False
|
33
29
|
|
34
30
|
def __enter__(self) -> Self:
|
35
31
|
if self.__defer_io == 0:
|
36
|
-
self.
|
32
|
+
self._get_parsed(missing_ok=True)
|
37
33
|
self.__defer_io += 1
|
38
34
|
return self
|
39
35
|
|
40
36
|
def __exit__(
|
41
37
|
self,
|
42
|
-
exc_type:
|
43
|
-
exc_val:
|
44
|
-
exc_tb:
|
38
|
+
exc_type: type[BaseException] | None,
|
39
|
+
exc_val: BaseException | None,
|
40
|
+
exc_tb: TracebackType | None,
|
45
41
|
) -> None:
|
46
42
|
self.__defer_io -= 1
|
47
|
-
if self.__defer_io == 0
|
48
|
-
assert self.
|
49
|
-
self.
|
43
|
+
if self.__defer_io == 0:
|
44
|
+
assert self.__parsed is not None
|
45
|
+
if self.__parsed.modified:
|
46
|
+
contents = self.__parsed.contents
|
47
|
+
|
48
|
+
if self.path.suffix == ".gz":
|
49
|
+
contents = gzip.compress(contents)
|
50
|
+
|
51
|
+
self.path.write_bytes(contents)
|
52
|
+
self.__parsed.modified = False
|
53
|
+
self.__missing = False
|
50
54
|
|
51
|
-
def
|
55
|
+
def _get_parsed(self, *, missing_ok: bool = False) -> Parsed:
|
52
56
|
if not self.__defer_io:
|
53
57
|
try:
|
54
58
|
contents = self.path.read_bytes()
|
55
59
|
except FileNotFoundError:
|
56
|
-
|
60
|
+
self.__missing = True
|
61
|
+
contents = b""
|
57
62
|
else:
|
58
|
-
|
63
|
+
self.__missing = False
|
59
64
|
if self.path.suffix == ".gz":
|
60
65
|
contents = gzip.decompress(contents)
|
61
66
|
|
62
|
-
if contents !=
|
63
|
-
self.
|
64
|
-
self.__parsed = None
|
67
|
+
if self.__parsed is None or self.__parsed.contents != contents:
|
68
|
+
self.__parsed = Parsed(contents)
|
65
69
|
|
66
|
-
|
67
|
-
|
68
|
-
return b"", Parsed(b"")
|
69
|
-
raise FileNotFoundError(self.path)
|
70
|
-
|
71
|
-
if self.__parsed is None:
|
72
|
-
parsed = Parsed(self.__contents)
|
73
|
-
self.__parsed = parsed
|
74
|
-
|
75
|
-
return self.__contents, deepcopy(self.__parsed)
|
70
|
+
assert self.__parsed is not None
|
71
|
+
assert self.__missing is not None
|
76
72
|
|
77
|
-
|
78
|
-
|
79
|
-
self.__parsed = None
|
80
|
-
if not self.__defer_io:
|
81
|
-
if self.path.suffix == ".gz":
|
82
|
-
contents = gzip.compress(contents)
|
73
|
+
if self.__missing and not self.__parsed.modified and not missing_ok:
|
74
|
+
raise FileNotFoundError(self.path)
|
83
75
|
|
84
|
-
|
85
|
-
self.__dirty = False
|
86
|
-
else:
|
87
|
-
self.__dirty = True
|
76
|
+
return self.__parsed
|
88
77
|
|
89
78
|
def __repr__(self) -> str:
|
90
79
|
return f"{type(self).__qualname__}('{self.path}')"
|