foamlib 0.5.1__py3-none-any.whl → 0.6.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  """A Python interface for interacting with OpenFOAM."""
2
2
 
3
- __version__ = "0.5.1"
3
+ __version__ = "0.6.0"
4
4
 
5
5
  from ._cases import (
6
6
  AsyncFoamCase,
foamlib/_cases/_async.py CHANGED
@@ -2,10 +2,8 @@ import asyncio
2
2
  import multiprocessing
3
3
  import os
4
4
  import sys
5
- import tempfile
6
5
  from contextlib import asynccontextmanager
7
- from pathlib import Path
8
- from typing import Callable, Optional, TypeVar, Union
6
+ from typing import Any, Callable, Optional, TypeVar, Union
9
7
 
10
8
  if sys.version_info >= (3, 9):
11
9
  from collections.abc import (
@@ -25,15 +23,15 @@ else:
25
23
 
26
24
  import aioshutil
27
25
 
28
- from ._recipes import _FoamCaseRecipes
26
+ from ._run import FoamCaseRunBase
29
27
  from ._subprocess import run_async
30
- from ._util import awaitableasynccontextmanager
28
+ from ._util import ValuedGenerator, awaitableasynccontextmanager
31
29
 
32
30
  X = TypeVar("X")
33
31
  Y = TypeVar("Y")
34
32
 
35
33
 
36
- class AsyncFoamCase(_FoamCaseRecipes):
34
+ class AsyncFoamCase(FoamCaseRunBase):
37
35
  """
38
36
  An OpenFOAM case with asynchronous support.
39
37
 
@@ -50,14 +48,11 @@ class AsyncFoamCase(_FoamCaseRecipes):
50
48
  """
51
49
 
52
50
  _reserved_cpus = 0
53
- _cpus_cond = None # Cannot be initialized here yet
51
+ _cpus_cond = asyncio.Condition()
54
52
 
55
53
  @staticmethod
56
54
  @asynccontextmanager
57
55
  async def _cpus(cpus: int) -> AsyncGenerator[None, None]:
58
- if AsyncFoamCase._cpus_cond is None:
59
- AsyncFoamCase._cpus_cond = asyncio.Condition()
60
-
61
56
  cpus = min(cpus, AsyncFoamCase.max_cpus)
62
57
  if cpus > 0:
63
58
  async with AsyncFoamCase._cpus_cond:
@@ -74,6 +69,16 @@ class AsyncFoamCase(_FoamCaseRecipes):
74
69
  AsyncFoamCase._reserved_cpus -= cpus
75
70
  AsyncFoamCase._cpus_cond.notify(cpus)
76
71
 
72
+ @staticmethod
73
+ async def _run(
74
+ cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
75
+ *,
76
+ cpus: int,
77
+ **kwargs: Any,
78
+ ) -> None:
79
+ async with AsyncFoamCase._cpus(cpus):
80
+ await run_async(cmd, **kwargs)
81
+
77
82
  @staticmethod
78
83
  async def _rmtree(
79
84
  path: Union["os.PathLike[str]", str], ignore_errors: bool = False
@@ -92,75 +97,37 @@ class AsyncFoamCase(_FoamCaseRecipes):
92
97
  ) -> None:
93
98
  await aioshutil.copytree(src, dest, symlinks=symlinks, ignore=ignore)
94
99
 
95
- async def clean(
96
- self,
97
- *,
98
- script: bool = True,
99
- check: bool = False,
100
- ) -> None:
100
+ async def clean(self, *, check: bool = False) -> None:
101
101
  """
102
102
  Clean this case.
103
103
 
104
- :param script: If True, use an (All)clean script if it exists. If False, ignore any clean scripts.
105
104
  :param check: If True, raise a CalledProcessError if the clean script returns a non-zero exit code.
106
105
  """
107
- for name, args, kwargs in self._clean_cmds(script=script, check=check):
108
- await getattr(self, name)(*args, **kwargs)
109
-
110
- async def _run(
111
- self,
112
- cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
113
- *,
114
- parallel: bool = False,
115
- cpus: int = 1,
116
- check: bool = True,
117
- log: bool = True,
118
- ) -> None:
119
- with self._output(cmd, log=log) as (stdout, stderr):
120
- if parallel:
121
- if isinstance(cmd, str):
122
- cmd = [
123
- "mpiexec",
124
- "-n",
125
- str(cpus),
126
- "/bin/sh",
127
- "-c",
128
- f"{cmd} -parallel",
129
- ]
130
- else:
131
- cmd = ["mpiexec", "-n", str(cpus), *cmd, "-parallel"]
132
-
133
- async with self._cpus(cpus):
134
- await run_async(
135
- cmd,
136
- check=check,
137
- cwd=self.path,
138
- env=self._env(shell=isinstance(cmd, str)),
139
- stdout=stdout,
140
- stderr=stderr,
141
- )
106
+ for coro in self._clean_calls(check=check):
107
+ await coro
142
108
 
143
109
  async def run(
144
110
  self,
145
111
  cmd: Optional[Union[Sequence[Union[str, "os.PathLike[str]"]], str]] = None,
146
112
  *,
147
- script: bool = True,
148
113
  parallel: Optional[bool] = None,
149
114
  cpus: Optional[int] = None,
150
115
  check: bool = True,
116
+ log: bool = True,
151
117
  ) -> None:
152
118
  """
153
119
  Run this case, or a specified command in the context of this case.
154
120
 
155
121
  :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.
156
- :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.
157
122
  :param parallel: If True, run in parallel using MPI. If None, autodetect whether to run in parallel.
123
+ :param cpus: The number of CPUs to use. If None, autodetect according to the case.
158
124
  :param check: If True, raise a CalledProcessError if any command returns a non-zero exit code.
125
+ :param log: If True, log the command output to a file.
159
126
  """
160
- for name, args, kwargs in self._run_cmds(
161
- cmd=cmd, script=script, parallel=parallel, cpus=cpus, check=check
127
+ for coro in self._run_calls(
128
+ cmd=cmd, parallel=parallel, cpus=cpus, check=check, log=log
162
129
  ):
163
- await getattr(self, name)(*args, **kwargs)
130
+ await coro
164
131
 
165
132
  async def block_mesh(self, *, check: bool = True) -> None:
166
133
  """Run blockMesh on this case."""
@@ -176,8 +143,8 @@ class AsyncFoamCase(_FoamCaseRecipes):
176
143
 
177
144
  async def restore_0_dir(self) -> None:
178
145
  """Restore the 0 directory from the 0.orig directory."""
179
- for name, args, kwargs in self._restore_0_dir_cmds():
180
- await getattr(self, name)(*args, **kwargs)
146
+ for coro in self._restore_0_dir_calls():
147
+ await coro
181
148
 
182
149
  @awaitableasynccontextmanager
183
150
  @asynccontextmanager
@@ -189,24 +156,16 @@ class AsyncFoamCase(_FoamCaseRecipes):
189
156
 
190
157
  Use as an async context manager to automatically delete the copy when done.
191
158
 
192
- :param dst: The destination path. If None, copy to a temporary directory.
159
+ :param dst: The destination path. If None, clone to `$FOAM_RUN/foamlib`.
193
160
  """
194
- if dst is None:
195
- dst = Path(tempfile.mkdtemp(), self.name)
196
- tmp = True
197
- else:
198
- tmp = False
161
+ calls = ValuedGenerator(self._copy_calls(dst))
199
162
 
200
- for name, args, kwargs in self._copy_cmds(dst):
201
- await getattr(self, name)(*args, **kwargs)
163
+ for coro in calls:
164
+ await coro
202
165
 
203
- yield type(self)(dst)
166
+ yield calls.value
204
167
 
205
- if tmp:
206
- assert isinstance(dst, Path)
207
- await self._rmtree(dst.parent)
208
- else:
209
- await self._rmtree(dst)
168
+ await self._rmtree(calls.value.path)
210
169
 
211
170
  @awaitableasynccontextmanager
212
171
  @asynccontextmanager
@@ -218,24 +177,16 @@ class AsyncFoamCase(_FoamCaseRecipes):
218
177
 
219
178
  Use as an async context manager to automatically delete the clone when done.
220
179
 
221
- :param dst: The destination path. If None, clone to a temporary directory.
180
+ :param dst: The destination path. If None, clone to `$FOAM_RUN/foamlib`.
222
181
  """
223
- if dst is None:
224
- dst = Path(tempfile.mkdtemp(), self.name)
225
- tmp = True
226
- else:
227
- tmp = False
228
-
229
- for name, args, kwargs in self._clone_cmds(dst):
230
- await getattr(self, name)(*args, **kwargs)
231
-
232
- yield type(self)(dst)
233
-
234
- if tmp:
235
- assert isinstance(dst, Path)
236
- await self._rmtree(dst.parent)
237
- else:
238
- await self._rmtree(dst)
182
+ calls = ValuedGenerator(self._clone_calls(dst))
183
+
184
+ for coro in calls:
185
+ await coro
186
+
187
+ yield calls.value
188
+
189
+ await self._rmtree(calls.value.path)
239
190
 
240
191
  @staticmethod
241
192
  def map(coro: Callable[[X], Awaitable[Y]], iterable: Iterable[X]) -> Iterable[Y]:
@@ -2,6 +2,8 @@ import os
2
2
  import shlex
3
3
  import shutil
4
4
  import sys
5
+ import tempfile
6
+ from abc import abstractmethod
5
7
  from contextlib import contextmanager
6
8
  from pathlib import Path
7
9
  from typing import (
@@ -16,6 +18,7 @@ if sys.version_info >= (3, 9):
16
18
  from collections.abc import (
17
19
  Callable,
18
20
  Collection,
21
+ Coroutine,
19
22
  Generator,
20
23
  Mapping,
21
24
  Sequence,
@@ -26,17 +29,96 @@ else:
26
29
  from typing import (
27
30
  Callable,
28
31
  Collection,
32
+ Coroutine,
29
33
  Generator,
30
34
  Mapping,
31
35
  Sequence,
32
36
  )
33
37
 
38
+ if sys.version_info >= (3, 11):
39
+ from typing import Self
40
+ else:
41
+ from typing_extensions import Self
42
+
34
43
  from ._base import FoamCaseBase
35
44
  from ._subprocess import DEVNULL, STDOUT
36
45
 
37
46
 
38
- class _FoamCaseRecipes(FoamCaseBase):
39
- def _clean_paths(self) -> Set[Path]:
47
+ class FoamCaseRunBase(FoamCaseBase):
48
+ def __delitem__(self, key: Union[int, float, str]) -> None:
49
+ shutil.rmtree(self[key].path)
50
+
51
+ @staticmethod
52
+ @abstractmethod
53
+ def _run(
54
+ cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
55
+ *,
56
+ cpus: int,
57
+ **kwargs: Any,
58
+ ) -> Union[None, Coroutine[None, None, None]]:
59
+ raise NotImplementedError
60
+
61
+ @staticmethod
62
+ @abstractmethod
63
+ def _rmtree(
64
+ path: Union["os.PathLike[str]", str], *, ignore_errors: bool = False
65
+ ) -> Union[None, Coroutine[None, None, None]]:
66
+ raise NotImplementedError
67
+
68
+ @staticmethod
69
+ @abstractmethod
70
+ def _copytree(
71
+ src: Union["os.PathLike[str]", str],
72
+ dest: Union["os.PathLike[str]", str],
73
+ *,
74
+ symlinks: bool = False,
75
+ ignore: Optional[
76
+ Callable[[Union["os.PathLike[str]", str], Collection[str]], Collection[str]]
77
+ ] = None,
78
+ ) -> Union[None, Coroutine[None, None, None]]:
79
+ raise NotImplementedError
80
+
81
+ @abstractmethod
82
+ def clean(self, *, check: bool = False) -> Union[None, Coroutine[None, None, None]]:
83
+ raise NotImplementedError
84
+
85
+ @abstractmethod
86
+ def copy(self, dst: Optional[Union["os.PathLike[str]", str]] = None) -> Any:
87
+ raise NotImplementedError
88
+
89
+ @abstractmethod
90
+ def clone(self, dst: Optional[Union["os.PathLike[str]", str]] = None) -> Any:
91
+ raise NotImplementedError
92
+
93
+ @abstractmethod
94
+ def run(
95
+ self,
96
+ cmd: Optional[Union[Sequence[Union[str, "os.PathLike[str]"]], str]] = None,
97
+ *,
98
+ parallel: Optional[bool] = None,
99
+ cpus: Optional[int] = None,
100
+ check: bool = True,
101
+ log: bool = True,
102
+ ) -> Union[None, Coroutine[None, None, None]]:
103
+ raise NotImplementedError
104
+
105
+ @abstractmethod
106
+ def block_mesh(
107
+ self, *, check: bool = True
108
+ ) -> Union[None, Coroutine[None, None, None]]:
109
+ raise NotImplementedError
110
+
111
+ @abstractmethod
112
+ def decompose_par(
113
+ self, *, check: bool = True
114
+ ) -> Union[None, Coroutine[None, None, None]]:
115
+ raise NotImplementedError
116
+
117
+ @abstractmethod
118
+ def restore_0_dir(self) -> Union[None, Coroutine[None, None, None]]:
119
+ raise NotImplementedError
120
+
121
+ def __clean_paths(self) -> Set[Path]:
40
122
  has_decompose_par_dict = (self.path / "system" / "decomposeParDict").is_file()
41
123
  has_block_mesh_dict = (self.path / "system" / "blockMeshDict").is_file()
42
124
 
@@ -61,18 +143,14 @@ class _FoamCaseRecipes(FoamCaseBase):
61
143
  if has_block_mesh_dict and (self.path / "constant" / "polyMesh").exists():
62
144
  paths.add(self.path / "constant" / "polyMesh")
63
145
 
64
- if self._run_script() is not None:
65
- paths.update(self.path.glob("log.*"))
146
+ paths.update(self.path.glob("log.*"))
66
147
 
67
148
  return paths
68
149
 
69
- def __delitem__(self, key: Union[int, float, str]) -> None:
70
- shutil.rmtree(self[key].path)
71
-
72
- def _clone_ignore(
150
+ def __clone_ignore(
73
151
  self,
74
152
  ) -> Callable[[Union["os.PathLike[str]", str], Collection[str]], Collection[str]]:
75
- clean_paths = self._clean_paths()
153
+ clean_paths = self.__clean_paths()
76
154
 
77
155
  def ignore(
78
156
  path: Union["os.PathLike[str]", str], names: Collection[str]
@@ -82,7 +160,7 @@ class _FoamCaseRecipes(FoamCaseBase):
82
160
 
83
161
  return ignore
84
162
 
85
- def _clean_script(self) -> Optional[Path]:
163
+ def __clean_script(self) -> Optional[Path]:
86
164
  """Return the path to the (All)clean script, or None if no clean script is found."""
87
165
  clean = self.path / "clean"
88
166
  all_clean = self.path / "Allclean"
@@ -97,9 +175,9 @@ class _FoamCaseRecipes(FoamCaseBase):
97
175
  if sys.argv and Path(sys.argv[0]).absolute() == script.absolute():
98
176
  return None
99
177
 
100
- return script if Path(sys.argv[0]).absolute() != script.absolute() else None
178
+ return script
101
179
 
102
- def _run_script(self, *, parallel: Optional[bool] = None) -> Optional[Path]:
180
+ def __run_script(self, *, parallel: Optional[bool] = None) -> Optional[Path]:
103
181
  """Return the path to the (All)run script, or None if no run script is found."""
104
182
  run = self.path / "run"
105
183
  run_parallel = self.path / "run-parallel"
@@ -132,7 +210,7 @@ class _FoamCaseRecipes(FoamCaseBase):
132
210
 
133
211
  return script
134
212
 
135
- def _env(self, *, shell: bool) -> Optional[Mapping[str, str]]:
213
+ def __env(self, *, shell: bool) -> Optional[Mapping[str, str]]:
136
214
  sip_workaround = os.environ.get(
137
215
  "FOAM_LD_LIBRARY_PATH", ""
138
216
  ) and not os.environ.get("DYLD_LIBRARY_PATH", "")
@@ -151,7 +229,7 @@ class _FoamCaseRecipes(FoamCaseBase):
151
229
  return None
152
230
 
153
231
  @contextmanager
154
- def _output(
232
+ def __output(
155
233
  self, cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str], *, log: bool
156
234
  ) -> Generator[Tuple[Union[int, IO[bytes]], Union[int, IO[bytes]]], None, None]:
157
235
  if log:
@@ -168,70 +246,64 @@ class _FoamCaseRecipes(FoamCaseBase):
168
246
  else:
169
247
  yield DEVNULL, DEVNULL
170
248
 
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
249
+ def __mkrundir(self) -> Path:
250
+ d = Path(os.environ["FOAM_RUN"], "foamlib")
251
+ d.mkdir(parents=True, exist_ok=True)
252
+ ret = Path(tempfile.mkdtemp(prefix=f"{self.name}-", dir=d))
253
+ ret.rmdir()
254
+ return ret
255
+
256
+ def _copy_calls(
257
+ self, dst: Optional[Union["os.PathLike[str]", str]]
258
+ ) -> Generator[Any, None, Self]:
259
+ if dst is None:
260
+ dst = self.__mkrundir()
261
+
262
+ yield self._copytree(self.path, dst, symlinks=True)
263
+
264
+ return type(self)(dst)
265
+
266
+ def _clean_calls(self, *, check: bool = False) -> Generator[Any, None, None]:
267
+ script_path = self.__clean_script()
187
268
 
188
269
  if script_path is not None:
189
- yield ("_run", ([script_path],), {"cpus": 0, "check": check, "log": False})
270
+ yield self.run([script_path], cpus=0, check=check, log=False)
190
271
  else:
191
- for p in self._clean_paths():
272
+ for p in self.__clean_paths():
192
273
  if p.is_dir():
193
- yield ("_rmtree", (p,), {})
274
+ yield self._rmtree(p)
194
275
  else:
195
276
  p.unlink()
196
277
 
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", (), {})
278
+ def _clone_calls(
279
+ self, dst: Optional[Union["os.PathLike[str]", str]]
280
+ ) -> Generator[Any, None, Self]:
281
+ if dst is None:
282
+ dst = self.__mkrundir()
283
+
284
+ if self.__clean_script() is not None:
285
+ yield self.copy(dst)
286
+ yield type(self)(dst).clean()
203
287
  else:
204
- yield (
205
- "_copytree",
206
- (
207
- self.path,
208
- dest,
209
- ),
210
- {"symlinks": True, "ignore": self._clone_ignore()},
288
+ yield self._copytree(
289
+ self.path, dst, symlinks=True, ignore=self.__clone_ignore()
211
290
  )
212
291
 
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(
292
+ return type(self)(dst)
293
+
294
+ def _restore_0_dir_calls(self) -> Generator[Any, None, None]:
295
+ yield self._rmtree(self.path / "0", ignore_errors=True)
296
+ yield self._copytree(self.path / "0.orig", self.path / "0", symlinks=True)
297
+
298
+ def _run_calls(
227
299
  self,
228
300
  cmd: Optional[Union[Sequence[Union[str, "os.PathLike[str]"]], str]] = None,
229
301
  *,
230
- script: bool = True,
231
302
  parallel: Optional[bool] = None,
232
303
  cpus: Optional[int] = None,
233
304
  check: bool = True,
234
- ) -> Generator[Tuple[str, Sequence[Any], Mapping[str, Any]], None, None]:
305
+ log: bool = True,
306
+ ) -> Generator[Any, None, None]:
235
307
  if cmd is not None:
236
308
  if parallel:
237
309
  if cpus is None:
@@ -241,10 +313,32 @@ class _FoamCaseRecipes(FoamCaseBase):
241
313
  if cpus is None:
242
314
  cpus = 1
243
315
 
244
- yield ("_run", (cmd,), {"parallel": parallel, "cpus": cpus, "check": check})
316
+ with self.__output(cmd, log=log) as (stdout, stderr):
317
+ if parallel:
318
+ if isinstance(cmd, str):
319
+ cmd = [
320
+ "mpiexec",
321
+ "-n",
322
+ str(cpus),
323
+ "/bin/sh",
324
+ "-c",
325
+ f"{cmd} -parallel",
326
+ ]
327
+ else:
328
+ cmd = ["mpiexec", "-n", str(cpus), *cmd, "-parallel"]
329
+
330
+ yield self._run(
331
+ cmd,
332
+ cpus=cpus,
333
+ check=check,
334
+ cwd=self.path,
335
+ env=self.__env(shell=isinstance(cmd, str)),
336
+ stdout=stdout,
337
+ stderr=stderr,
338
+ )
245
339
 
246
340
  else:
247
- script_path = self._run_script(parallel=parallel) if script else None
341
+ script_path = self.__run_script(parallel=parallel)
248
342
 
249
343
  if script_path is not None:
250
344
  if parallel or parallel is None:
@@ -259,18 +353,14 @@ class _FoamCaseRecipes(FoamCaseBase):
259
353
  if cpus is None:
260
354
  cpus = 1
261
355
 
262
- yield (
263
- "_run",
264
- ([script_path],),
265
- {"parallel": False, "cpus": cpus, "check": check},
266
- )
356
+ yield self.run([script_path], parallel=False, cpus=cpus, check=check)
267
357
 
268
358
  else:
269
359
  if not self and (self.path / "0.orig").is_dir():
270
- yield ("restore_0_dir", (), {})
360
+ yield self.restore_0_dir()
271
361
 
272
362
  if (self.path / "system" / "blockMeshDict").is_file():
273
- yield ("block_mesh", (), {"check": check})
363
+ yield self.block_mesh(check=check)
274
364
 
275
365
  if parallel is None:
276
366
  parallel = (
@@ -284,7 +374,7 @@ class _FoamCaseRecipes(FoamCaseBase):
284
374
  self._nprocessors == 0
285
375
  and (self.path / "system" / "decomposeParDict").is_file()
286
376
  ):
287
- yield ("decompose_par", (), {"check": check})
377
+ yield self.decompose_par(check=check)
288
378
 
289
379
  if cpus is None:
290
380
  cpus = max(self._nprocessors, 1)
@@ -292,8 +382,6 @@ class _FoamCaseRecipes(FoamCaseBase):
292
382
  if cpus is None:
293
383
  cpus = 1
294
384
 
295
- yield (
296
- "_run",
297
- ([self.application],),
298
- {"parallel": parallel, "cpus": cpus, "check": check},
385
+ yield self.run(
386
+ [self.application], parallel=parallel, cpus=cpus, check=check
299
387
  )
foamlib/_cases/_sync.py CHANGED
@@ -1,10 +1,10 @@
1
1
  import os
2
2
  import shutil
3
3
  import sys
4
- import tempfile
5
4
  from pathlib import Path
6
5
  from types import TracebackType
7
6
  from typing import (
7
+ Any,
8
8
  Callable,
9
9
  Optional,
10
10
  Type,
@@ -21,11 +21,12 @@ if sys.version_info >= (3, 11):
21
21
  else:
22
22
  from typing_extensions import Self
23
23
 
24
- from ._recipes import _FoamCaseRecipes
24
+ from ._run import FoamCaseRunBase
25
25
  from ._subprocess import run_sync
26
+ from ._util import ValuedGenerator
26
27
 
27
28
 
28
- class FoamCase(_FoamCaseRecipes):
29
+ class FoamCase(FoamCaseRunBase):
29
30
  """
30
31
  An OpenFOAM case.
31
32
 
@@ -38,7 +39,15 @@ class FoamCase(_FoamCaseRecipes):
38
39
 
39
40
  def __init__(self, path: Union["os.PathLike[str]", str] = Path()):
40
41
  super().__init__(path)
41
- self._tmp: Optional[bool] = None
42
+
43
+ @staticmethod
44
+ def _run(
45
+ cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
46
+ *,
47
+ cpus: int,
48
+ **kwargs: Any,
49
+ ) -> None:
50
+ run_sync(cmd, **kwargs)
42
51
 
43
52
  @staticmethod
44
53
  def _rmtree(
@@ -59,10 +68,6 @@ class FoamCase(_FoamCaseRecipes):
59
68
  shutil.copytree(src, dest, symlinks=symlinks, ignore=ignore)
60
69
 
61
70
  def __enter__(self) -> "FoamCase":
62
- if self._tmp is None:
63
- raise RuntimeError(
64
- "Cannot use a non-copied/cloned case as a context manager"
65
- )
66
71
  return self
67
72
 
68
73
  def __exit__(
@@ -71,83 +76,37 @@ class FoamCase(_FoamCaseRecipes):
71
76
  exc_val: Optional[BaseException],
72
77
  exc_tb: Optional[TracebackType],
73
78
  ) -> None:
74
- if self._tmp is not None:
75
- if self._tmp:
76
- self._rmtree(self.path.parent)
77
- else:
78
- self._rmtree(self.path)
79
- else:
80
- raise RuntimeError(
81
- "Cannot use a non-copied/cloned case as a context manager"
82
- )
83
-
84
- def clean(
85
- self,
86
- *,
87
- script: bool = True,
88
- check: bool = False,
89
- ) -> None:
79
+ self._rmtree(self.path)
80
+
81
+ def clean(self, *, check: bool = False) -> None:
90
82
  """
91
83
  Clean this case.
92
84
 
93
- :param script: If True, use an (All)clean script if it exists. If False, ignore any clean scripts.
94
85
  :param check: If True, raise a CalledProcessError if the clean script returns a non-zero exit code.
95
86
  """
96
- for name, args, kwargs in self._clean_cmds(script=script, check=check):
97
- getattr(self, name)(*args, **kwargs)
98
-
99
- def _run(
100
- self,
101
- cmd: Union[Sequence[Union[str, "os.PathLike[str]"]], str],
102
- *,
103
- parallel: bool = False,
104
- cpus: int = 1,
105
- check: bool = True,
106
- log: bool = True,
107
- ) -> None:
108
- with self._output(cmd, log=log) as (stdout, stderr):
109
- if parallel:
110
- if isinstance(cmd, str):
111
- cmd = [
112
- "mpiexec",
113
- "-n",
114
- str(cpus),
115
- "/bin/sh",
116
- "-c",
117
- f"{cmd} -parallel",
118
- ]
119
- else:
120
- cmd = ["mpiexec", "-n", str(cpus), *cmd, "-parallel"]
121
-
122
- run_sync(
123
- cmd,
124
- check=check,
125
- cwd=self.path,
126
- env=self._env(shell=isinstance(cmd, str)),
127
- stdout=stdout,
128
- stderr=stderr,
129
- )
87
+ for _ in self._clean_calls(check=check):
88
+ pass
130
89
 
131
90
  def run(
132
91
  self,
133
92
  cmd: Optional[Union[Sequence[Union[str, "os.PathLike[str]"]], str]] = None,
134
93
  *,
135
- script: bool = True,
136
94
  parallel: Optional[bool] = None,
95
+ cpus: Optional[int] = None,
137
96
  check: bool = True,
97
+ log: bool = True,
138
98
  ) -> None:
139
99
  """
140
100
  Run this case, or a specified command in the context of this case.
141
101
 
142
102
  :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.
143
- :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.
144
103
  :param parallel: If True, run in parallel using MPI. If None, autodetect whether to run in parallel.
104
+ :param cpus: The number of CPUs to use. If None, autodetect according to the case.
145
105
  :param check: If True, raise a CalledProcessError if any command returns a non-zero exit code.
106
+ :param log: If True, log the command output to a file.
146
107
  """
147
- for name, args, kwargs in self._run_cmds(
148
- cmd=cmd, script=script, parallel=parallel, check=check
149
- ):
150
- getattr(self, name)(*args, **kwargs)
108
+ for _ in self._run_calls(cmd=cmd, parallel=parallel, check=check):
109
+ pass
151
110
 
152
111
  def block_mesh(self, *, check: bool = True) -> None:
153
112
  """Run blockMesh on this case."""
@@ -163,49 +122,35 @@ class FoamCase(_FoamCaseRecipes):
163
122
 
164
123
  def restore_0_dir(self) -> None:
165
124
  """Restore the 0 directory from the 0.orig directory."""
166
- for name, args, kwargs in self._restore_0_dir_cmds():
167
- getattr(self, name)(*args, **kwargs)
125
+ for _ in self._restore_0_dir_calls():
126
+ pass
168
127
 
169
- def copy(self, dst: Optional[Union["os.PathLike[str]", str]] = None) -> "Self":
128
+ def copy(self, dst: Optional[Union["os.PathLike[str]", str]] = None) -> Self:
170
129
  """
171
130
  Make a copy of this case.
172
131
 
173
132
  Use as a context manager to automatically delete the copy when done.
174
133
 
175
- :param dst: The destination path. If None, copy to a temporary directory.
134
+ :param dst: The destination path. If None, clone to `$FOAM_RUN/foamlib`.
176
135
  """
177
- if dst is None:
178
- dst = Path(tempfile.mkdtemp(), self.name)
179
- tmp = True
180
- else:
181
- tmp = False
136
+ cmds = ValuedGenerator(self._copy_calls(dst))
182
137
 
183
- for name, args, kwargs in self._copy_cmds(dst):
184
- getattr(self, name)(*args, **kwargs)
138
+ for _ in cmds:
139
+ pass
185
140
 
186
- ret = type(self)(dst)
187
- ret._tmp = tmp
141
+ return cmds.value
188
142
 
189
- return ret
190
-
191
- def clone(self, dst: Optional[Union["os.PathLike[str]", str]] = None) -> "Self":
143
+ def clone(self, dst: Optional[Union["os.PathLike[str]", str]] = None) -> Self:
192
144
  """
193
145
  Clone this case (make a clean copy).
194
146
 
195
147
  Use as a context manager to automatically delete the clone when done.
196
148
 
197
- :param dst: The destination path. If None, clone to a temporary directory.
149
+ :param dst: The destination path. If None, clone to `$FOAM_RUN/foamlib`.
198
150
  """
199
- if dst is None:
200
- dst = Path(tempfile.mkdtemp(), self.name)
201
- tmp = True
202
- else:
203
- tmp = False
204
-
205
- for name, args, kwargs in self._clone_cmds(dst):
206
- getattr(self, name)(*args, **kwargs)
151
+ cmds = ValuedGenerator(self._clone_calls(dst))
207
152
 
208
- ret = type(self)(dst)
209
- ret._tmp = tmp
153
+ for _ in cmds:
154
+ pass
210
155
 
211
- return ret
156
+ return cmds.value
foamlib/_cases/_util.py CHANGED
@@ -17,9 +17,20 @@ else:
17
17
  from typing import Generator
18
18
 
19
19
 
20
+ Y = TypeVar("Y")
21
+ S = TypeVar("S")
20
22
  R = TypeVar("R")
21
23
 
22
24
 
25
+ class ValuedGenerator(Generic[Y, S, R]):
26
+ def __init__(self, generator: Generator[Y, S, R]):
27
+ self._generator = generator
28
+
29
+ def __iter__(self) -> Generator[Y, S, R]:
30
+ self.value = yield from self._generator
31
+ return self.value
32
+
33
+
23
34
  class _AwaitableAsyncContextManager(Generic[R]):
24
35
  def __init__(self, cm: "AsyncContextManager[R]"):
25
36
  self._cm = cm
foamlib/_files/_files.py CHANGED
@@ -11,13 +11,8 @@ if sys.version_info >= (3, 9):
11
11
  else:
12
12
  from typing import Iterator, Mapping, MutableMapping, Sequence
13
13
 
14
- if sys.version_info >= (3, 11):
15
- from typing import Self
16
- else:
17
- from typing_extensions import Self
18
-
19
14
  from ._base import FoamFileBase
20
- from ._io import _FoamFileIO
15
+ from ._io import FoamFileIO
21
16
  from ._serialization import Kind, dumpb
22
17
  from ._util import is_sequence
23
18
 
@@ -33,7 +28,7 @@ class FoamFile(
33
28
  Optional[Union[str, Tuple[str, ...]]],
34
29
  Union["FoamFile.Data", "FoamFile.SubDict"],
35
30
  ],
36
- _FoamFileIO,
31
+ FoamFileIO,
37
32
  ):
38
33
  """
39
34
  An OpenFOAM data file.
@@ -101,28 +96,6 @@ class FoamFile(
101
96
 
102
97
  return cast(FoamFileBase._Dict, ret)
103
98
 
104
- def create(self, *, exist_ok: bool = False, parents: bool = False) -> Self:
105
- """
106
- Create the file.
107
-
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.
111
- """
112
- if self.path.exists():
113
- if not exist_ok:
114
- raise FileExistsError(self.path)
115
- else:
116
- return self
117
-
118
- if parents:
119
- self.path.parent.mkdir(parents=True, exist_ok=True)
120
-
121
- self.path.touch()
122
- self._write_header()
123
-
124
- return self
125
-
126
99
  @property
127
100
  def version(self) -> float:
128
101
  """Alias of `self["FoamFile", "version"]`."""
@@ -185,17 +158,6 @@ class FoamFile(
185
158
  def object_(self, value: str) -> None:
186
159
  self["FoamFile", "object"] = value
187
160
 
188
- def _write_header(self) -> None:
189
- assert "FoamFile" not in self
190
- assert not self
191
-
192
- self["FoamFile"] = {}
193
- self.version = 2.0
194
- self.format = "ascii"
195
- self.class_ = "dictionary"
196
- self.location = f'"{self.path.parent.name}"'
197
- self.object_ = self.path.name
198
-
199
161
  def __getitem__(
200
162
  self, keywords: Optional[Union[str, Tuple[str, ...]]]
201
163
  ) -> Union["FoamFile.Data", "FoamFile.SubDict"]:
@@ -222,8 +184,24 @@ class FoamFile(
222
184
  elif not isinstance(keywords, tuple):
223
185
  keywords = (keywords,)
224
186
 
225
- if not self and "FoamFile" not in self and keywords[0] != "FoamFile":
226
- self._write_header()
187
+ try:
188
+ write_header = (
189
+ not self
190
+ and "FoamFile" not in self
191
+ and (not keywords or keywords[0] != "FoamFile")
192
+ )
193
+ except FileNotFoundError:
194
+ write_header = not keywords or keywords[0] != "FoamFile"
195
+
196
+ if write_header:
197
+ self["FoamFile"] = {}
198
+ self.version = 2.0
199
+ self.format = "ascii"
200
+ self.class_ = "dictionary"
201
+ self.location = f'"{self.path.parent.name}"'
202
+ self.object_ = (
203
+ self.path.stem if self.path.suffix == ".gz" else self.path.name
204
+ )
227
205
 
228
206
  kind = Kind.DEFAULT
229
207
  if keywords == ("internalField",) or (
@@ -253,7 +231,7 @@ class FoamFile(
253
231
  self[keywords] = data
254
232
 
255
233
  else:
256
- contents, parsed = self._read()
234
+ contents, parsed = self._read(missing_ok=True)
257
235
 
258
236
  start, end = parsed.entry_location(keywords, missing_ok=True)
259
237
 
foamlib/_files/_io.py CHANGED
@@ -18,7 +18,7 @@ else:
18
18
  from ._parsing import Parsed
19
19
 
20
20
 
21
- class _FoamFileIO:
21
+ class FoamFileIO:
22
22
  def __init__(self, path: Union[str, Path]) -> None:
23
23
  self.path = Path(path).absolute()
24
24
 
@@ -29,7 +29,7 @@ class _FoamFileIO:
29
29
 
30
30
  def __enter__(self) -> Self:
31
31
  if self.__defer_io == 0:
32
- self._read()
32
+ self._read(missing_ok=True)
33
33
  self.__defer_io += 1
34
34
  return self
35
35
 
@@ -44,18 +44,26 @@ class _FoamFileIO:
44
44
  assert self.__contents is not None
45
45
  self._write(self.__contents)
46
46
 
47
- def _read(self) -> Tuple[bytes, Parsed]:
47
+ def _read(self, *, missing_ok: bool = False) -> Tuple[bytes, Parsed]:
48
48
  if not self.__defer_io:
49
- contents = self.path.read_bytes()
50
-
51
- if self.path.suffix == ".gz":
52
- contents = gzip.decompress(contents)
49
+ try:
50
+ contents = self.path.read_bytes()
51
+ except FileNotFoundError:
52
+ contents = None
53
+ else:
54
+ assert isinstance(contents, bytes)
55
+ if self.path.suffix == ".gz":
56
+ contents = gzip.decompress(contents)
53
57
 
54
58
  if contents != self.__contents:
55
59
  self.__contents = contents
56
60
  self.__parsed = None
57
61
 
58
- assert self.__contents is not None
62
+ if self.__contents is None:
63
+ if missing_ok:
64
+ return b"", Parsed(b"")
65
+ else:
66
+ raise FileNotFoundError(self.path)
59
67
 
60
68
  if self.__parsed is None:
61
69
  parsed = Parsed(self.__contents)
@@ -95,68 +95,68 @@ def _unpack_binary_field(
95
95
  return [all]
96
96
 
97
97
 
98
- _BINARY_FIELD = (
99
- Keyword("nonuniform").suppress()
100
- + Literal("List").suppress()
101
- + Literal("<").suppress()
102
- + (
103
- counted_array(
104
- CharsNotIn(exact=8),
105
- Literal("scalar").suppress()
106
- + Literal(">").suppress()
107
- + common.integer
108
- + Literal("(").suppress(),
109
- )
110
- | counted_array(
111
- CharsNotIn(exact=8 * 3),
112
- Literal("vector").suppress()
113
- + Literal(">").suppress()
114
- + common.integer
115
- + Literal("(").suppress(),
116
- )
117
- | counted_array(
118
- CharsNotIn(exact=8 * 6),
119
- Literal("symmTensor").suppress()
120
- + Literal(">").suppress()
121
- + common.integer
122
- + Literal("(").suppress(),
123
- )
124
- | counted_array(
125
- CharsNotIn(exact=8 * 9),
126
- Literal("tensor").suppress()
127
- + Literal(">").suppress()
128
- + common.integer
129
- + Literal("(").suppress(),
130
- )
131
- )
132
- + Literal(")").suppress()
133
- ).set_parse_action(_unpack_binary_field)
134
-
135
-
136
98
  _SWITCH = (
137
99
  Keyword("yes") | Keyword("true") | Keyword("on") | Keyword("y") | Keyword("t")
138
100
  ).set_parse_action(lambda: True) | (
139
101
  Keyword("no") | Keyword("false") | Keyword("off") | Keyword("n") | Keyword("f")
140
102
  ).set_parse_action(lambda: False)
141
103
  _DIMENSIONS = (
142
- Literal("[").suppress() + common.number * 7 + Literal("]").suppress()
104
+ Literal("[").suppress() + common.number[0, 7] + Literal("]").suppress()
143
105
  ).set_parse_action(lambda tks: FoamFileBase.DimensionSet(*tks))
144
106
  _TENSOR = _list_of(common.number) | common.number
145
107
  _IDENTIFIER = Combine(
146
- Word(identchars + "$", printables, exclude_chars="{(;)}")
147
- + Opt(Literal("(") + Word(printables, exclude_chars="{(;)}") + Literal(")"))
108
+ Word(identchars + "$", printables, exclude_chars="[{(;)}]")
109
+ + Opt(Literal("(") + Word(printables, exclude_chars="[{(;)}]") + Literal(")"))
148
110
  )
149
111
  _DIMENSIONED = (Opt(_IDENTIFIER) + _DIMENSIONS + _TENSOR).set_parse_action(
150
112
  lambda tks: FoamFileBase.Dimensioned(*reversed(tks.as_list()))
151
113
  )
152
- _FIELD = (
153
- (Keyword("uniform").suppress() + _TENSOR)
154
- | (Keyword("nonuniform").suppress() + _list_of(_TENSOR))
155
- | _BINARY_FIELD
114
+ _FIELD = (Keyword("uniform").suppress() + _TENSOR) | (
115
+ Keyword("nonuniform").suppress()
116
+ + (
117
+ _list_of(_TENSOR)
118
+ | (
119
+ Literal("List").suppress()
120
+ + Literal("<").suppress()
121
+ + (
122
+ counted_array(
123
+ CharsNotIn(exact=8),
124
+ Literal("scalar").suppress()
125
+ + Literal(">").suppress()
126
+ + common.integer
127
+ + Literal("(").suppress(),
128
+ )
129
+ | counted_array(
130
+ CharsNotIn(exact=8 * 3),
131
+ Literal("vector").suppress()
132
+ + Literal(">").suppress()
133
+ + common.integer
134
+ + Literal("(").suppress(),
135
+ )
136
+ | counted_array(
137
+ CharsNotIn(exact=8 * 6),
138
+ Literal("symmTensor").suppress()
139
+ + Literal(">").suppress()
140
+ + common.integer
141
+ + Literal("(").suppress(),
142
+ )
143
+ | counted_array(
144
+ CharsNotIn(exact=8 * 9),
145
+ Literal("tensor").suppress()
146
+ + Literal(">").suppress()
147
+ + common.integer
148
+ + Literal("(").suppress(),
149
+ )
150
+ )
151
+ + Literal(")").suppress()
152
+ ).set_parse_action(_unpack_binary_field)
153
+ )
156
154
  )
157
155
  _TOKEN = QuotedString('"', unquote_results=False) | _IDENTIFIER
158
156
  _DATA = Forward()
159
- _KEYWORD = Combine(Literal("(") + Word(identchars + " ") + Literal(")")) | _TOKEN
157
+ _KEYWORD = _TOKEN | _list_of(_IDENTIFIER).set_parse_action(
158
+ lambda tks: "(" + " ".join(tks[0]) + ")"
159
+ )
160
160
  _KEYWORD_ENTRY = Dict(Group(_keyword_entry_of(_KEYWORD, _DATA)), asdict=True)
161
161
  _DATA_ENTRY = Forward()
162
162
  _LIST_ENTRY = _KEYWORD_ENTRY | _DATA_ENTRY
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foamlib
3
- Version: 0.5.1
3
+ Version: 0.6.0
4
4
  Summary: A Python interface for interacting with OpenFOAM
5
5
  Author-email: "Gabriel S. Gerlero" <ggerlero@cimec.unl.edu.ar>
6
6
  Project-URL: Homepage, https://github.com/gerlero/foamlib
@@ -0,0 +1,21 @@
1
+ foamlib/__init__.py,sha256=Qg1zNl1cgh7eMuYuDmUROom5krkuBVijkBu6M4m44No,392
2
+ foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ foamlib/_cases/__init__.py,sha256=C0mpRu7c-X-4uVMKmVrZhwIyhBNyvUoCv0o-BQ72RC0,236
4
+ foamlib/_cases/_async.py,sha256=8x8Mkql6XPzMjH4t-NszAEDx4gpjNPnQiWxFkHrcFJU,6436
5
+ foamlib/_cases/_base.py,sha256=1CUkkK4afBxDgP79dmho97WJdj-GLgYhnrCSf_52Eao,6604
6
+ foamlib/_cases/_run.py,sha256=0xu5V3qvWnXYJvOh0GV8DM1gOiNc65frWJF3mviHPOM,12307
7
+ foamlib/_cases/_subprocess.py,sha256=CfUy_LrqLnMLR9FHINqInCd3soN6eYvonwZ30epiLu8,2234
8
+ foamlib/_cases/_sync.py,sha256=BqEA-EE5JltOGwisAOeTM8kqawWqw33y0Ze92TZe6KQ,4758
9
+ foamlib/_cases/_util.py,sha256=GNndpqw3Jg_S-Hxzl5vwRgD0czcTNb9NYHMhcfBoMBg,1493
10
+ foamlib/_files/__init__.py,sha256=-UqB9YTH6mrJfXCX00kPTAAY20XG64u1MGPw_1ewLVs,148
11
+ foamlib/_files/_base.py,sha256=zaFDjLE6jB7WtGWk8hfKusjLtlGu6CZV16AHJpRUibs,1929
12
+ foamlib/_files/_files.py,sha256=DxM5JmXv19PJrqlKNwuP6SsB0nye6aN1hiPwble7fFM,15657
13
+ foamlib/_files/_io.py,sha256=uSh5XlgukwJkQSLELa4mshRD2aTajNk5vr_ZsBImSeU,2423
14
+ foamlib/_files/_parsing.py,sha256=DzgJ53QnohRQyLXn2zOs52RIQ7bJ_fS_S2Z7rmRyVK4,9023
15
+ foamlib/_files/_serialization.py,sha256=pb8_cIVgRhGS_ZV2p3x8p5_lK1SS6xzQHscAYYuOgFY,3407
16
+ foamlib/_files/_util.py,sha256=UMzXmTFgvbp46w6k3oEZJoYC98pFgEK6LN5uLOwrlCg,397
17
+ foamlib-0.6.0.dist-info/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
18
+ foamlib-0.6.0.dist-info/METADATA,sha256=IR3vRgU_77iKtMM_PiKdeGZIHi8EczgdhBbEsUF6dpE,6338
19
+ foamlib-0.6.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
20
+ foamlib-0.6.0.dist-info/top_level.txt,sha256=ZdVYtetXGwPwyfL-WhlhbTFQGAwKX5P_gXxtH9JYFPI,8
21
+ foamlib-0.6.0.dist-info/RECORD,,
@@ -1,21 +0,0 @@
1
- foamlib/__init__.py,sha256=bRzkPtbKy1RniCL9eYinI6Yc8g8utDoXrPV6hFimMZY,392
2
- foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- foamlib/_cases/__init__.py,sha256=C0mpRu7c-X-4uVMKmVrZhwIyhBNyvUoCv0o-BQ72RC0,236
4
- foamlib/_cases/_async.py,sha256=um1nrAjGcT8Vp1hz8XvC_87xVBjfeU3MrpnuzAHN5GY,8251
5
- foamlib/_cases/_base.py,sha256=1CUkkK4afBxDgP79dmho97WJdj-GLgYhnrCSf_52Eao,6604
6
- foamlib/_cases/_recipes.py,sha256=UTFnVuTvEf-9wn-Fr30a9wOOmyOxvWeDhbqBhdtbhzA,9692
7
- foamlib/_cases/_subprocess.py,sha256=CfUy_LrqLnMLR9FHINqInCd3soN6eYvonwZ30epiLu8,2234
8
- foamlib/_cases/_sync.py,sha256=R32Ea4BTOPq-x9OIndObcYZACF8e0RnzB4TywNvDxbA,6733
9
- foamlib/_cases/_util.py,sha256=BpPW_91bFLzV3b4r50u-2pGMR3tfQQfY2xqToOHCUBk,1204
10
- foamlib/_files/__init__.py,sha256=-UqB9YTH6mrJfXCX00kPTAAY20XG64u1MGPw_1ewLVs,148
11
- foamlib/_files/_base.py,sha256=zaFDjLE6jB7WtGWk8hfKusjLtlGu6CZV16AHJpRUibs,1929
12
- foamlib/_files/_files.py,sha256=6Fdrc0lksFK99i1a6wEsBi-BtYgSQnUc5s10h2LQ9ew,16091
13
- foamlib/_files/_io.py,sha256=f_tYI7AqaFsQ8mtK__fEoIUqpYb3YmrI8X5D8updmNM,2084
14
- foamlib/_files/_parsing.py,sha256=8V2CKZ45mKE3f9fP8lAfexIdhPGrq7elIZkpBkkGB6Q,8773
15
- foamlib/_files/_serialization.py,sha256=pb8_cIVgRhGS_ZV2p3x8p5_lK1SS6xzQHscAYYuOgFY,3407
16
- foamlib/_files/_util.py,sha256=UMzXmTFgvbp46w6k3oEZJoYC98pFgEK6LN5uLOwrlCg,397
17
- foamlib-0.5.1.dist-info/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
18
- foamlib-0.5.1.dist-info/METADATA,sha256=4Fq_okfGu-zY0IGrcUCD5IuuJxAESwNtr0EgXD0NzF8,6338
19
- foamlib-0.5.1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
20
- foamlib-0.5.1.dist-info/top_level.txt,sha256=ZdVYtetXGwPwyfL-WhlhbTFQGAwKX5P_gXxtH9JYFPI,8
21
- foamlib-0.5.1.dist-info/RECORD,,