foamlib 0.4.3__py3-none-any.whl → 0.5.0__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 -3
- foamlib/_cases/__init__.py +1 -2
- foamlib/_cases/_async.py +118 -72
- foamlib/_cases/_base.py +3 -184
- foamlib/_cases/_recipes.py +299 -0
- foamlib/_cases/_subprocess.py +86 -0
- foamlib/_cases/_sync.py +95 -54
- foamlib/_cases/_util.py +20 -28
- foamlib/_files/_files.py +4 -8
- foamlib/_files/_parsing.py +53 -46
- foamlib/_files/_serialization.py +1 -1
- {foamlib-0.4.3.dist-info → foamlib-0.5.0.dist-info}/METADATA +13 -5
- foamlib-0.5.0.dist-info/RECORD +21 -0
- {foamlib-0.4.3.dist-info → foamlib-0.5.0.dist-info}/WHEEL +1 -1
- foamlib-0.4.3.dist-info/RECORD +0 -19
- /foamlib/{_util.py → _files/_util.py} +0 -0
- {foamlib-0.4.3.dist-info → foamlib-0.5.0.dist-info}/LICENSE.txt +0 -0
- {foamlib-0.4.3.dist-info → foamlib-0.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,299 @@
|
|
1
|
+
import os
|
2
|
+
import shlex
|
3
|
+
import shutil
|
4
|
+
import sys
|
5
|
+
from contextlib import contextmanager
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import (
|
8
|
+
IO,
|
9
|
+
Any,
|
10
|
+
Optional,
|
11
|
+
Tuple,
|
12
|
+
Union,
|
13
|
+
)
|
14
|
+
|
15
|
+
if sys.version_info >= (3, 9):
|
16
|
+
from collections.abc import (
|
17
|
+
Callable,
|
18
|
+
Collection,
|
19
|
+
Generator,
|
20
|
+
Mapping,
|
21
|
+
Sequence,
|
22
|
+
Set,
|
23
|
+
)
|
24
|
+
else:
|
25
|
+
from typing import AbstractSet as Set
|
26
|
+
from typing import (
|
27
|
+
Callable,
|
28
|
+
Collection,
|
29
|
+
Generator,
|
30
|
+
Mapping,
|
31
|
+
Sequence,
|
32
|
+
)
|
33
|
+
|
34
|
+
from ._base import FoamCaseBase
|
35
|
+
from ._subprocess import DEVNULL, STDOUT
|
36
|
+
|
37
|
+
|
38
|
+
class _FoamCaseRecipes(FoamCaseBase):
|
39
|
+
def _clean_paths(self) -> Set[Path]:
|
40
|
+
has_decompose_par_dict = (self.path / "system" / "decomposeParDict").is_file()
|
41
|
+
has_block_mesh_dict = (self.path / "system" / "blockMeshDict").is_file()
|
42
|
+
|
43
|
+
paths = set()
|
44
|
+
|
45
|
+
for p in self.path.iterdir():
|
46
|
+
if p.is_dir():
|
47
|
+
try:
|
48
|
+
t = float(p.name)
|
49
|
+
except ValueError:
|
50
|
+
pass
|
51
|
+
else:
|
52
|
+
if t != 0:
|
53
|
+
paths.add(p)
|
54
|
+
|
55
|
+
if has_decompose_par_dict and p.name.startswith("processor"):
|
56
|
+
paths.add(p)
|
57
|
+
|
58
|
+
if (self.path / "0.orig").is_dir() and (self.path / "0").is_dir():
|
59
|
+
paths.add(self.path / "0")
|
60
|
+
|
61
|
+
if has_block_mesh_dict and (self.path / "constant" / "polyMesh").exists():
|
62
|
+
paths.add(self.path / "constant" / "polyMesh")
|
63
|
+
|
64
|
+
if self._run_script() is not None:
|
65
|
+
paths.update(self.path.glob("log.*"))
|
66
|
+
|
67
|
+
return paths
|
68
|
+
|
69
|
+
def __delitem__(self, key: Union[int, float, str]) -> None:
|
70
|
+
shutil.rmtree(self[key].path)
|
71
|
+
|
72
|
+
def _clone_ignore(
|
73
|
+
self,
|
74
|
+
) -> Callable[[Union["os.PathLike[str]", str], Collection[str]], Collection[str]]:
|
75
|
+
clean_paths = self._clean_paths()
|
76
|
+
|
77
|
+
def ignore(
|
78
|
+
path: Union["os.PathLike[str]", str], names: Collection[str]
|
79
|
+
) -> Collection[str]:
|
80
|
+
paths = {Path(path) / name for name in names}
|
81
|
+
return {p.name for p in paths.intersection(clean_paths)}
|
82
|
+
|
83
|
+
return ignore
|
84
|
+
|
85
|
+
def _clean_script(self) -> Optional[Path]:
|
86
|
+
"""Return the path to the (All)clean script, or None if no clean script is found."""
|
87
|
+
clean = self.path / "clean"
|
88
|
+
all_clean = self.path / "Allclean"
|
89
|
+
|
90
|
+
if clean.is_file():
|
91
|
+
script = clean
|
92
|
+
elif all_clean.is_file():
|
93
|
+
script = all_clean
|
94
|
+
else:
|
95
|
+
return None
|
96
|
+
|
97
|
+
if sys.argv and Path(sys.argv[0]).absolute() == script.absolute():
|
98
|
+
return None
|
99
|
+
|
100
|
+
return script if Path(sys.argv[0]).absolute() != script.absolute() else None
|
101
|
+
|
102
|
+
def _run_script(self, *, parallel: Optional[bool] = None) -> Optional[Path]:
|
103
|
+
"""Return the path to the (All)run script, or None if no run script is found."""
|
104
|
+
run = self.path / "run"
|
105
|
+
run_parallel = self.path / "run-parallel"
|
106
|
+
all_run = self.path / "Allrun"
|
107
|
+
all_run_parallel = self.path / "Allrun-parallel"
|
108
|
+
|
109
|
+
if run.is_file() or all_run.is_file():
|
110
|
+
if run_parallel.is_file() or all_run_parallel.is_file():
|
111
|
+
if parallel:
|
112
|
+
script = (
|
113
|
+
run_parallel if run_parallel.is_file() else all_run_parallel
|
114
|
+
)
|
115
|
+
elif parallel is False:
|
116
|
+
script = run if run.is_file() else all_run
|
117
|
+
else:
|
118
|
+
raise ValueError(
|
119
|
+
"Both (All)run and (All)run-parallel scripts are present. Please specify parallel argument."
|
120
|
+
)
|
121
|
+
else:
|
122
|
+
script = run if run.is_file() else all_run
|
123
|
+
elif parallel is not False and (
|
124
|
+
run_parallel.is_file() or all_run_parallel.is_file()
|
125
|
+
):
|
126
|
+
script = run_parallel if run_parallel.is_file() else all_run_parallel
|
127
|
+
else:
|
128
|
+
return None
|
129
|
+
|
130
|
+
if sys.argv and Path(sys.argv[0]).absolute() == script.absolute():
|
131
|
+
return None
|
132
|
+
|
133
|
+
return script
|
134
|
+
|
135
|
+
def _env(self, *, shell: bool) -> Optional[Mapping[str, str]]:
|
136
|
+
sip_workaround = os.environ.get(
|
137
|
+
"FOAM_LD_LIBRARY_PATH", ""
|
138
|
+
) and not os.environ.get("DYLD_LIBRARY_PATH", "")
|
139
|
+
|
140
|
+
if not shell or sip_workaround:
|
141
|
+
env = os.environ.copy()
|
142
|
+
|
143
|
+
if not shell:
|
144
|
+
env["PWD"] = str(self.path)
|
145
|
+
|
146
|
+
if sip_workaround:
|
147
|
+
env["DYLD_LIBRARY_PATH"] = env["FOAM_LD_LIBRARY_PATH"]
|
148
|
+
|
149
|
+
return env
|
150
|
+
else:
|
151
|
+
return None
|
152
|
+
|
153
|
+
@contextmanager
|
154
|
+
def _output(
|
155
|
+
self, cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str], *, log: bool
|
156
|
+
) -> Generator[Tuple[Union[int, IO[bytes]], Union[int, IO[bytes]]], None, None]:
|
157
|
+
if log:
|
158
|
+
if isinstance(cmd, str):
|
159
|
+
name = shlex.split(cmd)[0]
|
160
|
+
else:
|
161
|
+
if isinstance(cmd[0], os.PathLike):
|
162
|
+
name = Path(cmd[0]).name
|
163
|
+
else:
|
164
|
+
name = cmd[0]
|
165
|
+
|
166
|
+
with (self.path / f"log.{name}").open("ab") as stdout:
|
167
|
+
yield stdout, STDOUT
|
168
|
+
else:
|
169
|
+
yield DEVNULL, DEVNULL
|
170
|
+
|
171
|
+
def _copy_cmds(
|
172
|
+
self, dest: Union["os.PathLike[str]", str]
|
173
|
+
) -> Generator[Tuple[str, Sequence[Any], Mapping[str, Any]], None, None]:
|
174
|
+
yield (
|
175
|
+
"_copytree",
|
176
|
+
(
|
177
|
+
self.path,
|
178
|
+
dest,
|
179
|
+
),
|
180
|
+
{"symlinks": True},
|
181
|
+
)
|
182
|
+
|
183
|
+
def _clean_cmds(
|
184
|
+
self, *, script: bool = True, check: bool = False
|
185
|
+
) -> Generator[Tuple[str, Sequence[Any], Mapping[str, Any]], None, None]:
|
186
|
+
script_path = self._clean_script() if script else None
|
187
|
+
|
188
|
+
if script_path is not None:
|
189
|
+
yield ("_run", ([script_path],), {"cpus": 0, "check": check, "log": False})
|
190
|
+
else:
|
191
|
+
for p in self._clean_paths():
|
192
|
+
if p.is_dir():
|
193
|
+
yield ("_rmtree", (p,), {})
|
194
|
+
else:
|
195
|
+
p.unlink()
|
196
|
+
|
197
|
+
def _clone_cmds(
|
198
|
+
self, dest: Union["os.PathLike[str]", str]
|
199
|
+
) -> Generator[Tuple[str, Sequence[Any], Mapping[str, Any]], None, None]:
|
200
|
+
if self._clean_script() is not None:
|
201
|
+
yield ("copy", (dest,), {})
|
202
|
+
yield ("clean", (), {})
|
203
|
+
else:
|
204
|
+
yield (
|
205
|
+
"_copytree",
|
206
|
+
(
|
207
|
+
self.path,
|
208
|
+
dest,
|
209
|
+
),
|
210
|
+
{"symlinks": True, "ignore": self._clone_ignore()},
|
211
|
+
)
|
212
|
+
|
213
|
+
def _restore_0_dir_cmds(
|
214
|
+
self,
|
215
|
+
) -> Generator[Tuple[str, Sequence[Any], Mapping[str, Any]], None, None]:
|
216
|
+
yield ("_rmtree", (self.path / "0",), {"ignore_errors": True})
|
217
|
+
yield (
|
218
|
+
"_copytree",
|
219
|
+
(
|
220
|
+
self.path / "0.orig",
|
221
|
+
self.path / "0",
|
222
|
+
),
|
223
|
+
{"symlinks": True},
|
224
|
+
)
|
225
|
+
|
226
|
+
def _run_cmds(
|
227
|
+
self,
|
228
|
+
cmd: Optional[Union[Sequence[Union[str, "os.PathLike[str]"]], str]] = None,
|
229
|
+
*,
|
230
|
+
script: bool = True,
|
231
|
+
parallel: Optional[bool] = None,
|
232
|
+
cpus: Optional[int] = None,
|
233
|
+
check: bool = True,
|
234
|
+
) -> Generator[Tuple[str, Sequence[Any], Mapping[str, Any]], None, None]:
|
235
|
+
if cmd is not None:
|
236
|
+
if parallel:
|
237
|
+
if cpus is None:
|
238
|
+
cpus = max(self._nprocessors, 1)
|
239
|
+
else:
|
240
|
+
parallel = False
|
241
|
+
if cpus is None:
|
242
|
+
cpus = 1
|
243
|
+
|
244
|
+
yield ("_run", (cmd,), {"parallel": parallel, "cpus": cpus, "check": check})
|
245
|
+
|
246
|
+
else:
|
247
|
+
script_path = self._run_script(parallel=parallel) if script else None
|
248
|
+
|
249
|
+
if script_path is not None:
|
250
|
+
if parallel or parallel is None:
|
251
|
+
if cpus is None:
|
252
|
+
if self._nprocessors > 0:
|
253
|
+
cpus = self._nprocessors
|
254
|
+
elif (self.path / "system" / "decomposeParDict").is_file():
|
255
|
+
cpus = self._nsubdomains
|
256
|
+
else:
|
257
|
+
cpus = 1
|
258
|
+
else:
|
259
|
+
if cpus is None:
|
260
|
+
cpus = 1
|
261
|
+
|
262
|
+
yield (
|
263
|
+
"_run",
|
264
|
+
([script_path],),
|
265
|
+
{"parallel": False, "cpus": cpus, "check": check},
|
266
|
+
)
|
267
|
+
|
268
|
+
else:
|
269
|
+
if not self and (self.path / "0.orig").is_dir():
|
270
|
+
yield ("restore_0_dir", (), {})
|
271
|
+
|
272
|
+
if (self.path / "system" / "blockMeshDict").is_file():
|
273
|
+
yield ("block_mesh", (), {"check": check})
|
274
|
+
|
275
|
+
if parallel is None:
|
276
|
+
parallel = (
|
277
|
+
(cpus is not None and cpus > 1)
|
278
|
+
or self._nprocessors > 0
|
279
|
+
or (self.path / "system" / "decomposeParDict").is_file()
|
280
|
+
)
|
281
|
+
|
282
|
+
if parallel:
|
283
|
+
if (
|
284
|
+
self._nprocessors == 0
|
285
|
+
and (self.path / "system" / "decomposeParDict").is_file()
|
286
|
+
):
|
287
|
+
yield ("decompose_par", (), {"check": check})
|
288
|
+
|
289
|
+
if cpus is None:
|
290
|
+
cpus = max(self._nprocessors, 1)
|
291
|
+
else:
|
292
|
+
if cpus is None:
|
293
|
+
cpus = 1
|
294
|
+
|
295
|
+
yield (
|
296
|
+
"_run",
|
297
|
+
([self.application],),
|
298
|
+
{"parallel": parallel, "cpus": cpus, "check": check},
|
299
|
+
)
|
@@ -0,0 +1,86 @@
|
|
1
|
+
import asyncio
|
2
|
+
import os
|
3
|
+
import subprocess
|
4
|
+
import sys
|
5
|
+
from typing import IO, Optional, Union
|
6
|
+
|
7
|
+
if sys.version_info >= (3, 9):
|
8
|
+
from collections.abc import Mapping, Sequence
|
9
|
+
else:
|
10
|
+
from typing import Mapping, Sequence
|
11
|
+
|
12
|
+
CalledProcessError = subprocess.CalledProcessError
|
13
|
+
CompletedProcess = subprocess.CompletedProcess
|
14
|
+
|
15
|
+
DEVNULL = subprocess.DEVNULL
|
16
|
+
PIPE = subprocess.PIPE
|
17
|
+
STDOUT = subprocess.STDOUT
|
18
|
+
|
19
|
+
|
20
|
+
def run_sync(
|
21
|
+
cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
|
22
|
+
*,
|
23
|
+
check: bool = True,
|
24
|
+
cwd: Optional["os.PathLike[str]"] = None,
|
25
|
+
env: Optional[Mapping[str, str]] = None,
|
26
|
+
stdout: Optional[Union[int, IO[bytes]]] = None,
|
27
|
+
stderr: Optional[Union[int, IO[bytes]]] = None,
|
28
|
+
) -> "CompletedProcess[bytes]":
|
29
|
+
if not isinstance(cmd, str) and sys.version_info < (3, 8):
|
30
|
+
cmd = [str(arg) for arg in cmd]
|
31
|
+
|
32
|
+
return subprocess.run(
|
33
|
+
cmd,
|
34
|
+
cwd=cwd,
|
35
|
+
env=env,
|
36
|
+
stdout=stdout,
|
37
|
+
stderr=stderr,
|
38
|
+
shell=isinstance(cmd, str),
|
39
|
+
check=check,
|
40
|
+
)
|
41
|
+
|
42
|
+
|
43
|
+
async def run_async(
|
44
|
+
cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
|
45
|
+
*,
|
46
|
+
check: bool = True,
|
47
|
+
cwd: Optional["os.PathLike[str]"] = None,
|
48
|
+
env: Optional[Mapping[str, str]] = None,
|
49
|
+
stdout: Optional[Union[int, IO[bytes]]] = None,
|
50
|
+
stderr: Optional[Union[int, IO[bytes]]] = None,
|
51
|
+
) -> "CompletedProcess[bytes]":
|
52
|
+
if isinstance(cmd, str):
|
53
|
+
proc = await asyncio.create_subprocess_shell(
|
54
|
+
cmd,
|
55
|
+
cwd=cwd,
|
56
|
+
env=env,
|
57
|
+
stdout=stdout,
|
58
|
+
stderr=stderr,
|
59
|
+
)
|
60
|
+
|
61
|
+
else:
|
62
|
+
if sys.version_info < (3, 8):
|
63
|
+
cmd = [str(arg) for arg in cmd]
|
64
|
+
proc = await asyncio.create_subprocess_exec(
|
65
|
+
*cmd,
|
66
|
+
cwd=cwd,
|
67
|
+
env=env,
|
68
|
+
stdout=stdout,
|
69
|
+
stderr=stderr,
|
70
|
+
)
|
71
|
+
|
72
|
+
output, error = await proc.communicate()
|
73
|
+
|
74
|
+
assert proc.returncode is not None
|
75
|
+
|
76
|
+
if check and proc.returncode != 0:
|
77
|
+
raise CalledProcessError(
|
78
|
+
returncode=proc.returncode,
|
79
|
+
cmd=cmd,
|
80
|
+
output=output,
|
81
|
+
stderr=error,
|
82
|
+
)
|
83
|
+
|
84
|
+
return CompletedProcess(
|
85
|
+
cmd, returncode=proc.returncode, stdout=output, stderr=error
|
86
|
+
)
|
foamlib/_cases/_sync.py
CHANGED
@@ -1,23 +1,31 @@
|
|
1
|
+
import os
|
1
2
|
import shutil
|
2
|
-
import subprocess
|
3
3
|
import sys
|
4
|
+
import tempfile
|
4
5
|
from pathlib import Path
|
6
|
+
from types import TracebackType
|
5
7
|
from typing import (
|
8
|
+
Callable,
|
6
9
|
Optional,
|
10
|
+
Type,
|
7
11
|
Union,
|
8
12
|
)
|
9
13
|
|
10
14
|
if sys.version_info >= (3, 9):
|
11
|
-
from collections.abc import Sequence
|
15
|
+
from collections.abc import Collection, Sequence
|
12
16
|
else:
|
13
|
-
from typing import Sequence
|
17
|
+
from typing import Collection, Sequence
|
14
18
|
|
15
|
-
|
16
|
-
from
|
17
|
-
|
19
|
+
if sys.version_info >= (3, 11):
|
20
|
+
from typing import Self
|
21
|
+
else:
|
22
|
+
from typing_extensions import Self
|
23
|
+
|
24
|
+
from ._recipes import _FoamCaseRecipes
|
25
|
+
from ._subprocess import run_sync
|
18
26
|
|
19
27
|
|
20
|
-
class FoamCase(
|
28
|
+
class FoamCase(_FoamCaseRecipes):
|
21
29
|
"""
|
22
30
|
An OpenFOAM case.
|
23
31
|
|
@@ -28,6 +36,35 @@ class FoamCase(FoamCaseBase):
|
|
28
36
|
:param path: The path to the case directory.
|
29
37
|
"""
|
30
38
|
|
39
|
+
@staticmethod
|
40
|
+
def _rmtree(
|
41
|
+
path: Union["os.PathLike[str]", str], *, ignore_errors: bool = False
|
42
|
+
) -> None:
|
43
|
+
shutil.rmtree(path, ignore_errors=ignore_errors)
|
44
|
+
|
45
|
+
@staticmethod
|
46
|
+
def _copytree(
|
47
|
+
src: Union["os.PathLike[str]", str],
|
48
|
+
dest: Union["os.PathLike[str]", str],
|
49
|
+
*,
|
50
|
+
symlinks: bool = False,
|
51
|
+
ignore: Optional[
|
52
|
+
Callable[[Union["os.PathLike[str]", str], Collection[str]], Collection[str]]
|
53
|
+
] = None,
|
54
|
+
) -> None:
|
55
|
+
shutil.copytree(src, dest, symlinks=symlinks, ignore=ignore)
|
56
|
+
|
57
|
+
def __enter__(self) -> "FoamCase":
|
58
|
+
return self
|
59
|
+
|
60
|
+
def __exit__(
|
61
|
+
self,
|
62
|
+
exc_type: Optional[Type[BaseException]],
|
63
|
+
exc_val: Optional[BaseException],
|
64
|
+
exc_tb: Optional[TracebackType],
|
65
|
+
) -> None:
|
66
|
+
self._rmtree(self.path)
|
67
|
+
|
31
68
|
def clean(
|
32
69
|
self,
|
33
70
|
*,
|
@@ -40,47 +77,44 @@ class FoamCase(FoamCaseBase):
|
|
40
77
|
:param script: If True, use an (All)clean script if it exists. If False, ignore any clean scripts.
|
41
78
|
:param check: If True, raise a CalledProcessError if the clean script returns a non-zero exit code.
|
42
79
|
"""
|
43
|
-
|
44
|
-
|
45
|
-
if script_path is not None:
|
46
|
-
self.run([script_path], check=check)
|
47
|
-
else:
|
48
|
-
for p in self._clean_paths():
|
49
|
-
if p.is_dir():
|
50
|
-
shutil.rmtree(p)
|
51
|
-
else:
|
52
|
-
p.unlink()
|
80
|
+
for name, args, kwargs in self._clean_cmds(script=script, check=check):
|
81
|
+
getattr(self, name)(*args, **kwargs)
|
53
82
|
|
54
83
|
def _run(
|
55
84
|
self,
|
56
|
-
cmd: Union[Sequence[Union[str,
|
85
|
+
cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
|
57
86
|
*,
|
87
|
+
parallel: bool = False,
|
88
|
+
cpus: int = 1,
|
58
89
|
check: bool = True,
|
90
|
+
log: bool = True,
|
59
91
|
) -> None:
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
92
|
+
with self._output(cmd, log=log) as (stdout, stderr):
|
93
|
+
if parallel:
|
94
|
+
if isinstance(cmd, str):
|
95
|
+
cmd = [
|
96
|
+
"mpiexec",
|
97
|
+
"-n",
|
98
|
+
str(cpus),
|
99
|
+
"/bin/sh",
|
100
|
+
"-c",
|
101
|
+
f"{cmd} -parallel",
|
102
|
+
]
|
103
|
+
else:
|
104
|
+
cmd = ["mpiexec", "-n", str(cpus), *cmd, "-parallel"]
|
105
|
+
|
106
|
+
run_sync(
|
107
|
+
cmd,
|
108
|
+
check=check,
|
109
|
+
cwd=self.path,
|
110
|
+
env=self._env(shell=isinstance(cmd, str)),
|
111
|
+
stdout=stdout,
|
112
|
+
stderr=stderr,
|
113
|
+
)
|
80
114
|
|
81
115
|
def run(
|
82
116
|
self,
|
83
|
-
cmd: Optional[Union[Sequence[Union[str,
|
117
|
+
cmd: Optional[Union[Sequence[Union[str, "os.PathLike[str]"]], str]] = None,
|
84
118
|
*,
|
85
119
|
script: bool = True,
|
86
120
|
parallel: Optional[bool] = None,
|
@@ -113,30 +147,37 @@ class FoamCase(FoamCaseBase):
|
|
113
147
|
|
114
148
|
def restore_0_dir(self) -> None:
|
115
149
|
"""Restore the 0 directory from the 0.orig directory."""
|
116
|
-
|
117
|
-
|
150
|
+
for name, args, kwargs in self._restore_0_dir_cmds():
|
151
|
+
getattr(self, name)(*args, **kwargs)
|
118
152
|
|
119
|
-
def copy(self,
|
153
|
+
def copy(self, dst: Optional[Union["os.PathLike[str]", str]] = None) -> "Self":
|
120
154
|
"""
|
121
155
|
Make a copy of this case.
|
122
156
|
|
123
|
-
|
157
|
+
Use as a context manager to automatically delete the copy when done.
|
158
|
+
|
159
|
+
:param dst: The destination path. If None, copy to a temporary directory.
|
124
160
|
"""
|
125
|
-
|
161
|
+
if dst is None:
|
162
|
+
dst = Path(tempfile.mkdtemp(), self.name)
|
126
163
|
|
127
|
-
|
164
|
+
for name, args, kwargs in self._copy_cmds(dst):
|
165
|
+
getattr(self, name)(*args, **kwargs)
|
166
|
+
|
167
|
+
return type(self)(dst)
|
168
|
+
|
169
|
+
def clone(self, dst: Optional[Union["os.PathLike[str]", str]] = None) -> "Self":
|
128
170
|
"""
|
129
171
|
Clone this case (make a clean copy).
|
130
172
|
|
131
|
-
|
132
|
-
"""
|
133
|
-
if self._clean_script() is not None:
|
134
|
-
copy = self.copy(dest)
|
135
|
-
copy.clean()
|
136
|
-
return copy
|
173
|
+
Use as a context manager to automatically delete the clone when done.
|
137
174
|
|
138
|
-
|
175
|
+
:param dst: The destination path. If None, clone to a temporary directory.
|
176
|
+
"""
|
177
|
+
if dst is None:
|
178
|
+
dst = Path(tempfile.mkdtemp(), self.name)
|
139
179
|
|
140
|
-
|
180
|
+
for name, args, kwargs in self._clone_cmds(dst):
|
181
|
+
getattr(self, name)(*args, **kwargs)
|
141
182
|
|
142
|
-
return
|
183
|
+
return type(self)(dst)
|
foamlib/_cases/_util.py
CHANGED
@@ -1,35 +1,27 @@
|
|
1
|
-
import
|
2
|
-
import
|
3
|
-
from pathlib import Path
|
4
|
-
from typing import Optional, Union
|
5
|
-
from warnings import warn
|
1
|
+
from types import TracebackType
|
2
|
+
from typing import Any, AsyncContextManager, Callable, Optional, Type
|
6
3
|
|
7
|
-
if sys.version_info >= (3, 9):
|
8
|
-
from collections.abc import Sequence
|
9
|
-
else:
|
10
|
-
from typing import Sequence
|
11
4
|
|
5
|
+
class _AwaitableAsyncContextManager:
|
6
|
+
def __init__(self, cm: "AsyncContextManager[Any]"):
|
7
|
+
self._cm = cm
|
12
8
|
|
13
|
-
|
14
|
-
|
9
|
+
def __await__(self) -> Any:
|
10
|
+
return self._cm.__aenter__().__await__()
|
15
11
|
|
16
|
-
def
|
17
|
-
|
18
|
-
if self.stderr:
|
19
|
-
msg += f"\n{self.stderr}"
|
20
|
-
return msg
|
12
|
+
async def __aenter__(self) -> Any:
|
13
|
+
return await self._cm.__aenter__()
|
21
14
|
|
15
|
+
async def __aexit__(
|
16
|
+
self,
|
17
|
+
exc_type: Optional[Type[BaseException]],
|
18
|
+
exc_val: Optional[BaseException],
|
19
|
+
exc_tb: Optional[TracebackType],
|
20
|
+
) -> Any:
|
21
|
+
return await self._cm.__aexit__(exc_type, exc_val, exc_tb)
|
22
22
|
|
23
|
-
class CalledProcessWarning(Warning):
|
24
|
-
"""Warning raised when a process prints to stderr and `check=True`."""
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
stderr: Optional[str],
|
31
|
-
) -> None:
|
32
|
-
if retcode != 0:
|
33
|
-
raise CalledProcessError(retcode, cmd, None, stderr)
|
34
|
-
elif stderr:
|
35
|
-
warn(f"Command {cmd} printed to stderr.\n{stderr}", CalledProcessWarning)
|
24
|
+
def awaitableasynccontextmanager(
|
25
|
+
cm: Callable[..., "AsyncContextManager[Any]"],
|
26
|
+
) -> Callable[..., _AwaitableAsyncContextManager]:
|
27
|
+
return lambda *args, **kwargs: _AwaitableAsyncContextManager(cm(*args, **kwargs))
|
foamlib/_files/_files.py
CHANGED
@@ -16,10 +16,10 @@ if sys.version_info >= (3, 11):
|
|
16
16
|
else:
|
17
17
|
from typing_extensions import Self
|
18
18
|
|
19
|
-
from .._util import is_sequence
|
20
19
|
from ._base import FoamFileBase
|
21
20
|
from ._io import _FoamFileIO
|
22
21
|
from ._serialization import Kind, dumpb
|
22
|
+
from ._util import is_sequence
|
23
23
|
|
24
24
|
try:
|
25
25
|
import numpy as np
|
@@ -105,13 +105,9 @@ class FoamFile(
|
|
105
105
|
"""
|
106
106
|
Create the file.
|
107
107
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
If False (the default), raise a FileExistsError if the file already exists.
|
112
|
-
If True, do nothing if the file already exists.
|
113
|
-
parents : bool, optional
|
114
|
-
If True, also create parent directories as needed.
|
108
|
+
:param exist_ok: If False, raise a FileExistsError if the file already exists.
|
109
|
+
|
110
|
+
:param parents: If True, also create parent directories as needed.
|
115
111
|
"""
|
116
112
|
if self.path.exists():
|
117
113
|
if not exist_ok:
|