foamlib 0.8.2__tar.gz → 0.8.4__tar.gz

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.
Files changed (51) hide show
  1. {foamlib-0.8.2 → foamlib-0.8.4}/.github/workflows/ci.yml +5 -5
  2. {foamlib-0.8.2 → foamlib-0.8.4}/.github/workflows/pypi-publish.yml +1 -1
  3. {foamlib-0.8.2 → foamlib-0.8.4}/PKG-INFO +6 -5
  4. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/__init__.py +1 -1
  5. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_files/_files.py +3 -3
  6. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_files/_parsing.py +26 -9
  7. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_files/_serialization.py +5 -2
  8. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_files/_types.py +1 -1
  9. {foamlib-0.8.2 → foamlib-0.8.4}/pyproject.toml +1 -1
  10. {foamlib-0.8.2 → foamlib-0.8.4}/tests/test_files/test_dumps.py +1 -1
  11. {foamlib-0.8.2 → foamlib-0.8.4}/tests/test_files/test_files.py +7 -7
  12. {foamlib-0.8.2 → foamlib-0.8.4}/tests/test_files/test_parsing.py +9 -0
  13. {foamlib-0.8.2 → foamlib-0.8.4}/.devcontainer.json +0 -0
  14. {foamlib-0.8.2 → foamlib-0.8.4}/.dockerignore +0 -0
  15. {foamlib-0.8.2 → foamlib-0.8.4}/.git-blame-ignore-revs +0 -0
  16. {foamlib-0.8.2 → foamlib-0.8.4}/.github/dependabot.yml +0 -0
  17. {foamlib-0.8.2 → foamlib-0.8.4}/.github/workflows/docker.yml +0 -0
  18. {foamlib-0.8.2 → foamlib-0.8.4}/.github/workflows/dockerhub-description.yml +0 -0
  19. {foamlib-0.8.2 → foamlib-0.8.4}/.gitignore +0 -0
  20. {foamlib-0.8.2 → foamlib-0.8.4}/.readthedocs.yaml +0 -0
  21. {foamlib-0.8.2 → foamlib-0.8.4}/Dockerfile +0 -0
  22. {foamlib-0.8.2 → foamlib-0.8.4}/LICENSE.txt +0 -0
  23. {foamlib-0.8.2 → foamlib-0.8.4}/README.md +0 -0
  24. {foamlib-0.8.2 → foamlib-0.8.4}/benchmark.png +0 -0
  25. {foamlib-0.8.2 → foamlib-0.8.4}/docs/Makefile +0 -0
  26. {foamlib-0.8.2 → foamlib-0.8.4}/docs/cases.rst +0 -0
  27. {foamlib-0.8.2 → foamlib-0.8.4}/docs/conf.py +0 -0
  28. {foamlib-0.8.2 → foamlib-0.8.4}/docs/files.rst +0 -0
  29. {foamlib-0.8.2 → foamlib-0.8.4}/docs/index.rst +0 -0
  30. {foamlib-0.8.2 → foamlib-0.8.4}/docs/make.bat +0 -0
  31. {foamlib-0.8.2 → foamlib-0.8.4}/docs/ruff.toml +0 -0
  32. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_cases/__init__.py +0 -0
  33. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_cases/_async.py +0 -0
  34. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_cases/_base.py +0 -0
  35. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_cases/_run.py +0 -0
  36. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_cases/_slurm.py +0 -0
  37. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_cases/_subprocess.py +0 -0
  38. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_cases/_sync.py +0 -0
  39. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_cases/_util.py +0 -0
  40. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_files/__init__.py +0 -0
  41. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/_files/_io.py +0 -0
  42. {foamlib-0.8.2 → foamlib-0.8.4}/foamlib/py.typed +0 -0
  43. {foamlib-0.8.2 → foamlib-0.8.4}/logo.png +0 -0
  44. {foamlib-0.8.2 → foamlib-0.8.4}/tests/__init__.py +0 -0
  45. {foamlib-0.8.2 → foamlib-0.8.4}/tests/ruff.toml +0 -0
  46. {foamlib-0.8.2 → foamlib-0.8.4}/tests/test_cases/__init__.py +0 -0
  47. {foamlib-0.8.2 → foamlib-0.8.4}/tests/test_cases/test_cavity.py +0 -0
  48. {foamlib-0.8.2 → foamlib-0.8.4}/tests/test_cases/test_cavity_async.py +0 -0
  49. {foamlib-0.8.2 → foamlib-0.8.4}/tests/test_cases/test_flange.py +0 -0
  50. {foamlib-0.8.2 → foamlib-0.8.4}/tests/test_cases/test_flange_async.py +0 -0
  51. {foamlib-0.8.2 → foamlib-0.8.4}/tests/test_files/__init__.py +0 -0
@@ -16,9 +16,9 @@ jobs:
16
16
  - name: Checkout
17
17
  uses: actions/checkout@v4
18
18
  - name: Lint with Ruff
19
- uses: astral-sh/ruff-action@v2
19
+ uses: astral-sh/ruff-action@v3
20
20
  - name: Format with Ruff
21
- uses: astral-sh/ruff-action@v2
21
+ uses: astral-sh/ruff-action@v3
22
22
  with:
23
23
  args: 'format --check'
24
24
 
@@ -28,7 +28,7 @@ jobs:
28
28
  - name: Checkout
29
29
  uses: actions/checkout@v4
30
30
  - name: Install uv
31
- uses: astral-sh/setup-uv@v4
31
+ uses: astral-sh/setup-uv@v5
32
32
  - name: Set up Python
33
33
  uses: actions/setup-python@v5
34
34
  with:
@@ -74,7 +74,7 @@ jobs:
74
74
  with:
75
75
  openfoam-version: ${{ matrix.openfoam-version }}
76
76
  - name: Install uv
77
- uses: astral-sh/setup-uv@v4
77
+ uses: astral-sh/setup-uv@v5
78
78
  - name: Set up Python ${{ matrix.python-version }}
79
79
  uses: actions/setup-python@v5
80
80
  with:
@@ -103,7 +103,7 @@ jobs:
103
103
  - name: Checkout
104
104
  uses: actions/checkout@v4
105
105
  - name: Install uv
106
- uses: astral-sh/setup-uv@v4
106
+ uses: astral-sh/setup-uv@v5
107
107
  - name: Check package build
108
108
  run: |
109
109
  uv build
@@ -17,7 +17,7 @@ jobs:
17
17
  - name: Checkout
18
18
  uses: actions/checkout@v4
19
19
  - name: Install uv
20
- uses: astral-sh/setup-uv@v4
20
+ uses: astral-sh/setup-uv@v5
21
21
  - name: Build
22
22
  run: uv build
23
23
  - name: Publish to PyPI
@@ -1,11 +1,12 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: foamlib
3
- Version: 0.8.2
3
+ Version: 0.8.4
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
7
7
  Project-URL: Documentation, https://foamlib.readthedocs.io
8
8
  Author-email: "Gabriel S. Gerlero" <ggerlero@cimec.unl.edu.ar>
9
+ License-File: LICENSE.txt
9
10
  Classifier: Development Status :: 4 - Beta
10
11
  Classifier: Framework :: AsyncIO
11
12
  Classifier: Intended Audience :: Developers
@@ -31,7 +32,7 @@ Requires-Dist: pyparsing<4,>=3.1.2
31
32
  Requires-Dist: typing-extensions<5,>=4; python_version < '3.11'
32
33
  Provides-Extra: dev
33
34
  Requires-Dist: mypy<2,>=1; extra == 'dev'
34
- Requires-Dist: pytest-asyncio<0.25,>=0.21; extra == 'dev'
35
+ Requires-Dist: pytest-asyncio<0.26,>=0.21; extra == 'dev'
35
36
  Requires-Dist: pytest-cov; extra == 'dev'
36
37
  Requires-Dist: pytest<9,>=7; extra == 'dev'
37
38
  Requires-Dist: ruff; extra == 'dev'
@@ -43,12 +44,12 @@ Requires-Dist: sphinx<9,>=5; extra == 'docs'
43
44
  Provides-Extra: lint
44
45
  Requires-Dist: ruff; extra == 'lint'
45
46
  Provides-Extra: test
46
- Requires-Dist: pytest-asyncio<0.25,>=0.21; extra == 'test'
47
+ Requires-Dist: pytest-asyncio<0.26,>=0.21; extra == 'test'
47
48
  Requires-Dist: pytest-cov; extra == 'test'
48
49
  Requires-Dist: pytest<9,>=7; extra == 'test'
49
50
  Provides-Extra: typing
50
51
  Requires-Dist: mypy<2,>=1; extra == 'typing'
51
- Requires-Dist: pytest-asyncio<0.25,>=0.21; extra == 'typing'
52
+ Requires-Dist: pytest-asyncio<0.26,>=0.21; extra == 'typing'
52
53
  Requires-Dist: pytest-cov; extra == 'typing'
53
54
  Requires-Dist: pytest<9,>=7; extra == 'typing'
54
55
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  """A Python interface for interacting with OpenFOAM."""
2
2
 
3
- __version__ = "0.8.2"
3
+ __version__ = "0.8.4"
4
4
 
5
5
  from ._cases import (
6
6
  AsyncFoamCase,
@@ -308,15 +308,15 @@ class FoamFile(
308
308
  self[(*keywords, k)] = v
309
309
 
310
310
  elif keywords:
311
+ val = dumps(data, kind=kind)
311
312
  parsed.put(
312
313
  keywords,
313
314
  normalize(data, kind=kind),
314
315
  before
315
316
  + indentation
316
317
  + dumps(keywords[-1])
317
- + b" "
318
- + dumps(data, kind=kind)
319
- + b";"
318
+ + ((b" " + val) if val else b"")
319
+ + (b";" if not keywords[-1].startswith("#") else b"")
320
320
  + after,
321
321
  )
322
322
 
@@ -89,7 +89,7 @@ def _parse_ascii_field(
89
89
  s = re.sub(ignore.re, " ", s)
90
90
  s = s.replace("(", " ").replace(")", " ")
91
91
 
92
- return np.fromstring(s, sep=" ").reshape(-1, *tensor_kind.shape)
92
+ return np.fromstring(s, sep=" ").reshape(-1, *tensor_kind.shape) # type: ignore [return-value]
93
93
 
94
94
 
95
95
  def _unpack_binary_field(
@@ -99,7 +99,7 @@ def _unpack_binary_field(
99
99
  assert float_size in (4, 8)
100
100
 
101
101
  dtype = np.float32 if float_size == 4 else float
102
- return np.frombuffer(b, dtype=dtype).reshape(-1, *tensor_kind.shape)
102
+ return np.frombuffer(b, dtype=dtype).reshape(-1, *tensor_kind.shape) # type: ignore [return-value]
103
103
 
104
104
 
105
105
  def _tensor_list(
@@ -153,12 +153,19 @@ def _tensor_list(
153
153
 
154
154
 
155
155
  def _dict_of(
156
- keyword: ParserElement, data: ParserElement, *, located: bool = False
156
+ keyword: ParserElement,
157
+ data: ParserElement,
158
+ *,
159
+ directive: ParserElement | None = None,
160
+ located: bool = False,
157
161
  ) -> ParserElement:
158
162
  dict_ = Forward()
159
163
 
160
164
  keyword_entry = keyword + (dict_ | (data + Literal(";").suppress()))
161
165
 
166
+ if directive is not None:
167
+ keyword_entry |= directive + data + LineEnd().suppress() # type: ignore [no-untyped-call]
168
+
162
169
  if located:
163
170
  keyword_entry = Located(keyword_entry)
164
171
 
@@ -175,12 +182,17 @@ def _keyword_entry_of(
175
182
  keyword: ParserElement,
176
183
  data: ParserElement,
177
184
  *,
185
+ directive: ParserElement | None = None,
178
186
  located: bool = False,
179
187
  ) -> ParserElement:
180
188
  keyword_entry = keyword + (
181
- _dict_of(keyword, data, located=located) | (data + Literal(";").suppress())
189
+ _dict_of(keyword, data, directive=directive, located=located)
190
+ | (data + Literal(";").suppress())
182
191
  )
183
192
 
193
+ if directive is not None:
194
+ keyword_entry |= directive + data + LineEnd().suppress() # type: ignore [no-untyped-call]
195
+
184
196
  if located:
185
197
  keyword_entry = Located(keyword_entry)
186
198
  else:
@@ -223,9 +235,10 @@ _TENSOR = (
223
235
  | _tensor(TensorKind.SYMM_TENSOR)
224
236
  | _tensor(TensorKind.TENSOR)
225
237
  )
226
- _IDENTIFIER = Combine(
238
+ _IDENTIFIER = Forward()
239
+ _IDENTIFIER <<= Combine(
227
240
  Word(_IDENTCHARS, _IDENTBODYCHARS, exclude_chars="()")
228
- + Opt(Literal("(") + Word(_IDENTBODYCHARS, exclude_chars="()") + Literal(")"))
241
+ + Opt(Literal("(") + _IDENTIFIER + Literal(")"))
229
242
  )
230
243
  _DIMENSIONED = (Opt(_IDENTIFIER) + _DIMENSIONS + _TENSOR).set_parse_action(
231
244
  lambda tks: Dimensioned(*reversed(tks.as_list()))
@@ -239,7 +252,8 @@ _FIELD = (Keyword("uniform", _IDENTBODYCHARS).suppress() + _TENSOR) | (
239
252
  | _tensor_list(TensorKind.TENSOR, ignore=_COMMENT)
240
253
  )
241
254
  )
242
- _TOKEN = dbl_quoted_string | _IDENTIFIER
255
+ _DIRECTIVE = Word("#", _IDENTBODYCHARS)
256
+ _TOKEN = dbl_quoted_string | _IDENTIFIER | _DIRECTIVE
243
257
  _DATA = Forward()
244
258
  _KEYWORD_ENTRY = _keyword_entry_of(_TOKEN | _list_of(_IDENTIFIER), _DATA)
245
259
  _DICT = _dict_of(_TOKEN, _DATA)
@@ -258,18 +272,21 @@ _DATA <<= (
258
272
 
259
273
 
260
274
  def parse_data(s: str) -> Data:
275
+ if not s.strip():
276
+ return ""
261
277
  return cast(Data, _DATA.parse_string(s, parse_all=True)[0])
262
278
 
263
279
 
264
280
  _LOCATED_DICTIONARY = Group(
265
- _keyword_entry_of(_TOKEN, Opt(_DATA, default=""), located=True)
281
+ _keyword_entry_of(
282
+ _TOKEN, Opt(_DATA, default=""), directive=_DIRECTIVE, located=True
283
+ )
266
284
  )[...]
267
285
  _LOCATED_DATA = Group(Located(_DATA.copy().add_parse_action(lambda tks: ["", tks[0]])))
268
286
 
269
287
  _FILE = (
270
288
  Dict(_LOCATED_DICTIONARY + Opt(_LOCATED_DATA) + _LOCATED_DICTIONARY)
271
289
  .ignore(_COMMENT)
272
- .ignore(Literal("#include") + ... + LineEnd()) # type: ignore [no-untyped-call]
273
290
  .parse_with_tabs()
274
291
  )
275
292
 
@@ -50,7 +50,7 @@ def normalize(data: Entry, *, kind: Kind = Kind.DEFAULT) -> Entry:
50
50
  arr = arr.astype(float)
51
51
 
52
52
  if arr.ndim == 1 or (arr.ndim == 2 and arr.shape[1] in (3, 6, 9)):
53
- return arr
53
+ return arr # type: ignore [return-value]
54
54
 
55
55
  return data
56
56
 
@@ -111,7 +111,10 @@ def dumps(
111
111
 
112
112
  if isinstance(data, tuple) and kind == Kind.SINGLE_ENTRY and len(data) == 2:
113
113
  k, v = data
114
- ret = dumps(k) + b" " + dumps(v)
114
+ ret = dumps(k)
115
+ val = dumps(v)
116
+ if val:
117
+ ret += b" " + val
115
118
  if not isinstance(v, Mapping):
116
119
  ret += b";"
117
120
  return ret
@@ -79,7 +79,7 @@ class Dimensioned:
79
79
 
80
80
  def __post_init__(self) -> None:
81
81
  if is_sequence(self.value):
82
- self.value = np.asarray(self.value, dtype=float)
82
+ self.value = np.asarray(self.value, dtype=float) # type: ignore [assignment]
83
83
  else:
84
84
  assert isinstance(self.value, (int, float, np.ndarray))
85
85
  self.value = float(self.value)
@@ -42,7 +42,7 @@ dynamic = ["version"]
42
42
  lint = ["ruff"]
43
43
  test = [
44
44
  "pytest>=7,<9",
45
- "pytest-asyncio>=0.21,<0.25",
45
+ "pytest-asyncio>=0.21,<0.26",
46
46
  "pytest-cov",
47
47
  ]
48
48
  typing = [
@@ -37,7 +37,7 @@ def test_serialize_data() -> None:
37
37
  == b"nonuniform List<vector> 2(\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x00\x18@)"
38
38
  )
39
39
  assert (
40
- dumps(np.array([1, 2], dtype=np.float32), kind=Kind.BINARY_FIELD)
40
+ dumps(np.array([1, 2], dtype=np.float32), kind=Kind.BINARY_FIELD) # type: ignore [arg-type]
41
41
  == b"nonuniform List<scalar> 2(\x00\x00\x80?\x00\x00\x00@)"
42
42
  )
43
43
  assert (
@@ -163,19 +163,19 @@ def test_internal_field(cavity: FoamCase) -> None:
163
163
  p_arr = np.zeros(size)
164
164
  U_arr = np.zeros((size, 3))
165
165
 
166
- cavity[0]["p"].internal_field = p_arr
167
- cavity[0]["U"].internal_field = U_arr
166
+ cavity[0]["p"].internal_field = p_arr # type: ignore [assignment]
167
+ cavity[0]["U"].internal_field = U_arr # type: ignore [assignment]
168
168
 
169
169
  assert cavity[0]["p"].internal_field == pytest.approx(p_arr)
170
170
  U = cavity[0]["U"].internal_field
171
171
  assert isinstance(U, np.ndarray)
172
172
  assert U_arr == pytest.approx(U)
173
173
 
174
- p_arr = np.arange(size) * 1e-6
174
+ p_arr = np.arange(size) * 1e-6 # type: ignore [assignment]
175
175
  U_arr = np.full((size, 3), [-1e-6, 1e-6, 0]) * np.arange(size)[:, np.newaxis]
176
176
 
177
- cavity[0]["p"].internal_field = p_arr
178
- cavity[0]["U"].internal_field = U_arr
177
+ cavity[0]["p"].internal_field = p_arr # type: ignore [assignment]
178
+ cavity[0]["U"].internal_field = U_arr # type: ignore [assignment]
179
179
 
180
180
  assert cavity[0]["p"].internal_field == pytest.approx(p_arr)
181
181
  U = cavity[0]["U"].internal_field
@@ -210,7 +210,7 @@ def test_binary_field(cavity: FoamCase) -> None:
210
210
  p_arr = np.arange(len(p_bin)) * 1e-6
211
211
  U_arr = np.full_like(U_bin, [-1e-6, 1e-6, 0]) * np.arange(len(U_bin))[:, np.newaxis]
212
212
 
213
- cavity[0]["p"].internal_field = p_arr
213
+ cavity[0]["p"].internal_field = p_arr # type: ignore [assignment]
214
214
  cavity[0]["U"].internal_field = U_arr
215
215
 
216
216
  assert cavity[0]["p"].internal_field == pytest.approx(p_arr)
@@ -237,7 +237,7 @@ def test_compressed_field(cavity: FoamCase) -> None:
237
237
  p_arr = np.arange(len(p_bin)) * 1e-6
238
238
  U_arr = np.full_like(U_bin, [-1e-6, 1e-6, 0]) * np.arange(len(U_bin))[:, np.newaxis]
239
239
 
240
- cavity[0]["p"].internal_field = p_arr
240
+ cavity[0]["p"].internal_field = p_arr # type: ignore [assignment]
241
241
  cavity[0]["U"].internal_field = U_arr
242
242
 
243
243
  assert cavity[0]["p"].internal_field == pytest.approx(p_arr)
@@ -92,8 +92,17 @@ def test_parse_value() -> None:
92
92
  assert Parsed(b"({a b; c d;} {e g;})")[()] == [{"a": "b", "c": "d"}, {"e": "g"}]
93
93
  assert Parsed(b"(water oil mercury air)")[()] == ["water", "oil", "mercury", "air"]
94
94
  assert Parsed(b"div(phi,U)")[()] == "div(phi,U)"
95
+ assert Parsed(b"div(nuEff*dev(T(grad(U))))")[()] == "div(nuEff*dev(T(grad(U))))"
95
96
  assert Parsed(b"((air and water) { type constant; sigma 0.07; })")[()] == [
96
97
  (["air", "and", "water"], {"type": "constant", "sigma": 0.07})
97
98
  ]
98
99
  assert Parsed(b"[]")[()] == FoamFile.DimensionSet()
99
100
  assert Parsed(b"object f.1;")[("object",)] == "f.1"
101
+
102
+
103
+ def test_parse_directive() -> None:
104
+ assert Parsed(b'#include "filename"')[("#include",)] == '"filename"'
105
+ assert (
106
+ Parsed(b"functions\n{\n#includeFunc funcName\n}")[("functions", "#includeFunc")]
107
+ == "funcName"
108
+ )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes