foamlib 0.3.22__py3-none-any.whl → 0.4.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.3.22"
3
+ __version__ = "0.4.0"
4
4
 
5
5
  from ._cases import (
6
6
  AsyncFoamCase,
@@ -9,7 +9,7 @@ from ._cases import (
9
9
  FoamCase,
10
10
  FoamCaseBase,
11
11
  )
12
- from ._files import FoamDict, FoamFieldFile, FoamFile
12
+ from ._files import FoamFieldFile, FoamFile, FoamFileBase
13
13
 
14
14
  __all__ = [
15
15
  "FoamCase",
@@ -17,7 +17,7 @@ __all__ = [
17
17
  "FoamCaseBase",
18
18
  "FoamFile",
19
19
  "FoamFieldFile",
20
- "FoamDict",
20
+ "FoamFileBase",
21
21
  "CalledProcessError",
22
22
  "CalledProcessWarning",
23
23
  ]
foamlib/_cases/_base.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import shutil
2
3
  import sys
3
4
  from pathlib import Path
4
5
  from typing import (
@@ -62,12 +63,10 @@ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
62
63
  return self.path.name
63
64
 
64
65
  def __getitem__(self, key: str) -> FoamFieldFile:
65
- if (self.path / key).is_file():
66
- return FoamFieldFile(self.path / key)
67
- elif (self.path / f"{key}.gz").is_file():
66
+ if (self.path / f"{key}.gz").is_file() and not (self.path / key).is_file():
68
67
  return FoamFieldFile(self.path / f"{key}.gz")
69
68
  else:
70
- raise KeyError(key)
69
+ return FoamFieldFile(self.path / key)
71
70
 
72
71
  def __contains__(self, obj: object) -> bool:
73
72
  if isinstance(obj, FoamFieldFile):
@@ -89,6 +88,12 @@ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
89
88
  def __len__(self) -> int:
90
89
  return len(list(iter(self)))
91
90
 
91
+ def __delitem__(self, key: str) -> None:
92
+ if (self.path / f"{key}.gz").is_file() and not (self.path / key).is_file():
93
+ (self.path / f"{key}.gz").unlink()
94
+ else:
95
+ (self.path / key).unlink()
96
+
92
97
  def __fspath__(self) -> str:
93
98
  return str(self.path)
94
99
 
@@ -167,6 +172,9 @@ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
167
172
 
168
173
  return paths
169
174
 
175
+ def __delitem__(self, key: Union[int, float, str]) -> None:
176
+ shutil.rmtree(self[key].path)
177
+
170
178
  def _clone_ignore(
171
179
  self,
172
180
  ) -> Callable[[Union[Path, str], Collection[str]], Collection[str]]:
@@ -1,8 +1,8 @@
1
- from ._base import FoamDict
1
+ from ._base import FoamFileBase
2
2
  from ._files import FoamFieldFile, FoamFile
3
3
 
4
4
  __all__ = [
5
5
  "FoamFile",
6
6
  "FoamFieldFile",
7
- "FoamDict",
7
+ "FoamFileBase",
8
8
  ]
foamlib/_files/_base.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import sys
2
- from abc import abstractmethod
3
2
  from dataclasses import dataclass
4
3
  from typing import Dict, NamedTuple, Optional, Tuple, Union
5
4
 
@@ -14,7 +13,7 @@ except ModuleNotFoundError:
14
13
  pass
15
14
 
16
15
 
17
- class FoamDict:
16
+ class FoamFileBase:
18
17
  class DimensionSet(NamedTuple):
19
18
  mass: Union[int, float] = 0
20
19
  length: Union[int, float] = 0
@@ -30,12 +29,12 @@ class FoamDict:
30
29
  @dataclass
31
30
  class Dimensioned:
32
31
  value: Union[int, float, Sequence[Union[int, float]]] = 0
33
- dimensions: Union["FoamDict.DimensionSet", Sequence[Union[int, float]]] = ()
32
+ dimensions: Union["FoamFileBase.DimensionSet", Sequence[Union[int, float]]] = ()
34
33
  name: Optional[str] = None
35
34
 
36
35
  def __post_init__(self) -> None:
37
- if not isinstance(self.dimensions, FoamDict.DimensionSet):
38
- self.dimensions = FoamDict.DimensionSet(*self.dimensions)
36
+ if not isinstance(self.dimensions, FoamFileBase.DimensionSet):
37
+ self.dimensions = FoamFileBase.DimensionSet(*self.dimensions)
39
38
 
40
39
  Data = Union[
41
40
  str,
@@ -48,16 +47,11 @@ class FoamDict:
48
47
  Mapping[str, "Data"],
49
48
  ]
50
49
  """
51
- A value that can be stored in an OpenFOAM dictionary.
50
+ A value that can be stored in an OpenFOAM file.
52
51
  """
53
52
 
54
53
  _Dict = Dict[str, Union["Data", "_Dict"]]
55
54
 
56
- @abstractmethod
57
- def as_dict(self) -> _Dict:
58
- """Return a nested dict representation of the dictionary."""
59
- raise NotImplementedError
60
-
61
55
  _SetData = Union[
62
56
  str,
63
57
  int,
foamlib/_files/_files.py CHANGED
@@ -1,14 +1,24 @@
1
1
  import sys
2
- from typing import Any, Tuple, Union, cast
2
+ from typing import Any, Optional, Tuple, Union, cast
3
+
4
+ if sys.version_info >= (3, 8):
5
+ from typing import Literal
6
+ else:
7
+ from typing_extensions import Literal
3
8
 
4
9
  if sys.version_info >= (3, 9):
5
10
  from collections.abc import Iterator, Mapping, MutableMapping, Sequence
6
11
  else:
7
12
  from typing import Iterator, Mapping, MutableMapping, Sequence
8
13
 
14
+ if sys.version_info >= (3, 11):
15
+ from typing import Self
16
+ else:
17
+ from typing_extensions import Self
18
+
9
19
  from .._util import is_sequence
10
- from ._base import FoamDict
11
- from ._io import FoamFileIO
20
+ from ._base import FoamFileBase
21
+ from ._io import _FoamFileIO
12
22
  from ._serialization import Kind, dumpb
13
23
 
14
24
  try:
@@ -18,11 +28,12 @@ except ModuleNotFoundError:
18
28
 
19
29
 
20
30
  class FoamFile(
21
- FoamDict,
31
+ FoamFileBase,
22
32
  MutableMapping[
23
- Union[str, Tuple[str, ...]], Union["FoamFile.Data", "FoamFile.SubDict"]
33
+ Optional[Union[str, Tuple[str, ...]]],
34
+ Union["FoamFile.Data", "FoamFile.SubDict"],
24
35
  ],
25
- FoamFileIO,
36
+ _FoamFileIO,
26
37
  ):
27
38
  """
28
39
  An OpenFOAM data file.
@@ -33,7 +44,6 @@ class FoamFile(
33
44
  """
34
45
 
35
46
  class SubDict(
36
- FoamDict,
37
47
  MutableMapping[str, Union["FoamFile.Data", "FoamFile.SubDict"]],
38
48
  ):
39
49
  """An OpenFOAM dictionary within a file as a mutable mapping."""
@@ -58,7 +68,9 @@ class FoamFile(
58
68
  del self._file[(*self._keywords, keyword)]
59
69
 
60
70
  def __iter__(self) -> Iterator[str]:
61
- return self._file._iter(self._keywords)
71
+ for k in self._file._iter(self._keywords):
72
+ assert k is not None
73
+ yield k
62
74
 
63
75
  def __contains__(self, keyword: object) -> bool:
64
76
  return (*self._keywords, keyword) in self._file
@@ -77,7 +89,7 @@ class FoamFile(
77
89
  def __repr__(self) -> str:
78
90
  return f"{type(self).__qualname__}('{self._file}', {self._keywords})"
79
91
 
80
- def as_dict(self) -> FoamDict._Dict:
92
+ def as_dict(self) -> FoamFileBase._Dict:
81
93
  """Return a nested dict representation of the dictionary."""
82
94
  ret = self._file.as_dict()
83
95
 
@@ -89,85 +101,111 @@ class FoamFile(
89
101
 
90
102
  return ret
91
103
 
92
- class Header(SubDict):
93
- """The header of an OpenFOAM file."""
104
+ def create(self, *, exist_ok: bool = False, parents: bool = False) -> Self:
105
+ """
106
+ Create the file.
107
+
108
+ Parameters
109
+ ----------
110
+ exist_ok : bool, optional
111
+ If False (the default), raise a FileExistsError if the file already exists.
112
+ If True, do nothing if the file already exists.
113
+ parents : bool, optional
114
+ If True, also create parent directories as needed.
115
+ """
116
+ if self.path.exists():
117
+ if not exist_ok:
118
+ raise FileExistsError(self.path)
119
+ else:
120
+ return self
94
121
 
95
- def __init__(self, _file: "FoamFile") -> None:
96
- super().__init__(_file, ("FoamFile",))
122
+ if parents:
123
+ self.path.parent.mkdir(parents=True, exist_ok=True)
97
124
 
98
- @property
99
- def version(self) -> float:
100
- """Alias of `self["version"]`."""
101
- ret = self["version"]
102
- if not isinstance(ret, float):
103
- raise TypeError("version is not a float")
104
- return ret
125
+ self.path.touch()
126
+ self._write_header()
105
127
 
106
- @version.setter
107
- def version(self, data: float) -> None:
108
- self["version"] = data
128
+ return self
109
129
 
110
- @property
111
- def format(self) -> str:
112
- """Alias of `self["format"]`."""
113
- ret = self["format"]
114
- if not isinstance(ret, str):
115
- raise TypeError("format is not a string")
116
- return ret
130
+ @property
131
+ def version(self) -> float:
132
+ """Alias of `self["FoamFile", "version"]`."""
133
+ ret = self["FoamFile", "version"]
134
+ if not isinstance(ret, float):
135
+ raise TypeError("version is not a float")
136
+ return ret
117
137
 
118
- @format.setter
119
- def format(self, data: str) -> None:
120
- self["format"] = data
138
+ @version.setter
139
+ def version(self, value: float) -> None:
140
+ self["FoamFile", "version"] = value
121
141
 
122
- @property
123
- def class_(self) -> str:
124
- """Alias of `self["class"]`."""
125
- ret = self["class"]
126
- if not isinstance(ret, str):
127
- raise TypeError("class is not a string")
128
- return ret
142
+ @property
143
+ def format(self) -> Literal["ascii", "binary"]:
144
+ """Alias of `self["FoamFile", "format"]`."""
145
+ ret = self["FoamFile", "format"]
146
+ if not isinstance(ret, str):
147
+ raise TypeError("format is not a string")
148
+ if ret not in ("ascii", "binary"):
149
+ raise ValueError("format is not 'ascii' or 'binary'")
150
+ return cast(Literal["ascii", "binary"], ret)
151
+
152
+ @format.setter
153
+ def format(self, value: Literal["ascii", "binary"]) -> None:
154
+ self["FoamFile", "format"] = value
129
155
 
130
- @class_.setter
131
- def class_(self, data: str) -> None:
132
- self["class"] = data
156
+ @property
157
+ def class_(self) -> str:
158
+ """Alias of `self["FoamFile", "class"]`."""
159
+ ret = self["FoamFile", "class"]
160
+ if not isinstance(ret, str):
161
+ raise TypeError("class is not a string")
162
+ return ret
133
163
 
134
- @property
135
- def location(self) -> str:
136
- """Alias of `self["location"]`."""
137
- ret = self["location"]
138
- if not isinstance(ret, str):
139
- raise TypeError("location is not a string")
140
- return ret
164
+ @class_.setter
165
+ def class_(self, value: str) -> None:
166
+ self["FoamFile", "class"] = value
141
167
 
142
- @location.setter
143
- def location(self, data: str) -> None:
144
- self["location"] = data
168
+ @property
169
+ def location(self) -> str:
170
+ """Alias of `self["FoamFile", "location"]`."""
171
+ ret = self["FoamFile", "location"]
172
+ if not isinstance(ret, str):
173
+ raise TypeError("location is not a string")
174
+ return ret
145
175
 
146
- @property
147
- def object(self) -> str:
148
- """Alias of `self["object"]`."""
149
- ret = self["object"]
150
- if not isinstance(ret, str):
151
- raise TypeError("object is not a string")
152
- return ret
176
+ @location.setter
177
+ def location(self, value: str) -> None:
178
+ self["FoamFile", "location"] = value
153
179
 
154
180
  @property
155
- def header(self) -> Header:
156
- """Alias of `self["FoamFile"]`."""
157
- ret = self["FoamFile"]
158
- if not isinstance(ret, FoamFile.Header):
159
- assert not isinstance(ret, FoamFile.SubDict)
160
- raise TypeError("FoamFile is not a dictionary")
181
+ def object_(self) -> str:
182
+ """Alias of `self["FoamFile", "object"]`."""
183
+ ret = self["FoamFile", "object"]
184
+ if not isinstance(ret, str):
185
+ raise TypeError("object is not a string")
161
186
  return ret
162
187
 
163
- @header.setter
164
- def header(self, data: FoamDict._Dict) -> None:
165
- self["FoamFile"] = data
188
+ @object_.setter
189
+ def object_(self, value: str) -> None:
190
+ self["FoamFile", "object"] = value
191
+
192
+ def _write_header(self) -> None:
193
+ assert "FoamFile" not in self
194
+ assert not self
195
+
196
+ self["FoamFile"] = {}
197
+ self.version = 2.0
198
+ self.format = "ascii"
199
+ self.class_ = "dictionary"
200
+ self.location = f'"{self.path.parent.name}"'
201
+ self.object_ = self.path.name
166
202
 
167
203
  def __getitem__(
168
- self, keywords: Union[str, Tuple[str, ...]]
204
+ self, keywords: Optional[Union[str, Tuple[str, ...]]]
169
205
  ) -> Union["FoamFile.Data", "FoamFile.SubDict"]:
170
- if not isinstance(keywords, tuple):
206
+ if not keywords:
207
+ keywords = ()
208
+ elif not isinstance(keywords, tuple):
171
209
  keywords = (keywords,)
172
210
 
173
211
  _, parsed = self._read()
@@ -175,28 +213,21 @@ class FoamFile(
175
213
  value = parsed[keywords]
176
214
 
177
215
  if value is ...:
178
- if keywords == ("FoamFile",):
179
- return FoamFile.Header(self)
180
- else:
181
- return FoamFile.SubDict(self, keywords)
216
+ return FoamFile.SubDict(self, keywords)
182
217
  else:
183
218
  return value
184
219
 
185
220
  def __setitem__(
186
- self, keywords: Union[str, Tuple[str, ...]], data: "FoamFile._SetData"
221
+ self, keywords: Optional[Union[str, Tuple[str, ...]]], data: "FoamFile._SetData"
187
222
  ) -> None:
188
223
  with self:
189
- if not isinstance(keywords, tuple):
224
+ if not keywords:
225
+ keywords = ()
226
+ elif not isinstance(keywords, tuple):
190
227
  keywords = (keywords,)
191
228
 
192
- if not self and keywords[0] != "FoamFile":
193
- self.header = {
194
- "version": 2.0,
195
- "format": "ascii",
196
- "class": "dictionary",
197
- "location": f'"{self.path.parent.name}"',
198
- "object": self.path.name,
199
- } # type: ignore [assignment]
229
+ if not self and "FoamFile" not in self and keywords[0] != "FoamFile":
230
+ self._write_header()
200
231
 
201
232
  kind = Kind.DEFAULT
202
233
  if keywords == ("internalField",) or (
@@ -204,34 +235,13 @@ class FoamFile(
204
235
  and keywords[0] == "boundaryField"
205
236
  and keywords[2] == "value"
206
237
  ):
207
- kind = (
208
- Kind.BINARY_FIELD if self.header.format == "binary" else Kind.FIELD
209
- )
238
+ kind = Kind.BINARY_FIELD if self.format == "binary" else Kind.FIELD
210
239
  elif keywords == ("dimensions",):
211
240
  kind = Kind.DIMENSIONS
212
241
 
213
- contents, parsed = self._read()
214
-
215
- if isinstance(data, Mapping):
216
- if isinstance(data, FoamDict):
217
- data = data.as_dict()
218
-
219
- start, end = parsed.entry_location(keywords, missing_ok=True)
220
-
221
- self._write(
222
- contents[:start]
223
- + b"\n"
224
- + dumpb({keywords[-1]: {}})
225
- + b"\n"
226
- + contents[end:]
227
- )
228
-
229
- for k, v in data.items():
230
- self[(*keywords, k)] = v
231
-
232
- elif (
242
+ if (
233
243
  kind == Kind.FIELD or kind == Kind.BINARY_FIELD
234
- ) and self.header.class_ == "dictionary":
244
+ ) and self.class_ == "dictionary":
235
245
  if not is_sequence(data):
236
246
  class_ = "volScalarField"
237
247
  elif (len(data) == 3 and not is_sequence(data[0])) or len(data[0]) == 3:
@@ -243,22 +253,65 @@ class FoamFile(
243
253
  else:
244
254
  class_ = "volScalarField"
245
255
 
246
- self.header.class_ = class_
256
+ self.class_ = class_
247
257
  self[keywords] = data
248
258
 
249
259
  else:
260
+ contents, parsed = self._read()
261
+
250
262
  start, end = parsed.entry_location(keywords, missing_ok=True)
251
263
 
252
- self._write(
253
- contents[:start]
254
- + b"\n"
255
- + dumpb({keywords[-1]: data}, kind=kind)
256
- + b"\n"
257
- + contents[end:]
258
- )
264
+ before = contents[:start].rstrip() + b"\n"
265
+ if len(keywords) <= 1:
266
+ before += b"\n"
267
+
268
+ after = contents[end:]
269
+ if after.startswith(b"}"):
270
+ after = b" " * (len(keywords) - 2) + after
271
+ if not after or after[:1] != b"\n":
272
+ after = b"\n" + after
273
+ if len(keywords) <= 1 and len(after) > 1 and after[:2] != b"\n\n":
274
+ after = b"\n" + after
275
+
276
+ indentation = b" " * (len(keywords) - 1)
277
+
278
+ if isinstance(data, Mapping):
279
+ if isinstance(data, (FoamFile, FoamFile.SubDict)):
280
+ data = data.as_dict()
281
+
282
+ self._write(
283
+ before
284
+ + indentation
285
+ + dumpb(keywords[-1])
286
+ + b"\n"
287
+ + indentation
288
+ + b"{\n"
289
+ + indentation
290
+ + b"}"
291
+ + after
292
+ )
293
+
294
+ for k, v in data.items():
295
+ self[(*keywords, k)] = v
296
+
297
+ elif keywords:
298
+ self._write(
299
+ before
300
+ + indentation
301
+ + dumpb(keywords[-1])
302
+ + b" "
303
+ + dumpb(data, kind=kind)
304
+ + b";"
305
+ + after
306
+ )
307
+
308
+ else:
309
+ self._write(before + dumpb(data, kind=kind) + after)
259
310
 
260
- def __delitem__(self, keywords: Union[str, Tuple[str, ...]]) -> None:
261
- if not isinstance(keywords, tuple):
311
+ def __delitem__(self, keywords: Optional[Union[str, Tuple[str, ...]]]) -> None:
312
+ if not keywords:
313
+ keywords = ()
314
+ elif not isinstance(keywords, tuple):
262
315
  keywords = (keywords,)
263
316
 
264
317
  contents, parsed = self._read()
@@ -267,21 +320,26 @@ class FoamFile(
267
320
 
268
321
  self._write(contents[:start] + contents[end:])
269
322
 
270
- def _iter(self, keywords: Union[str, Tuple[str, ...]] = ()) -> Iterator[str]:
271
- if not isinstance(keywords, tuple):
272
- keywords = (keywords,)
273
-
323
+ def _iter(self, keywords: Tuple[str, ...] = ()) -> Iterator[Optional[str]]:
274
324
  _, parsed = self._read()
275
325
 
276
- yield from (k[-1] for k in parsed if k[:-1] == keywords)
326
+ yield from (
327
+ k[-1] if k else None
328
+ for k in parsed
329
+ if k != ("FoamFile",) and k[:-1] == keywords
330
+ )
277
331
 
278
- def __iter__(self) -> Iterator[str]:
332
+ def __iter__(self) -> Iterator[Optional[str]]:
279
333
  return self._iter()
280
334
 
281
335
  def __contains__(self, keywords: object) -> bool:
282
- if not isinstance(keywords, tuple):
336
+ if not keywords:
337
+ keywords = ()
338
+ elif not isinstance(keywords, tuple):
283
339
  keywords = (keywords,)
340
+
284
341
  _, parsed = self._read()
342
+
285
343
  return keywords in parsed
286
344
 
287
345
  def __len__(self) -> int:
@@ -298,10 +356,12 @@ class FoamFile(
298
356
  def __fspath__(self) -> str:
299
357
  return str(self.path)
300
358
 
301
- def as_dict(self) -> FoamDict._Dict:
359
+ def as_dict(self) -> FoamFileBase._Dict:
302
360
  """Return a nested dict representation of the file."""
303
361
  _, parsed = self._read()
304
- return parsed.as_dict()
362
+ d = parsed.as_dict()
363
+ del d["FoamFile"]
364
+ return d
305
365
 
306
366
 
307
367
  class FoamFieldFile(FoamFile):
@@ -371,9 +431,11 @@ class FoamFieldFile(FoamFile):
371
431
  del self["value"]
372
432
 
373
433
  def __getitem__(
374
- self, keywords: Union[str, Tuple[str, ...]]
434
+ self, keywords: Optional[Union[str, Tuple[str, ...]]]
375
435
  ) -> Union[FoamFile.Data, FoamFile.SubDict]:
376
- if not isinstance(keywords, tuple):
436
+ if not keywords:
437
+ keywords = ()
438
+ elif not isinstance(keywords, tuple):
377
439
  keywords = (keywords,)
378
440
 
379
441
  ret = super().__getitem__(keywords)
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
 
@@ -33,7 +33,7 @@ from pyparsing import (
33
33
  printables,
34
34
  )
35
35
 
36
- from ._base import FoamDict
36
+ from ._base import FoamFileBase
37
37
 
38
38
 
39
39
  def _list_of(entry: ParserElement) -> ParserElement:
@@ -132,11 +132,11 @@ _SWITCH = (
132
132
  ).set_parse_action(lambda: False)
133
133
  _DIMENSIONS = (
134
134
  Literal("[").suppress() + common.number * 7 + Literal("]").suppress()
135
- ).set_parse_action(lambda tks: FoamDict.DimensionSet(*tks))
135
+ ).set_parse_action(lambda tks: FoamFileBase.DimensionSet(*tks))
136
136
  _TENSOR = _list_of(common.number) | common.number
137
137
  _IDENTIFIER = Word(identchars + "$", printables, exclude_chars="{;}")
138
138
  _DIMENSIONED = (Opt(_IDENTIFIER) + _DIMENSIONS + _TENSOR).set_parse_action(
139
- lambda tks: FoamDict.Dimensioned(*reversed(tks.as_list()))
139
+ lambda tks: FoamFileBase.Dimensioned(*reversed(tks.as_list()))
140
140
  )
141
141
  _FIELD = (
142
142
  (Keyword("uniform").suppress() + _TENSOR)
@@ -164,7 +164,7 @@ _FILE = (
164
164
  Group(
165
165
  Located(
166
166
  _DATA_ENTRY[1, ...].set_parse_action(
167
- lambda tks: ["", tuple(tks) if len(tks) > 1 else tks[0]]
167
+ lambda tks: [None, tuple(tks) if len(tks) > 1 else tks[0]]
168
168
  )
169
169
  )
170
170
  )
@@ -178,12 +178,14 @@ _FILE = (
178
178
  )
179
179
 
180
180
 
181
- class Parsed(Mapping[Tuple[str, ...], Union[FoamDict.Data, EllipsisType]]):
181
+ class Parsed(Mapping[Tuple[str, ...], Union[FoamFileBase.Data, EllipsisType]]):
182
182
  def __init__(self, contents: bytes) -> None:
183
183
  self._parsed: MutableMapping[
184
184
  Tuple[str, ...],
185
- Tuple[int, Union[FoamDict.Data, EllipsisType], int],
185
+ Tuple[int, Union[FoamFileBase.Data, EllipsisType], int],
186
186
  ] = {}
187
+ self._end = len(contents)
188
+
187
189
  for parse_result in _FILE.parse_string(
188
190
  contents.decode("latin-1"), parse_all=True
189
191
  ):
@@ -192,10 +194,12 @@ class Parsed(Mapping[Tuple[str, ...], Union[FoamDict.Data, EllipsisType]]):
192
194
  @staticmethod
193
195
  def _flatten_result(
194
196
  parse_result: ParseResults, *, _keywords: Tuple[str, ...] = ()
195
- ) -> Mapping[Tuple[str, ...], Tuple[int, Union[FoamDict.Data, EllipsisType], int]]:
197
+ ) -> Mapping[
198
+ Tuple[str, ...], Tuple[int, Union[FoamFileBase.Data, EllipsisType], int]
199
+ ]:
196
200
  ret: MutableMapping[
197
201
  Tuple[str, ...],
198
- Tuple[int, Union[FoamDict.Data, EllipsisType], int],
202
+ Tuple[int, Union[FoamFileBase.Data, EllipsisType], int],
199
203
  ] = {}
200
204
  start = parse_result.locn_start
201
205
  assert isinstance(start, int)
@@ -204,18 +208,26 @@ class Parsed(Mapping[Tuple[str, ...], Union[FoamDict.Data, EllipsisType]]):
204
208
  end = parse_result.locn_end
205
209
  assert isinstance(end, int)
206
210
  keyword, *data = item
207
- assert isinstance(keyword, str)
208
- ret[(*_keywords, keyword)] = (start, ..., end)
209
- for d in data:
210
- if isinstance(d, ParseResults):
211
- ret.update(Parsed._flatten_result(d, _keywords=(*_keywords, keyword)))
212
- else:
213
- ret[(*_keywords, keyword)] = (start, d, end)
211
+ if keyword is None:
212
+ assert not _keywords
213
+ assert len(data) == 1
214
+ assert not isinstance(data[0], ParseResults)
215
+ ret[()] = (start, data[0], end)
216
+ else:
217
+ assert isinstance(keyword, str)
218
+ ret[(*_keywords, keyword)] = (start, ..., end)
219
+ for d in data:
220
+ if isinstance(d, ParseResults):
221
+ ret.update(
222
+ Parsed._flatten_result(d, _keywords=(*_keywords, keyword))
223
+ )
224
+ else:
225
+ ret[(*_keywords, keyword)] = (start, d, end)
214
226
  return ret
215
227
 
216
228
  def __getitem__(
217
229
  self, keywords: Union[str, Tuple[str, ...]]
218
- ) -> Union[FoamDict.Data, EllipsisType]:
230
+ ) -> Union[FoamFileBase.Data, EllipsisType]:
219
231
  if isinstance(keywords, str):
220
232
  keywords = (keywords,)
221
233
 
@@ -242,7 +254,7 @@ class Parsed(Mapping[Tuple[str, ...], Union[FoamDict.Data, EllipsisType]]):
242
254
  _, _, end = self._parsed[keywords[:-1]]
243
255
  end -= 1
244
256
  else:
245
- end = -1
257
+ end = self._end
246
258
 
247
259
  start = end
248
260
  else:
@@ -250,8 +262,8 @@ class Parsed(Mapping[Tuple[str, ...], Union[FoamDict.Data, EllipsisType]]):
250
262
 
251
263
  return start, end
252
264
 
253
- def as_dict(self) -> FoamDict._Dict:
254
- ret: FoamDict._Dict = {}
265
+ def as_dict(self) -> FoamFileBase._Dict:
266
+ ret: FoamFileBase._Dict = {}
255
267
  for keywords, (_, data, _) in self._parsed.items():
256
268
  r = ret
257
269
  for k in keywords[:-1]:
@@ -9,7 +9,7 @@ else:
9
9
  from typing import Mapping
10
10
 
11
11
  from .._util import is_sequence
12
- from ._base import FoamDict
12
+ from ._base import FoamFileBase
13
13
 
14
14
  try:
15
15
  import numpy as np
@@ -28,7 +28,7 @@ class Kind(Enum):
28
28
 
29
29
 
30
30
  def dumpb(
31
- data: FoamDict._SetData,
31
+ data: FoamFileBase._SetData,
32
32
  *,
33
33
  kind: Kind = Kind.DEFAULT,
34
34
  ) -> bytes:
@@ -39,18 +39,16 @@ def dumpb(
39
39
  entries = []
40
40
  for k, v in data.items():
41
41
  b = dumpb(v, kind=kind)
42
- if not k:
43
- entries.append(b)
44
- elif isinstance(v, Mapping):
45
- entries.append(dumpb(k) + b"\n" + b"{\n" + b + b"\n}")
42
+ if isinstance(v, Mapping):
43
+ entries.append(dumpb(k) + b" {" + b + b"}")
46
44
  elif not b:
47
45
  entries.append(dumpb(k) + b";")
48
46
  else:
49
47
  entries.append(dumpb(k) + b" " + b + b";")
50
48
 
51
- return b"\n".join(entries)
49
+ return b" ".join(entries)
52
50
 
53
- elif isinstance(data, FoamDict.DimensionSet) or (
51
+ elif isinstance(data, FoamFileBase.DimensionSet) or (
54
52
  kind == Kind.DIMENSIONS and is_sequence(data) and len(data) == 7
55
53
  ):
56
54
  return b"[" + b" ".join(dumpb(v) for v in data) + b"]"
@@ -93,7 +91,7 @@ def dumpb(
93
91
  elif kind != Kind.SINGLE_ENTRY and isinstance(data, tuple):
94
92
  return b" ".join(dumpb(v) for v in data)
95
93
 
96
- elif isinstance(data, FoamDict.Dimensioned):
94
+ elif isinstance(data, FoamFileBase.Dimensioned):
97
95
  if data.name is not None:
98
96
  return (
99
97
  dumpb(data.name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foamlib
3
- Version: 0.3.22
3
+ Version: 0.4.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
@@ -45,7 +45,7 @@ Requires-Dist: numpy <3,>=1 ; extra == 'numpy'
45
45
  Provides-Extra: test
46
46
  Requires-Dist: foamlib[numpy] ; extra == 'test'
47
47
  Requires-Dist: pytest <9,>=7 ; extra == 'test'
48
- Requires-Dist: pytest-asyncio <0.24,>=0.21 ; extra == 'test'
48
+ Requires-Dist: pytest-asyncio <0.25,>=0.21 ; extra == 'test'
49
49
  Requires-Dist: pytest-cov ; extra == 'test'
50
50
  Provides-Extra: typing
51
51
  Requires-Dist: foamlib[test] ; extra == 'typing'
@@ -0,0 +1,19 @@
1
+ foamlib/__init__.py,sha256=ZL6X6uzZgGj8D5Thi9ZuZyr1Mpo6f78yT_Rf4sBU12I,446
2
+ foamlib/_util.py,sha256=UMzXmTFgvbp46w6k3oEZJoYC98pFgEK6LN5uLOwrlCg,397
3
+ foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ foamlib/_cases/__init__.py,sha256=xnQpR64EvFtCh07qnSz7kjQ1IhJXjRDwaZWwQGZhGv4,280
5
+ foamlib/_cases/_async.py,sha256=ehmAedDepDXaRdf8f0IznxDMYAQxoUWSIxbhxG_17Rw,6035
6
+ foamlib/_cases/_base.py,sha256=JrYJplmXV84qwgerdVSCHz2rCI-KWB4IvTBPMkYpecw,12657
7
+ foamlib/_cases/_sync.py,sha256=CloQgd-93jxfSXIt7J5NxcAu3N_iF3eXMKO-NfNOgi4,4522
8
+ foamlib/_cases/_util.py,sha256=v6sHxHCEgagsVuup0S1xJW-x9py5xj3bUye8PiFfb3o,925
9
+ foamlib/_files/__init__.py,sha256=-UqB9YTH6mrJfXCX00kPTAAY20XG64u1MGPw_1ewLVs,148
10
+ foamlib/_files/_base.py,sha256=sy1RP08pdv079KD_UBy7xUYEnXyNK35AiKVcCalYmPU,1873
11
+ foamlib/_files/_files.py,sha256=2bvl2YZ0bqHpbQV35vzYO7wWNOo_2WDF9LbyLagIvBE,16193
12
+ foamlib/_files/_io.py,sha256=f_tYI7AqaFsQ8mtK__fEoIUqpYb3YmrI8X5D8updmNM,2084
13
+ foamlib/_files/_parsing.py,sha256=bOJkqNUMUiXZJQXXEnIAb0bcdj_AgDmDhrr19oyY_Sw,8078
14
+ foamlib/_files/_serialization.py,sha256=3yb9fgjCpDoRfZoLsbZaIFrkZ3vGBzleFRw6IbaZuuY,3408
15
+ foamlib-0.4.0.dist-info/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
16
+ foamlib-0.4.0.dist-info/METADATA,sha256=3DObrf-prhkIgLQ2crezzAVkKW4ZIuNrXEsbqT3ilUc,5457
17
+ foamlib-0.4.0.dist-info/WHEEL,sha256=uCRv0ZEik_232NlR4YDw4Pv3Ajt5bKvMH13NUU7hFuI,91
18
+ foamlib-0.4.0.dist-info/top_level.txt,sha256=ZdVYtetXGwPwyfL-WhlhbTFQGAwKX5P_gXxtH9JYFPI,8
19
+ foamlib-0.4.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (73.0.1)
2
+ Generator: setuptools (74.1.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,19 +0,0 @@
1
- foamlib/__init__.py,sha256=ubVl04mBxekohoPKeF_Ap77kAq8Sg69BeDC5qKNy66k,439
2
- foamlib/_util.py,sha256=UMzXmTFgvbp46w6k3oEZJoYC98pFgEK6LN5uLOwrlCg,397
3
- foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- foamlib/_cases/__init__.py,sha256=xnQpR64EvFtCh07qnSz7kjQ1IhJXjRDwaZWwQGZhGv4,280
5
- foamlib/_cases/_async.py,sha256=ehmAedDepDXaRdf8f0IznxDMYAQxoUWSIxbhxG_17Rw,6035
6
- foamlib/_cases/_base.py,sha256=jfKevsV5CtxsyORvp-sYrdnW4pUxs6Or0I6nGXZVjII,12336
7
- foamlib/_cases/_sync.py,sha256=CloQgd-93jxfSXIt7J5NxcAu3N_iF3eXMKO-NfNOgi4,4522
8
- foamlib/_cases/_util.py,sha256=v6sHxHCEgagsVuup0S1xJW-x9py5xj3bUye8PiFfb3o,925
9
- foamlib/_files/__init__.py,sha256=vDkPj8u8bX_I_m2YfeKvXBgwg8D1ufyFCfHGHKN3JPQ,140
10
- foamlib/_files/_base.py,sha256=YA5a-i5HZuA3JslCD6r-DwZzpSA8r42dqSXef286Ako,2050
11
- foamlib/_files/_files.py,sha256=rH__Zk7ScS4Q2jyuOcbWnrg0NX_sMSb3D4CiK13Z03A,14277
12
- foamlib/_files/_io.py,sha256=hlcqQqU-1cdIbDc3YqxnMfxALo4SFAEcRIoZM2vMtnE,2083
13
- foamlib/_files/_parsing.py,sha256=-IgvCOFMAERNOrpw2NaHtVXubBg4Ey6xR55bzEZb2B4,7696
14
- foamlib/_files/_serialization.py,sha256=LCeaLWtNvkcs0dfowL7nViiByxw7U_fvgueVjFliipU,3462
15
- foamlib-0.3.22.dist-info/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
16
- foamlib-0.3.22.dist-info/METADATA,sha256=8LhhVcQYHM8WC_dva0cmrFlwWGl3Nmw0NkrpzSgTkGk,5458
17
- foamlib-0.3.22.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
18
- foamlib-0.3.22.dist-info/top_level.txt,sha256=ZdVYtetXGwPwyfL-WhlhbTFQGAwKX5P_gXxtH9JYFPI,8
19
- foamlib-0.3.22.dist-info/RECORD,,