foamlib 0.8.5__py3-none-any.whl → 0.8.7__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.8.5"
3
+ __version__ = "0.8.7"
4
4
 
5
5
  from ._cases import (
6
6
  AsyncFoamCase,
foamlib/_cases/_async.py CHANGED
@@ -99,6 +99,9 @@ class AsyncFoamCase(FoamCaseRunBase):
99
99
  cpus: int,
100
100
  **kwargs: Any,
101
101
  ) -> None:
102
+ if isinstance(cmd, str):
103
+ cmd = [*AsyncFoamCase._SHELL, cmd]
104
+
102
105
  async with AsyncFoamCase._cpus(cpus):
103
106
  await run_async(cmd, **kwargs)
104
107
 
foamlib/_cases/_run.py CHANGED
@@ -10,13 +10,16 @@ from contextlib import contextmanager
10
10
  from pathlib import Path
11
11
  from typing import IO, TYPE_CHECKING, Any
12
12
 
13
+ from rich.progress import Progress
14
+
15
+ from ._util import SingletonContextManager
16
+
13
17
  if sys.version_info >= (3, 9):
14
18
  from collections.abc import (
15
19
  Callable,
16
20
  Collection,
17
21
  Coroutine,
18
22
  Generator,
19
- Mapping,
20
23
  Sequence,
21
24
  )
22
25
  from collections.abc import Set as AbstractSet
@@ -27,7 +30,6 @@ else:
27
30
  Collection,
28
31
  Coroutine,
29
32
  Generator,
30
- Mapping,
31
33
  Sequence,
32
34
  )
33
35
 
@@ -68,6 +70,10 @@ class FoamCaseRunBase(FoamCaseBase):
68
70
 
69
71
  return ret
70
72
 
73
+ _SHELL = ("bash", "-c")
74
+
75
+ __progress = SingletonContextManager(Progress)
76
+
71
77
  def __delitem__(self, key: int | float | str) -> None:
72
78
  shutil.rmtree(self[key].path)
73
79
 
@@ -255,38 +261,60 @@ class FoamCaseRunBase(FoamCaseBase):
255
261
 
256
262
  return script
257
263
 
258
- def __env(self, *, shell: bool) -> Mapping[str, str] | None:
259
- sip_workaround = os.environ.get(
260
- "FOAM_LD_LIBRARY_PATH", ""
261
- ) and not os.environ.get("DYLD_LIBRARY_PATH", "")
262
-
263
- if not shell or sip_workaround:
264
- env = os.environ.copy()
265
-
266
- if not shell:
267
- env["PWD"] = str(self.path)
268
-
269
- if sip_workaround:
270
- env["DYLD_LIBRARY_PATH"] = env["FOAM_LD_LIBRARY_PATH"]
271
-
272
- return env
273
- return None
274
-
275
264
  @contextmanager
276
265
  def __output(
277
266
  self, cmd: Sequence[str | os.PathLike[str]] | str, *, log: bool
278
- ) -> Generator[tuple[int | IO[bytes], int | IO[bytes]], None, None]:
267
+ ) -> Generator[tuple[int | IO[str], int | IO[str]], None, None]:
279
268
  if log:
280
269
  if isinstance(cmd, str):
281
270
  name = shlex.split(cmd)[0]
282
271
  else:
283
272
  name = Path(cmd[0]).name if isinstance(cmd[0], os.PathLike) else cmd[0]
284
273
 
285
- with (self.path / f"log.{name}").open("ab") as stdout:
274
+ with (self.path / f"log.{name}").open("a") as stdout:
286
275
  yield stdout, STDOUT
287
276
  else:
288
277
  yield DEVNULL, DEVNULL
289
278
 
279
+ @contextmanager
280
+ def __process_stdout(
281
+ self, cmd: Sequence[str | os.PathLike[str]] | str
282
+ ) -> Generator[Callable[[str], None], None, None]:
283
+ if isinstance(cmd, str):
284
+ name = shlex.split(cmd)[0]
285
+ else:
286
+ name = Path(cmd[0]).name if isinstance(cmd[0], os.PathLike) else cmd[0]
287
+
288
+ try:
289
+ with self.control_dict as control_dict:
290
+ if control_dict["stopAt"] == "endTime":
291
+ control_dict_end_time = control_dict["endTime"]
292
+ if isinstance(control_dict_end_time, (int, float)):
293
+ end_time = control_dict_end_time
294
+ else:
295
+ end_time = None
296
+ else:
297
+ end_time = None
298
+ except (KeyError, FileNotFoundError):
299
+ end_time = None
300
+
301
+ with self.__progress as progress:
302
+ task = progress.add_task(f"({self.name}) Running {name}...", total=None)
303
+
304
+ def process_stdout(line: str) -> None:
305
+ if line.startswith("Time = "):
306
+ try:
307
+ time = float(line.split()[2])
308
+ except ValueError:
309
+ progress.update(task)
310
+ else:
311
+ progress.update(task, completed=time, total=end_time)
312
+ else:
313
+ progress.update(task)
314
+
315
+ yield process_stdout
316
+ progress.update(task, completed=1, total=1)
317
+
290
318
  def __mkrundir(self) -> Path:
291
319
  d = Path(os.environ["FOAM_RUN"], "foamlib")
292
320
  d.mkdir(parents=True, exist_ok=True)
@@ -379,15 +407,16 @@ class FoamCaseRunBase(FoamCaseBase):
379
407
  if cpus is None:
380
408
  cpus = 1
381
409
 
382
- with self.__output(cmd, log=log) as (stdout, stderr):
410
+ with self.__output(cmd, log=log) as (stdout, stderr), self.__process_stdout(
411
+ cmd
412
+ ) as process_stdout:
383
413
  if parallel:
384
414
  if isinstance(cmd, str):
385
415
  cmd = [
386
416
  "mpiexec",
387
417
  "-n",
388
418
  str(cpus),
389
- "/bin/sh",
390
- "-c",
419
+ *FoamCaseRunBase._SHELL,
391
420
  f"{cmd} -parallel",
392
421
  ]
393
422
  else:
@@ -396,11 +425,11 @@ class FoamCaseRunBase(FoamCaseBase):
396
425
  yield self._run(
397
426
  cmd,
398
427
  cpus=cpus,
428
+ case=self,
399
429
  check=check,
400
- cwd=self.path,
401
- env=self.__env(shell=isinstance(cmd, str)),
402
430
  stdout=stdout,
403
431
  stderr=stderr,
432
+ process_stdout=process_stdout,
404
433
  **kwargs,
405
434
  )
406
435
 
foamlib/_cases/_slurm.py CHANGED
@@ -31,10 +31,10 @@ class AsyncSlurmFoamCase(AsyncFoamCase):
31
31
  await AsyncFoamCase._run(cmd, cpus=cpus, **kwargs)
32
32
  return
33
33
 
34
- if cpus >= 1:
35
- if isinstance(cmd, str):
36
- cmd = ["/bin/sh", "-c", cmd]
34
+ if isinstance(cmd, str):
35
+ cmd = [*AsyncSlurmFoamCase._SHELL, cmd]
37
36
 
37
+ if cpus >= 1:
38
38
  if cpus == 1:
39
39
  cmd = ["srun", *cmd]
40
40
 
@@ -1,18 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import os
5
+ import selectors
4
6
  import subprocess
5
7
  import sys
6
- from io import BytesIO
7
- from typing import IO, TYPE_CHECKING
8
-
9
- if TYPE_CHECKING:
10
- import os
8
+ from io import StringIO
9
+ from pathlib import Path
10
+ from typing import IO
11
11
 
12
12
  if sys.version_info >= (3, 9):
13
- from collections.abc import Mapping, Sequence
13
+ from collections.abc import Callable, Mapping, Sequence
14
14
  else:
15
- from typing import Mapping, Sequence
15
+ from typing import Callable, Mapping, Sequence
16
16
 
17
17
  CompletedProcess = subprocess.CompletedProcess
18
18
 
@@ -32,46 +32,72 @@ PIPE = subprocess.PIPE
32
32
  STDOUT = subprocess.STDOUT
33
33
 
34
34
 
35
+ def _env(case: os.PathLike[str]) -> Mapping[str, str]:
36
+ env = os.environ.copy()
37
+
38
+ env["PWD"] = str(Path(case))
39
+
40
+ if os.environ.get("FOAM_LD_LIBRARY_PATH", "") and not os.environ.get(
41
+ "DYLD_LIBRARY_PATH", ""
42
+ ):
43
+ env["DYLD_LIBRARY_PATH"] = env["FOAM_LD_LIBRARY_PATH"]
44
+
45
+ return env
46
+
47
+
35
48
  def run_sync(
36
- cmd: Sequence[str | os.PathLike[str]] | str,
49
+ cmd: Sequence[str | os.PathLike[str]],
37
50
  *,
51
+ case: os.PathLike[str],
38
52
  check: bool = True,
39
- cwd: os.PathLike[str] | None = None,
40
- env: Mapping[str, str] | None = None,
41
- stdout: int | IO[bytes] | None = None,
42
- stderr: int | IO[bytes] | None = None,
43
- ) -> CompletedProcess[bytes]:
44
- if not isinstance(cmd, str) and sys.version_info < (3, 8):
53
+ stdout: int | IO[str] = DEVNULL,
54
+ stderr: int | IO[str] = STDOUT,
55
+ process_stdout: Callable[[str], None] = lambda _: None,
56
+ ) -> CompletedProcess[str]:
57
+ if sys.version_info < (3, 8):
45
58
  cmd = [str(arg) for arg in cmd]
46
59
 
47
- proc = subprocess.Popen(
60
+ with subprocess.Popen(
48
61
  cmd,
49
- cwd=cwd,
50
- env=env,
51
- stdout=stdout,
62
+ cwd=case,
63
+ env=_env(case),
64
+ stdout=PIPE,
52
65
  stderr=PIPE,
53
- shell=isinstance(cmd, str),
54
- )
55
-
56
- if stderr == STDOUT:
57
- stderr = stdout
58
- if stderr not in (PIPE, DEVNULL):
59
- stderr_copy = BytesIO()
60
-
61
- assert not isinstance(stderr, int)
62
- if stderr is None:
63
- stderr = sys.stderr.buffer
64
-
66
+ text=True,
67
+ ) as proc:
68
+ assert proc.stdout is not None
65
69
  assert proc.stderr is not None
66
- for line in proc.stderr:
67
- stderr.write(line)
68
- stderr_copy.write(line)
69
70
 
70
- output, _ = proc.communicate()
71
- assert not _
72
- error = stderr_copy.getvalue()
73
- else:
74
- output, error = proc.communicate()
71
+ output = StringIO() if stdout is PIPE else None
72
+ error = StringIO()
73
+
74
+ if stderr is STDOUT:
75
+ stderr = stdout
76
+
77
+ with selectors.DefaultSelector() as selector:
78
+ selector.register(proc.stdout, selectors.EVENT_READ)
79
+ selector.register(proc.stderr, selectors.EVENT_READ)
80
+ open_streams = {proc.stdout, proc.stderr}
81
+ while open_streams:
82
+ for key, _ in selector.select():
83
+ assert key.fileobj in open_streams
84
+ line = key.fileobj.readline() # type: ignore [union-attr]
85
+ if not line:
86
+ selector.unregister(key.fileobj)
87
+ open_streams.remove(key.fileobj) # type: ignore [arg-type]
88
+ elif key.fileobj is proc.stdout:
89
+ process_stdout(line)
90
+ if output is not None:
91
+ output.write(line)
92
+ if stdout not in (DEVNULL, PIPE):
93
+ assert not isinstance(stdout, int)
94
+ stdout.write(line)
95
+ else:
96
+ assert key.fileobj is proc.stderr
97
+ error.write(line)
98
+ if stderr not in (DEVNULL, PIPE):
99
+ assert not isinstance(stderr, int)
100
+ stderr.write(line)
75
101
 
76
102
  assert proc.returncode is not None
77
103
 
@@ -79,74 +105,84 @@ def run_sync(
79
105
  raise CalledProcessError(
80
106
  returncode=proc.returncode,
81
107
  cmd=cmd,
82
- output=output,
83
- stderr=error,
108
+ output=output.getvalue() if output is not None else None,
109
+ stderr=error.getvalue(),
84
110
  )
85
111
 
86
112
  return CompletedProcess(
87
- cmd, returncode=proc.returncode, stdout=output, stderr=error
113
+ cmd,
114
+ returncode=proc.returncode,
115
+ stdout=output.getvalue() if output is not None else None,
116
+ stderr=error.getvalue(),
88
117
  )
89
118
 
90
119
 
91
120
  async def run_async(
92
- cmd: Sequence[str | os.PathLike[str]] | str,
121
+ cmd: Sequence[str | os.PathLike[str]],
93
122
  *,
123
+ case: os.PathLike[str],
94
124
  check: bool = True,
95
- cwd: os.PathLike[str] | None = None,
96
- env: Mapping[str, str] | None = None,
97
- stdout: int | IO[bytes] | None = None,
98
- stderr: int | IO[bytes] | None = None,
99
- ) -> CompletedProcess[bytes]:
100
- if isinstance(cmd, str):
101
- proc = await asyncio.create_subprocess_shell(
102
- cmd,
103
- cwd=cwd,
104
- env=env,
105
- stdout=stdout,
106
- stderr=PIPE,
107
- )
125
+ stdout: int | IO[str] = DEVNULL,
126
+ stderr: int | IO[str] = STDOUT,
127
+ process_stdout: Callable[[str], None] = lambda _: None,
128
+ ) -> CompletedProcess[str]:
129
+ if sys.version_info < (3, 8):
130
+ cmd = [str(arg) for arg in cmd]
108
131
 
109
- else:
110
- if sys.version_info < (3, 8):
111
- cmd = [str(arg) for arg in cmd]
112
- proc = await asyncio.create_subprocess_exec(
113
- *cmd,
114
- cwd=cwd,
115
- env=env,
116
- stdout=stdout,
117
- stderr=PIPE,
118
- )
132
+ proc = await asyncio.create_subprocess_exec(
133
+ *cmd,
134
+ cwd=case,
135
+ env=_env(case),
136
+ stdout=PIPE,
137
+ stderr=PIPE,
138
+ )
119
139
 
120
- if stderr == STDOUT:
140
+ if stderr is STDOUT:
121
141
  stderr = stdout
122
- if stderr not in (PIPE, DEVNULL):
123
- stderr_copy = BytesIO()
124
-
125
- assert not isinstance(stderr, int)
126
- if stderr is None:
127
- stderr = sys.stderr.buffer
128
-
129
- assert proc.stderr is not None
130
- async for line in proc.stderr:
131
- stderr.write(line)
132
- stderr_copy.write(line)
133
-
134
- output, _ = await proc.communicate()
135
- assert not _
136
- error = stderr_copy.getvalue()
137
- else:
138
- output, error = await proc.communicate()
139
142
 
143
+ output = StringIO() if stdout is PIPE else None
144
+ error = StringIO()
145
+
146
+ async def tee_stdout() -> None:
147
+ while True:
148
+ assert proc.stdout is not None
149
+ line = (await proc.stdout.readline()).decode()
150
+ if not line:
151
+ break
152
+ process_stdout(line)
153
+ if output is not None:
154
+ output.write(line)
155
+ if stdout not in (DEVNULL, PIPE):
156
+ assert not isinstance(stdout, int)
157
+ stdout.write(line)
158
+
159
+ async def tee_stderr() -> None:
160
+ while True:
161
+ assert proc.stderr is not None
162
+ line = (await proc.stderr.readline()).decode()
163
+ if not line:
164
+ break
165
+ error.write(line)
166
+ if stderr not in (DEVNULL, PIPE):
167
+ assert not isinstance(stderr, int)
168
+ stderr.write(line)
169
+
170
+ await asyncio.gather(tee_stdout(), tee_stderr())
171
+
172
+ await proc.wait()
140
173
  assert proc.returncode is not None
141
174
 
142
175
  if check and proc.returncode != 0:
143
176
  raise CalledProcessError(
144
177
  returncode=proc.returncode,
145
178
  cmd=cmd,
146
- output=output,
147
- stderr=error,
179
+ output=output.getvalue() if output is not None else None,
180
+ stderr=error.getvalue(),
148
181
  )
149
182
 
150
183
  return CompletedProcess(
151
- cmd, returncode=proc.returncode, stdout=output, stderr=error
184
+ cmd,
185
+ returncode=proc.returncode,
186
+ stdout=output.getvalue() if output is not None else None,
187
+ stderr=error.getvalue(),
152
188
  )
foamlib/_cases/_sync.py CHANGED
@@ -58,6 +58,9 @@ class FoamCase(FoamCaseRunBase):
58
58
  cpus: int,
59
59
  **kwargs: Any,
60
60
  ) -> None:
61
+ if isinstance(cmd, str):
62
+ cmd = [*FoamCase._SHELL, cmd]
63
+
61
64
  run_sync(cmd, **kwargs)
62
65
 
63
66
  @staticmethod
foamlib/_cases/_util.py CHANGED
@@ -2,7 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  import functools
4
4
  import sys
5
- from typing import TYPE_CHECKING, Any, AsyncContextManager, Callable, Generic, TypeVar
5
+ import threading
6
+ from typing import (
7
+ TYPE_CHECKING,
8
+ Any,
9
+ AsyncContextManager,
10
+ Callable,
11
+ ContextManager,
12
+ Generic,
13
+ TypeVar,
14
+ cast,
15
+ )
6
16
 
7
17
  if TYPE_CHECKING:
8
18
  from types import TracebackType
@@ -54,3 +64,33 @@ def awaitableasynccontextmanager(
54
64
  return _AwaitableAsyncContextManager(cm(*args, **kwargs))
55
65
 
56
66
  return f
67
+
68
+
69
+ class SingletonContextManager(Generic[R]):
70
+ def __init__(self, factory: Callable[[], ContextManager[R]]) -> None:
71
+ self._factory = factory
72
+ self._users = 0
73
+ self._cm: ContextManager[R] | None = None
74
+ self._ret: R | None = None
75
+ self._lock = threading.Lock()
76
+
77
+ def __enter__(self) -> R:
78
+ with self._lock:
79
+ if self._users == 0:
80
+ self._cm = self._factory()
81
+ self._ret = self._cm.__enter__()
82
+ self._users += 1
83
+ return cast("R", self._ret)
84
+
85
+ def __exit__(
86
+ self,
87
+ exc_type: type[BaseException] | None,
88
+ exc_val: BaseException | None,
89
+ exc_tb: TracebackType | None,
90
+ ) -> bool | None:
91
+ with self._lock:
92
+ self._users -= 1
93
+ if self._users == 0:
94
+ assert self._cm is not None
95
+ return self._cm.__exit__(exc_type, exc_val, exc_tb)
96
+ return False
foamlib/_files/_files.py CHANGED
@@ -100,9 +100,9 @@ class FoamFile(
100
100
  assert isinstance(ret, dict)
101
101
  v = ret[k]
102
102
  assert isinstance(v, dict)
103
- ret = cast(File, v)
103
+ ret = cast("File", v)
104
104
 
105
- return cast(Dict_, ret)
105
+ return cast("Dict_", ret)
106
106
 
107
107
  @property
108
108
  def version(self) -> float:
@@ -127,7 +127,7 @@ class FoamFile(
127
127
  if ret not in ("ascii", "binary"):
128
128
  msg = "format is not 'ascii' or 'binary'"
129
129
  raise ValueError(msg)
130
- return cast(Literal["ascii", "binary"], ret)
130
+ return cast("Literal['ascii', 'binary']", ret)
131
131
 
132
132
  @format.setter
133
133
  def format(self, value: Literal["ascii", "binary"]) -> None:
@@ -412,7 +412,7 @@ class FoamFieldFile(FoamFile):
412
412
  ) -> Field:
413
413
  """Alias of `self["value"]`."""
414
414
  return cast(
415
- Field,
415
+ "Field",
416
416
  self["value"],
417
417
  )
418
418
 
@@ -461,7 +461,7 @@ class FoamFieldFile(FoamFile):
461
461
  self,
462
462
  ) -> Field:
463
463
  """Alias of `self["internalField"]`."""
464
- return cast(Field, self["internalField"])
464
+ return cast("Field", self["internalField"])
465
465
 
466
466
  @internal_field.setter
467
467
  def internal_field(
@@ -16,6 +16,7 @@ else:
16
16
 
17
17
  import numpy as np
18
18
  from pyparsing import (
19
+ CaselessKeyword,
19
20
  Combine,
20
21
  Dict,
21
22
  Forward,
@@ -157,6 +158,7 @@ def _dict_of(
157
158
  data: ParserElement,
158
159
  *,
159
160
  directive: ParserElement | None = None,
161
+ data_entry: ParserElement | None = None,
160
162
  located: bool = False,
161
163
  ) -> ParserElement:
162
164
  dict_ = Forward()
@@ -164,7 +166,8 @@ def _dict_of(
164
166
  keyword_entry = keyword + (dict_ | (data + Literal(";").suppress()))
165
167
 
166
168
  if directive is not None:
167
- keyword_entry |= directive + data + LineEnd().suppress() # type: ignore [no-untyped-call]
169
+ assert data_entry is not None
170
+ keyword_entry |= directive + data_entry + LineEnd().suppress()
168
171
 
169
172
  if located:
170
173
  keyword_entry = Located(keyword_entry)
@@ -183,15 +186,19 @@ def _keyword_entry_of(
183
186
  data: ParserElement,
184
187
  *,
185
188
  directive: ParserElement | None = None,
189
+ data_entry: ParserElement | None = None,
186
190
  located: bool = False,
187
191
  ) -> ParserElement:
188
192
  keyword_entry = keyword + (
189
- _dict_of(keyword, data, directive=directive, located=located)
193
+ _dict_of(
194
+ keyword, data, directive=directive, data_entry=data_entry, located=located
195
+ )
190
196
  | (data + Literal(";").suppress())
191
197
  )
192
198
 
193
199
  if directive is not None:
194
- keyword_entry |= directive + data + LineEnd().suppress() # type: ignore [no-untyped-call]
200
+ assert data_entry is not None
201
+ keyword_entry |= directive + data_entry + LineEnd().suppress()
195
202
 
196
203
  if located:
197
204
  keyword_entry = Located(keyword_entry)
@@ -261,7 +268,16 @@ _DICT = _dict_of(_TOKEN, _DATA)
261
268
  _DATA_ENTRY = Forward()
262
269
  _LIST_ENTRY = _DICT | _KEYWORD_ENTRY | _DATA_ENTRY
263
270
  _LIST = _list_of(_LIST_ENTRY)
264
- _NUMBER = common.signed_integer ^ common.ieee_float
271
+ _NUMBER = (
272
+ common.number
273
+ | CaselessKeyword("nan").set_parse_action(lambda: np.nan)
274
+ | (CaselessKeyword("inf") | CaselessKeyword("infinity")).set_parse_action(
275
+ lambda: np.inf
276
+ )
277
+ | (CaselessKeyword("-inf") | CaselessKeyword("-infinity")).set_parse_action(
278
+ lambda: -np.inf
279
+ )
280
+ )
265
281
  _DATA_ENTRY <<= _FIELD | _LIST | _DIMENSIONED | _DIMENSIONS | _NUMBER | _SWITCH | _TOKEN
266
282
 
267
283
  _DATA <<= (
@@ -275,12 +291,16 @@ _DATA <<= (
275
291
  def parse_data(s: str) -> Data:
276
292
  if not s.strip():
277
293
  return ""
278
- return cast(Data, _DATA.parse_string(s, parse_all=True)[0])
294
+ return cast("Data", _DATA.parse_string(s, parse_all=True)[0])
279
295
 
280
296
 
281
297
  _LOCATED_DICTIONARY = Group(
282
298
  _keyword_entry_of(
283
- _TOKEN, Opt(_DATA, default=""), directive=_DIRECTIVE, located=True
299
+ _TOKEN,
300
+ Opt(_DATA, default=""),
301
+ directive=_DIRECTIVE,
302
+ data_entry=_DATA_ENTRY,
303
+ located=True,
284
304
  )
285
305
  )[...]
286
306
  _LOCATED_DATA = Group(Located(_DATA.copy().add_parse_action(lambda tks: ["", tks[0]])))
@@ -421,7 +441,7 @@ class Parsed(Mapping[Tuple[str, ...], Union[Data, EllipsisType]]):
421
441
  for k in keywords[:-1]:
422
442
  v = r[k]
423
443
  assert isinstance(v, dict)
424
- r = cast(File, v)
444
+ r = cast("File", v)
425
445
 
426
446
  assert isinstance(r, dict)
427
447
  if keywords:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: foamlib
3
- Version: 0.8.5
3
+ Version: 0.8.7
4
4
  Summary: A Python interface for interacting with OpenFOAM
5
5
  Project-URL: Homepage, https://github.com/gerlero/foamlib
6
6
  Project-URL: Repository, https://github.com/gerlero/foamlib
@@ -29,6 +29,7 @@ Requires-Dist: aioshutil<2,>=1
29
29
  Requires-Dist: numpy<3,>=1
30
30
  Requires-Dist: numpy<3,>=1.25.0; python_version >= '3.10'
31
31
  Requires-Dist: pyparsing<4,>=3.1.2
32
+ Requires-Dist: rich<14,>=13
32
33
  Requires-Dist: typing-extensions<5,>=4; python_version < '3.11'
33
34
  Provides-Extra: dev
34
35
  Requires-Dist: mypy<2,>=1; extra == 'dev'
@@ -0,0 +1,20 @@
1
+ foamlib/__init__.py,sha256=TrTrsv_QjPqa16duuXQl5FgjxRPwuHlhO-MHOtxUzdY,452
2
+ foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ foamlib/_cases/__init__.py,sha256=_A1TTHuQfS9FH2_33lSEyLtOJZGFHZBco1tWJCVOHks,358
4
+ foamlib/_cases/_async.py,sha256=vXpq5T2gDomuawARsLaUEMjUBhMsZK58q7iVwQpXsYE,7903
5
+ foamlib/_cases/_base.py,sha256=37oBbM3NM-hpG7dKewZvyJNtqSAogMurcbmX-wLIgMU,6727
6
+ foamlib/_cases/_run.py,sha256=IRsozHdXx5tjLYYIWMiKGeaf5188unhp5VwIAZLlr3E,15578
7
+ foamlib/_cases/_slurm.py,sha256=Hzpf5Ugahwq7sUsCfhZ6JgXDOdkoYGGsHGiO7NV8uFs,2331
8
+ foamlib/_cases/_subprocess.py,sha256=VHV2SuOLqa711an6kCuvN6UlIkeh4qqFfdrpNoKzQps,5630
9
+ foamlib/_cases/_sync.py,sha256=e06aGGZ9n6WTWK9eWZhrezsT4yebGhKPE0sn0nwQH8A,5977
10
+ foamlib/_cases/_util.py,sha256=QCizfbuJdOCeF9ogU2R-y-iWX5kfaOA4U2W68t6QlOM,2544
11
+ foamlib/_files/__init__.py,sha256=q1vkjXnjnSZvo45jPAICpWeF2LZv5V6xfzAR6S8fS5A,96
12
+ foamlib/_files/_files.py,sha256=YAKw3RHEw8eCja4JAxobp6BC-2Kyy7AvG1O7yOqyMic,15414
13
+ foamlib/_files/_io.py,sha256=BGbbm6HKxL2ka0YMCmHqZQZ1R4PPQlkvWWb4FHMAS8k,2217
14
+ foamlib/_files/_parsing.py,sha256=XrqG8IcTM--qZYqEHYO_jcCTb7ev9M9kz75T_DQtPuc,14047
15
+ foamlib/_files/_serialization.py,sha256=PvMzNyTja6OKT_GUfExTulx9nMLBjfu0-9yThCKbvxs,5227
16
+ foamlib/_files/_types.py,sha256=m-fFjJnS4sFSavDsijlXpAfEhnbh10RBumSHAT0GOgQ,3408
17
+ foamlib-0.8.7.dist-info/METADATA,sha256=HVzB10Bdz97hbXxBDunpGy-_seeNduk5msDAKVCXmx4,7996
18
+ foamlib-0.8.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
+ foamlib-0.8.7.dist-info/licenses/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
20
+ foamlib-0.8.7.dist-info/RECORD,,
@@ -1,20 +0,0 @@
1
- foamlib/__init__.py,sha256=vhMMDd4SJsEtAYoKY35ByJmFCQcyDMEb-WpdS8ERF3g,452
2
- foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- foamlib/_cases/__init__.py,sha256=_A1TTHuQfS9FH2_33lSEyLtOJZGFHZBco1tWJCVOHks,358
4
- foamlib/_cases/_async.py,sha256=onECxRLQCF7Kd-GeuLqH_Xv3gbMMhKOSbFaUG5Ttgmk,7822
5
- foamlib/_cases/_base.py,sha256=37oBbM3NM-hpG7dKewZvyJNtqSAogMurcbmX-wLIgMU,6727
6
- foamlib/_cases/_run.py,sha256=lveqKZium_qK_eTxYE8jOjwx0eiIoolCBbi56-zLw1o,14420
7
- foamlib/_cases/_slurm.py,sha256=kj4wqgr3foMyAoUkoHOZODRBmVqH1B9KqAIEEjM8ZBg,2328
8
- foamlib/_cases/_subprocess.py,sha256=6BlBRxknj2-BFcGkx7oVcuL63_utSaY1Axmsc1qV9j8,3887
9
- foamlib/_cases/_sync.py,sha256=2BJXB7Nzldb4OgPukqupgYqdceUGkI2mYhhtGPWEBrc,5901
10
- foamlib/_cases/_util.py,sha256=tK4SM5WT3eEgGsFLnidIySbom1qowBAua9z13gipKJk,1518
11
- foamlib/_files/__init__.py,sha256=q1vkjXnjnSZvo45jPAICpWeF2LZv5V6xfzAR6S8fS5A,96
12
- foamlib/_files/_files.py,sha256=V740S36NVunULv8pnyEsJ7lIWbSjk_-LwbP7BBqFsO0,15404
13
- foamlib/_files/_io.py,sha256=BGbbm6HKxL2ka0YMCmHqZQZ1R4PPQlkvWWb4FHMAS8k,2217
14
- foamlib/_files/_parsing.py,sha256=uM9eD67Coo5K-F2EWkbzLfpdTr56mUbLw2kE8Q94-PE,13549
15
- foamlib/_files/_serialization.py,sha256=PvMzNyTja6OKT_GUfExTulx9nMLBjfu0-9yThCKbvxs,5227
16
- foamlib/_files/_types.py,sha256=m-fFjJnS4sFSavDsijlXpAfEhnbh10RBumSHAT0GOgQ,3408
17
- foamlib-0.8.5.dist-info/METADATA,sha256=H5RgbkxMQZf-PG38gx3Ckhu3q6eqOYndn457-htff84,7968
18
- foamlib-0.8.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
- foamlib-0.8.5.dist-info/licenses/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
20
- foamlib-0.8.5.dist-info/RECORD,,