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
foamlib/__init__.py
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
"""A Python interface for interacting with OpenFOAM."""
|
2
2
|
|
3
|
-
__version__ = "0.
|
3
|
+
__version__ = "0.5.0"
|
4
4
|
|
5
5
|
from ._cases import (
|
6
6
|
AsyncFoamCase,
|
7
7
|
CalledProcessError,
|
8
|
-
CalledProcessWarning,
|
9
8
|
FoamCase,
|
10
9
|
FoamCaseBase,
|
11
10
|
)
|
@@ -19,5 +18,4 @@ __all__ = [
|
|
19
18
|
"FoamFieldFile",
|
20
19
|
"FoamFileBase",
|
21
20
|
"CalledProcessError",
|
22
|
-
"CalledProcessWarning",
|
23
21
|
]
|
foamlib/_cases/__init__.py
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
from ._async import AsyncFoamCase
|
2
2
|
from ._base import FoamCaseBase
|
3
|
+
from ._subprocess import CalledProcessError
|
3
4
|
from ._sync import FoamCase
|
4
|
-
from ._util import CalledProcessError, CalledProcessWarning
|
5
5
|
|
6
6
|
__all__ = [
|
7
7
|
"FoamCaseBase",
|
8
8
|
"FoamCase",
|
9
9
|
"AsyncFoamCase",
|
10
10
|
"CalledProcessError",
|
11
|
-
"CalledProcessWarning",
|
12
11
|
]
|
foamlib/_cases/_async.py
CHANGED
@@ -1,26 +1,34 @@
|
|
1
1
|
import asyncio
|
2
2
|
import multiprocessing
|
3
|
+
import os
|
3
4
|
import sys
|
5
|
+
import tempfile
|
4
6
|
from contextlib import asynccontextmanager
|
5
7
|
from pathlib import Path
|
6
8
|
from typing import (
|
9
|
+
Callable,
|
7
10
|
Optional,
|
8
11
|
Union,
|
9
12
|
)
|
10
13
|
|
11
14
|
if sys.version_info >= (3, 9):
|
12
|
-
from collections.abc import AsyncGenerator, Sequence
|
15
|
+
from collections.abc import AsyncGenerator, Collection, Sequence
|
13
16
|
else:
|
14
|
-
from typing import AsyncGenerator, Sequence
|
17
|
+
from typing import AsyncGenerator, Collection, Sequence
|
18
|
+
|
19
|
+
if sys.version_info >= (3, 11):
|
20
|
+
from typing import Self
|
21
|
+
else:
|
22
|
+
from typing_extensions import Self
|
15
23
|
|
16
24
|
import aioshutil
|
17
25
|
|
18
|
-
from
|
19
|
-
from .
|
20
|
-
from ._util import
|
26
|
+
from ._recipes import _FoamCaseRecipes
|
27
|
+
from ._subprocess import run_async
|
28
|
+
from ._util import awaitableasynccontextmanager
|
21
29
|
|
22
30
|
|
23
|
-
class AsyncFoamCase(
|
31
|
+
class AsyncFoamCase(_FoamCaseRecipes):
|
24
32
|
"""
|
25
33
|
An OpenFOAM case with asynchronous support.
|
26
34
|
|
@@ -61,6 +69,24 @@ class AsyncFoamCase(FoamCaseBase):
|
|
61
69
|
AsyncFoamCase._reserved_cpus -= cpus
|
62
70
|
AsyncFoamCase._cpus_cond.notify(cpus)
|
63
71
|
|
72
|
+
@staticmethod
|
73
|
+
async def _rmtree(
|
74
|
+
path: Union["os.PathLike[str]", str], ignore_errors: bool = False
|
75
|
+
) -> None:
|
76
|
+
await aioshutil.rmtree(path, ignore_errors=ignore_errors) # type: ignore [call-arg]
|
77
|
+
|
78
|
+
@staticmethod
|
79
|
+
async def _copytree(
|
80
|
+
src: Union["os.PathLike[str]", str],
|
81
|
+
dest: Union["os.PathLike[str]", str],
|
82
|
+
*,
|
83
|
+
symlinks: bool = False,
|
84
|
+
ignore: Optional[
|
85
|
+
Callable[[Union["os.PathLike[str]", str], Collection[str]], Collection[str]]
|
86
|
+
] = None,
|
87
|
+
) -> None:
|
88
|
+
await aioshutil.copytree(src, dest, symlinks=symlinks, ignore=ignore)
|
89
|
+
|
64
90
|
async def clean(
|
65
91
|
self,
|
66
92
|
*,
|
@@ -73,74 +99,63 @@ class AsyncFoamCase(FoamCaseBase):
|
|
73
99
|
:param script: If True, use an (All)clean script if it exists. If False, ignore any clean scripts.
|
74
100
|
:param check: If True, raise a CalledProcessError if the clean script returns a non-zero exit code.
|
75
101
|
"""
|
76
|
-
|
77
|
-
|
78
|
-
if script_path is not None:
|
79
|
-
await self.run([script_path], check=check)
|
80
|
-
else:
|
81
|
-
for p in self._clean_paths():
|
82
|
-
if p.is_dir():
|
83
|
-
await aioshutil.rmtree(p) # type: ignore [call-arg]
|
84
|
-
else:
|
85
|
-
p.unlink()
|
102
|
+
for name, args, kwargs in self._clean_cmds(script=script, check=check):
|
103
|
+
await getattr(self, name)(*args, **kwargs)
|
86
104
|
|
87
105
|
async def _run(
|
88
106
|
self,
|
89
|
-
cmd: Union[Sequence[Union[str,
|
107
|
+
cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
|
90
108
|
*,
|
109
|
+
parallel: bool = False,
|
110
|
+
cpus: int = 1,
|
91
111
|
check: bool = True,
|
112
|
+
log: bool = True,
|
92
113
|
) -> None:
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
*cmd,
|
107
|
-
cwd=self.path,
|
108
|
-
env=self._env(shell=False),
|
109
|
-
stdout=asyncio.subprocess.DEVNULL,
|
110
|
-
stderr=asyncio.subprocess.PIPE if check else asyncio.subprocess.DEVNULL,
|
111
|
-
)
|
112
|
-
|
113
|
-
stdout, stderr = await proc.communicate()
|
114
|
-
|
115
|
-
assert stdout is None
|
116
|
-
assert proc.returncode is not None
|
114
|
+
with self._output(cmd, log=log) as (stdout, stderr):
|
115
|
+
if parallel:
|
116
|
+
if isinstance(cmd, str):
|
117
|
+
cmd = [
|
118
|
+
"mpiexec",
|
119
|
+
"-n",
|
120
|
+
str(cpus),
|
121
|
+
"/bin/sh",
|
122
|
+
"-c",
|
123
|
+
f"{cmd} -parallel",
|
124
|
+
]
|
125
|
+
else:
|
126
|
+
cmd = ["mpiexec", "-n", str(cpus), *cmd, "-parallel"]
|
117
127
|
|
118
|
-
|
119
|
-
|
128
|
+
async with self._cpus(cpus):
|
129
|
+
await run_async(
|
130
|
+
cmd,
|
131
|
+
check=check,
|
132
|
+
cwd=self.path,
|
133
|
+
env=self._env(shell=isinstance(cmd, str)),
|
134
|
+
stdout=stdout,
|
135
|
+
stderr=stderr,
|
136
|
+
)
|
120
137
|
|
121
138
|
async def run(
|
122
139
|
self,
|
123
|
-
cmd: Optional[Union[Sequence[Union[str,
|
140
|
+
cmd: Optional[Union[Sequence[Union[str, "os.PathLike[str]"]], str]] = None,
|
124
141
|
*,
|
125
142
|
script: bool = True,
|
126
143
|
parallel: Optional[bool] = None,
|
127
144
|
cpus: Optional[int] = None,
|
128
145
|
check: bool = True,
|
129
146
|
) -> None:
|
147
|
+
"""
|
148
|
+
Run this case, or a specified command in the context of this case.
|
149
|
+
|
150
|
+
:param cmd: The command to run. If None, run the case. If a sequence, the first element is the command and the rest are arguments. If a string, `cmd` is executed in a shell.
|
151
|
+
:param script: If True and `cmd` is None, use an (All)run(-parallel) script if it exists for running the case. If False or no run script is found, autodetermine the command(s) needed to run the case.
|
152
|
+
:param parallel: If True, run in parallel using MPI. If None, autodetect whether to run in parallel.
|
153
|
+
:param check: If True, raise a CalledProcessError if any command returns a non-zero exit code.
|
154
|
+
"""
|
130
155
|
for name, args, kwargs in self._run_cmds(
|
131
|
-
cmd=cmd, script=script, parallel=parallel, check=check
|
156
|
+
cmd=cmd, script=script, parallel=parallel, cpus=cpus, check=check
|
132
157
|
):
|
133
|
-
|
134
|
-
if name == "run":
|
135
|
-
if kwargs.get("parallel", False):
|
136
|
-
cpus = max(self._nprocessors, 1)
|
137
|
-
else:
|
138
|
-
cpus = 1
|
139
|
-
else:
|
140
|
-
cpus = 0
|
141
|
-
|
142
|
-
async with self._cpus(cpus):
|
143
|
-
await getattr(self, name)(*args, **kwargs)
|
158
|
+
await getattr(self, name)(*args, **kwargs)
|
144
159
|
|
145
160
|
async def block_mesh(self, *, check: bool = True) -> None:
|
146
161
|
"""Run blockMesh on this case."""
|
@@ -156,32 +171,63 @@ class AsyncFoamCase(FoamCaseBase):
|
|
156
171
|
|
157
172
|
async def restore_0_dir(self) -> None:
|
158
173
|
"""Restore the 0 directory from the 0.orig directory."""
|
159
|
-
|
160
|
-
|
174
|
+
for name, args, kwargs in self._restore_0_dir_cmds():
|
175
|
+
await getattr(self, name)(*args, **kwargs)
|
161
176
|
|
162
|
-
|
177
|
+
@awaitableasynccontextmanager
|
178
|
+
@asynccontextmanager
|
179
|
+
async def copy(
|
180
|
+
self, dst: Optional[Union["os.PathLike[str]", str]] = None
|
181
|
+
) -> "AsyncGenerator[Self]":
|
163
182
|
"""
|
164
183
|
Make a copy of this case.
|
165
184
|
|
166
|
-
|
185
|
+
Use as an async context manager to automatically delete the copy when done.
|
186
|
+
|
187
|
+
:param dst: The destination path. If None, copy to a temporary directory.
|
167
188
|
"""
|
168
|
-
|
189
|
+
if dst is None:
|
190
|
+
dst = Path(tempfile.mkdtemp(), self.name)
|
191
|
+
tmp = True
|
192
|
+
else:
|
193
|
+
tmp = False
|
194
|
+
|
195
|
+
for name, args, kwargs in self._copy_cmds(dst):
|
196
|
+
await getattr(self, name)(*args, **kwargs)
|
197
|
+
|
198
|
+
yield type(self)(dst)
|
199
|
+
|
200
|
+
if tmp:
|
201
|
+
assert isinstance(dst, Path)
|
202
|
+
await self._rmtree(dst.parent)
|
203
|
+
else:
|
204
|
+
await self._rmtree(dst)
|
169
205
|
|
170
|
-
|
206
|
+
@awaitableasynccontextmanager
|
207
|
+
@asynccontextmanager
|
208
|
+
async def clone(
|
209
|
+
self, dst: Optional[Union["os.PathLike[str]", str]] = None
|
210
|
+
) -> "AsyncGenerator[Self]":
|
171
211
|
"""
|
172
212
|
Clone this case (make a clean copy).
|
173
213
|
|
174
|
-
|
214
|
+
Use as an async context manager to automatically delete the clone when done.
|
215
|
+
|
216
|
+
:param dst: The destination path. If None, clone to a temporary directory.
|
175
217
|
"""
|
176
|
-
if
|
177
|
-
|
178
|
-
|
179
|
-
|
218
|
+
if dst is None:
|
219
|
+
dst = Path(tempfile.mkdtemp(), self.name)
|
220
|
+
tmp = True
|
221
|
+
else:
|
222
|
+
tmp = False
|
180
223
|
|
181
|
-
|
224
|
+
for name, args, kwargs in self._clone_cmds(dst):
|
225
|
+
await getattr(self, name)(*args, **kwargs)
|
182
226
|
|
183
|
-
|
184
|
-
self.path, dest, symlinks=True, ignore=self._clone_ignore()
|
185
|
-
)
|
227
|
+
yield type(self)(dst)
|
186
228
|
|
187
|
-
|
229
|
+
if tmp:
|
230
|
+
assert isinstance(dst, Path)
|
231
|
+
await self._rmtree(dst.parent)
|
232
|
+
else:
|
233
|
+
await self._rmtree(dst)
|
foamlib/_cases/_base.py
CHANGED
@@ -3,41 +3,29 @@ import shutil
|
|
3
3
|
import sys
|
4
4
|
from pathlib import Path
|
5
5
|
from typing import (
|
6
|
-
Any,
|
7
6
|
Optional,
|
8
|
-
Tuple,
|
9
7
|
Union,
|
10
8
|
overload,
|
11
9
|
)
|
12
10
|
|
13
11
|
if sys.version_info >= (3, 9):
|
14
12
|
from collections.abc import (
|
15
|
-
Callable,
|
16
|
-
Collection,
|
17
|
-
Generator,
|
18
13
|
Iterator,
|
19
|
-
Mapping,
|
20
14
|
Sequence,
|
21
15
|
Set,
|
22
16
|
)
|
23
17
|
else:
|
24
18
|
from typing import AbstractSet as Set
|
25
19
|
from typing import (
|
26
|
-
Callable,
|
27
|
-
Collection,
|
28
|
-
Generator,
|
29
20
|
Iterator,
|
30
|
-
Mapping,
|
31
21
|
Sequence,
|
32
22
|
)
|
33
23
|
|
34
|
-
|
35
24
|
from .._files import FoamFieldFile, FoamFile
|
36
|
-
from .._util import is_sequence
|
37
25
|
|
38
26
|
|
39
27
|
class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
|
40
|
-
def __init__(self, path: Union[
|
28
|
+
def __init__(self, path: Union["os.PathLike[str]", str] = Path()):
|
41
29
|
self.path = Path(path).absolute()
|
42
30
|
|
43
31
|
class TimeDirectory(Set[FoamFieldFile]):
|
@@ -49,7 +37,7 @@ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
|
|
49
37
|
:param path: The path to the time directory.
|
50
38
|
"""
|
51
39
|
|
52
|
-
def __init__(self, path: Union[
|
40
|
+
def __init__(self, path: Union["os.PathLike[str]", str]):
|
53
41
|
self.path = Path(path).absolute()
|
54
42
|
|
55
43
|
@property
|
@@ -142,184 +130,15 @@ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
|
|
142
130
|
def __len__(self) -> int:
|
143
131
|
return len(self._times)
|
144
132
|
|
145
|
-
def _clean_paths(self) -> Set[Path]:
|
146
|
-
has_decompose_par_dict = (self.path / "system" / "decomposeParDict").is_file()
|
147
|
-
has_block_mesh_dict = (self.path / "system" / "blockMeshDict").is_file()
|
148
|
-
|
149
|
-
paths = set()
|
150
|
-
|
151
|
-
for p in self.path.iterdir():
|
152
|
-
if p.is_dir():
|
153
|
-
try:
|
154
|
-
t = float(p.name)
|
155
|
-
except ValueError:
|
156
|
-
pass
|
157
|
-
else:
|
158
|
-
if t != 0:
|
159
|
-
paths.add(p)
|
160
|
-
|
161
|
-
if has_decompose_par_dict and p.name.startswith("processor"):
|
162
|
-
paths.add(p)
|
163
|
-
|
164
|
-
if (self.path / "0.orig").is_dir() and (self.path / "0").is_dir():
|
165
|
-
paths.add(self.path / "0")
|
166
|
-
|
167
|
-
if has_block_mesh_dict and (self.path / "constant" / "polyMesh").exists():
|
168
|
-
paths.add(self.path / "constant" / "polyMesh")
|
169
|
-
|
170
|
-
if self._run_script() is not None:
|
171
|
-
paths.update(self.path.glob("log.*"))
|
172
|
-
|
173
|
-
return paths
|
174
|
-
|
175
133
|
def __delitem__(self, key: Union[int, float, str]) -> None:
|
176
134
|
shutil.rmtree(self[key].path)
|
177
135
|
|
178
|
-
def _clone_ignore(
|
179
|
-
self,
|
180
|
-
) -> Callable[[Union[Path, str], Collection[str]], Collection[str]]:
|
181
|
-
clean_paths = self._clean_paths()
|
182
|
-
|
183
|
-
def ignore(path: Union[Path, str], names: Collection[str]) -> Collection[str]:
|
184
|
-
paths = {Path(path) / name for name in names}
|
185
|
-
return {p.name for p in paths.intersection(clean_paths)}
|
186
|
-
|
187
|
-
return ignore
|
188
|
-
|
189
|
-
def _clean_script(self) -> Optional[Path]:
|
190
|
-
"""Return the path to the (All)clean script, or None if no clean script is found."""
|
191
|
-
clean = self.path / "clean"
|
192
|
-
all_clean = self.path / "Allclean"
|
193
|
-
|
194
|
-
if clean.is_file():
|
195
|
-
script = clean
|
196
|
-
elif all_clean.is_file():
|
197
|
-
script = all_clean
|
198
|
-
else:
|
199
|
-
return None
|
200
|
-
|
201
|
-
if sys.argv and Path(sys.argv[0]).absolute() == script.absolute():
|
202
|
-
return None
|
203
|
-
|
204
|
-
return script if Path(sys.argv[0]).absolute() != script.absolute() else None
|
205
|
-
|
206
|
-
def _run_script(self, *, parallel: Optional[bool] = None) -> Optional[Path]:
|
207
|
-
"""Return the path to the (All)run script, or None if no run script is found."""
|
208
|
-
run = self.path / "run"
|
209
|
-
run_parallel = self.path / "run-parallel"
|
210
|
-
all_run = self.path / "Allrun"
|
211
|
-
all_run_parallel = self.path / "Allrun-parallel"
|
212
|
-
|
213
|
-
if run.is_file() or all_run.is_file():
|
214
|
-
if run_parallel.is_file() or all_run_parallel.is_file():
|
215
|
-
if parallel:
|
216
|
-
script = (
|
217
|
-
run_parallel if run_parallel.is_file() else all_run_parallel
|
218
|
-
)
|
219
|
-
elif parallel is False:
|
220
|
-
script = run if run.is_file() else all_run
|
221
|
-
else:
|
222
|
-
raise ValueError(
|
223
|
-
"Both (All)run and (All)run-parallel scripts are present. Please specify parallel argument."
|
224
|
-
)
|
225
|
-
else:
|
226
|
-
script = run if run.is_file() else all_run
|
227
|
-
elif parallel is not False and (
|
228
|
-
run_parallel.is_file() or all_run_parallel.is_file()
|
229
|
-
):
|
230
|
-
script = run_parallel if run_parallel.is_file() else all_run_parallel
|
231
|
-
else:
|
232
|
-
return None
|
233
|
-
|
234
|
-
if sys.argv and Path(sys.argv[0]).absolute() == script.absolute():
|
235
|
-
return None
|
236
|
-
|
237
|
-
return script
|
238
|
-
|
239
|
-
def _env(self, *, shell: bool) -> Optional[Mapping[str, str]]:
|
240
|
-
sip_workaround = os.environ.get(
|
241
|
-
"FOAM_LD_LIBRARY_PATH", ""
|
242
|
-
) and not os.environ.get("DYLD_LIBRARY_PATH", "")
|
243
|
-
|
244
|
-
if not shell or sip_workaround:
|
245
|
-
env = os.environ.copy()
|
246
|
-
|
247
|
-
if not shell:
|
248
|
-
env["PWD"] = str(self.path)
|
249
|
-
|
250
|
-
if sip_workaround:
|
251
|
-
env["DYLD_LIBRARY_PATH"] = env["FOAM_LD_LIBRARY_PATH"]
|
252
|
-
|
253
|
-
return env
|
254
|
-
else:
|
255
|
-
return None
|
256
|
-
|
257
|
-
def _run_cmds(
|
258
|
-
self,
|
259
|
-
cmd: Optional[Union[Sequence[Union[str, Path]], str, Path]] = None,
|
260
|
-
*,
|
261
|
-
script: bool = True,
|
262
|
-
parallel: Optional[bool] = None,
|
263
|
-
check: bool = True,
|
264
|
-
) -> Generator[Tuple[str, Sequence[Any], Mapping[str, Any]], None, None]:
|
265
|
-
if cmd is not None:
|
266
|
-
if parallel:
|
267
|
-
cmd = self._parallel_cmd(cmd)
|
268
|
-
|
269
|
-
yield ("_run", (cmd,), {"check": check})
|
270
|
-
else:
|
271
|
-
script_path = self._run_script(parallel=parallel) if script else None
|
272
|
-
|
273
|
-
if script_path is not None:
|
274
|
-
yield ("_run", ([script_path],), {"check": check})
|
275
|
-
|
276
|
-
else:
|
277
|
-
if not self and (self.path / "0.orig").is_dir():
|
278
|
-
yield ("restore_0_dir", (), {})
|
279
|
-
|
280
|
-
if (self.path / "system" / "blockMeshDict").is_file():
|
281
|
-
yield ("block_mesh", (), {"check": check})
|
282
|
-
|
283
|
-
if parallel is None:
|
284
|
-
parallel = (
|
285
|
-
self._nprocessors > 0
|
286
|
-
or (self.path / "system" / "decomposeParDict").is_file()
|
287
|
-
)
|
288
|
-
|
289
|
-
if parallel:
|
290
|
-
if (
|
291
|
-
self._nprocessors == 0
|
292
|
-
and (self.path / "system" / "decomposeParDict").is_file()
|
293
|
-
):
|
294
|
-
yield ("decompose_par", (), {"check": check})
|
295
|
-
|
296
|
-
yield (
|
297
|
-
"run",
|
298
|
-
([self.application],),
|
299
|
-
{"parallel": parallel, "check": check},
|
300
|
-
)
|
301
|
-
|
302
|
-
def _parallel_cmd(
|
303
|
-
self, cmd: Union[Sequence[Union[str, Path]], str, Path]
|
304
|
-
) -> Union[Sequence[Union[str, Path]], str]:
|
305
|
-
if not is_sequence(cmd):
|
306
|
-
return f"mpiexec -np {self._nprocessors} {cmd} -parallel"
|
307
|
-
else:
|
308
|
-
return [
|
309
|
-
"mpiexec",
|
310
|
-
"-np",
|
311
|
-
str(self._nprocessors),
|
312
|
-
cmd[0],
|
313
|
-
"-parallel",
|
314
|
-
*cmd[1:],
|
315
|
-
]
|
316
|
-
|
317
136
|
@property
|
318
137
|
def name(self) -> str:
|
319
138
|
"""The name of the case."""
|
320
139
|
return self.path.name
|
321
140
|
|
322
|
-
def file(self, path: Union[
|
141
|
+
def file(self, path: Union["os.PathLike[str]", str]) -> FoamFile:
|
323
142
|
"""Return a FoamFile object for the given path in the case."""
|
324
143
|
return FoamFile(self.path / path)
|
325
144
|
|