foamlib 0.2.9__py3-none-any.whl → 0.3.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.
@@ -1,422 +0,0 @@
1
- import sys
2
- from copy import deepcopy
3
- from pathlib import Path
4
- from types import TracebackType
5
- from typing import (
6
- Any,
7
- Optional,
8
- Tuple,
9
- Type,
10
- Union,
11
- cast,
12
- )
13
-
14
- if sys.version_info >= (3, 9):
15
- from collections.abc import Iterator, Mapping, MutableMapping, Sequence
16
- else:
17
- from typing import Iterator, Mapping, MutableMapping, Sequence
18
-
19
- if sys.version_info >= (3, 11):
20
- from typing import Self
21
- else:
22
- from typing_extensions import Self
23
-
24
- try:
25
- import numpy as np
26
- from numpy.typing import NDArray
27
- except ModuleNotFoundError:
28
- pass
29
-
30
- from ._base import FoamDictionaryBase
31
- from ._parsing import Parsed, as_dict, get_entry_locn, get_value, parse
32
- from ._serialization import serialize_entry
33
-
34
-
35
- class _FoamFileBase:
36
- def __init__(self, path: Union[str, Path]) -> None:
37
- self.path = Path(path).absolute()
38
- if self.path.is_dir():
39
- raise IsADirectoryError(self.path)
40
- elif not self.path.is_file():
41
- raise FileNotFoundError(self.path)
42
-
43
- self.__contents: Optional[str] = None
44
- self.__parsed: Optional[Parsed] = None
45
- self.__defer_io = 0
46
- self.__dirty = False
47
-
48
- def __enter__(self) -> Self:
49
- if self.__defer_io == 0:
50
- self._read()
51
- self.__defer_io += 1
52
- return self
53
-
54
- def __exit__(
55
- self,
56
- exc_type: Optional[Type[BaseException]],
57
- exc_val: Optional[BaseException],
58
- exc_tb: Optional[TracebackType],
59
- ) -> None:
60
- self.__defer_io -= 1
61
- if self.__defer_io == 0 and self.__dirty:
62
- assert self.__contents is not None
63
- self._write(self.__contents)
64
- assert not self.__dirty
65
-
66
- def _read(self) -> Tuple[str, Parsed]:
67
- if not self.__defer_io:
68
- contents = self.path.read_text()
69
- if contents != self.__contents:
70
- self.__contents = contents
71
- self.__parsed = None
72
-
73
- assert self.__contents is not None
74
-
75
- if self.__parsed is None:
76
- parsed = parse(self.__contents)
77
- self.__parsed = parsed
78
- else:
79
- parsed = deepcopy(self.__parsed)
80
-
81
- return self.__contents, parsed
82
-
83
- def _write(self, contents: str) -> None:
84
- self.__contents = contents
85
- self.__parsed = None
86
- if not self.__defer_io:
87
- self.path.write_text(contents)
88
- self.__dirty = False
89
- else:
90
- self.__dirty = True
91
-
92
-
93
- class FoamFile(
94
- _FoamFileBase,
95
- FoamDictionaryBase,
96
- MutableMapping[
97
- Union[str, Tuple[str, ...]], Union["FoamFile.Value", "FoamFile.Dictionary"]
98
- ],
99
- ):
100
- """
101
- An OpenFOAM dictionary file.
102
-
103
- Use as a mutable mapping (i.e., like a dict) to access and modify entries.
104
-
105
- Use as a context manager to make multiple changes to the file while saving all changes only once at the end.
106
- """
107
-
108
- class Dictionary(
109
- FoamDictionaryBase,
110
- MutableMapping[str, Union["FoamFile.Value", "FoamFile.Dictionary"]],
111
- ):
112
- """An OpenFOAM dictionary within a file as a mutable mapping."""
113
-
114
- def __init__(self, _file: "FoamFile", _keywords: Sequence[str]) -> None:
115
- self._file = _file
116
- self._keywords = _keywords
117
-
118
- def __getitem__(
119
- self, keyword: str
120
- ) -> Union["FoamFile.Value", "FoamFile.Dictionary"]:
121
- return self._file[(*self._keywords, keyword)]
122
-
123
- def _setitem(
124
- self,
125
- keyword: str,
126
- value: Any,
127
- *,
128
- assume_field: bool = False,
129
- assume_dimensions: bool = False,
130
- ) -> None:
131
- self._file._setitem(
132
- (*self._keywords, keyword),
133
- value,
134
- assume_field=assume_field,
135
- assume_dimensions=assume_dimensions,
136
- )
137
-
138
- def __setitem__(self, keyword: str, value: "FoamFile._SetValue") -> None:
139
- self._setitem(keyword, value)
140
-
141
- def __delitem__(self, keyword: str) -> None:
142
- del self._file[(*self._keywords, keyword)]
143
-
144
- def __iter__(self) -> Iterator[str]:
145
- return self._file._iter(tuple(self._keywords))
146
-
147
- def __contains__(self, keyword: object) -> bool:
148
- return (*self._keywords, keyword) in self._file
149
-
150
- def __len__(self) -> int:
151
- return len(list(iter(self)))
152
-
153
- def update(self, *args: Any, **kwargs: Any) -> None:
154
- with self._file:
155
- super().update(*args, **kwargs)
156
-
157
- def clear(self) -> None:
158
- with self._file:
159
- super().clear()
160
-
161
- def __repr__(self) -> str:
162
- return f"{type(self).__qualname__}({self._file}, {self._keywords})"
163
-
164
- def as_dict(self) -> FoamDictionaryBase._Dict:
165
- """Return a nested dict representation of the dictionary."""
166
- ret = self._file.as_dict()
167
-
168
- for k in self._keywords:
169
- assert isinstance(ret, dict)
170
- v = ret[k]
171
- assert isinstance(v, dict)
172
- ret = v
173
-
174
- return ret
175
-
176
- def __getitem__(
177
- self, keywords: Union[str, Tuple[str, ...]]
178
- ) -> Union["FoamFile.Value", "FoamFile.Dictionary"]:
179
- if not isinstance(keywords, tuple):
180
- keywords = (keywords,)
181
-
182
- _, parsed = self._read()
183
-
184
- value = get_value(parsed, keywords)
185
-
186
- if value is None:
187
- return FoamFile.Dictionary(self, keywords)
188
- else:
189
- return value
190
-
191
- def _setitem(
192
- self,
193
- keywords: Union[str, Tuple[str, ...]],
194
- value: "FoamFile._SetValue",
195
- *,
196
- assume_field: bool = False,
197
- assume_dimensions: bool = False,
198
- ) -> None:
199
- if not isinstance(keywords, tuple):
200
- keywords = (keywords,)
201
-
202
- contents, parsed = self._read()
203
-
204
- if isinstance(value, Mapping):
205
- with self:
206
- if isinstance(value, FoamDictionaryBase):
207
- value = value.as_dict()
208
-
209
- start, end = get_entry_locn(parsed, keywords, missing_ok=True)
210
-
211
- self._write(
212
- f"{contents[:start]}\n{serialize_entry(keywords[-1], {})}\n{contents[end:]}"
213
- )
214
-
215
- for k, v in value.items():
216
- self[(*keywords, k)] = v
217
- else:
218
- start, end = get_entry_locn(parsed, keywords, missing_ok=True)
219
-
220
- self._write(
221
- f"{contents[:start]}\n{serialize_entry(keywords[-1], value, assume_field=assume_field, assume_dimensions=assume_dimensions)}\n{contents[end:]}"
222
- )
223
-
224
- def __setitem__(
225
- self,
226
- keywords: Union[str, Tuple[str, ...]],
227
- value: "FoamFile._SetValue",
228
- ) -> None:
229
- self._setitem(keywords, value)
230
-
231
- def __delitem__(self, keywords: Union[str, Tuple[str, ...]]) -> None:
232
- if not isinstance(keywords, tuple):
233
- keywords = (keywords,)
234
-
235
- contents, parsed = self._read()
236
-
237
- start, end = get_entry_locn(parsed, keywords)
238
-
239
- self._write(contents[:start] + contents[end:])
240
-
241
- def _iter(self, keywords: Union[str, Tuple[str, ...]] = ()) -> Iterator[str]:
242
- if not isinstance(keywords, tuple):
243
- keywords = (keywords,)
244
-
245
- contents = self.path.read_text()
246
- parsed = parse(contents)
247
-
248
- yield from (k[-1] for k in parsed if k[:-1] == keywords)
249
-
250
- def __iter__(self) -> Iterator[str]:
251
- return self._iter()
252
-
253
- def __contains__(self, keywords: object) -> bool:
254
- if not isinstance(keywords, tuple):
255
- keywords = (keywords,)
256
- _, parsed = self._read()
257
- return keywords in parsed
258
-
259
- def __len__(self) -> int:
260
- return len(list(iter(self)))
261
-
262
- def update(self, *args: Any, **kwargs: Any) -> None:
263
- with self:
264
- super().update(*args, **kwargs)
265
-
266
- def clear(self) -> None:
267
- with self:
268
- super().clear()
269
-
270
- def __fspath__(self) -> str:
271
- return str(self.path)
272
-
273
- def __repr__(self) -> str:
274
- return f"{type(self).__name__}({self.path})"
275
-
276
- def as_dict(self) -> FoamDictionaryBase._Dict:
277
- """Return a nested dict representation of the file."""
278
- _, parsed = self._read()
279
- return as_dict(parsed)
280
-
281
-
282
- class FoamFieldFile(FoamFile):
283
- """An OpenFOAM dictionary file representing a field as a mutable mapping."""
284
-
285
- class BoundariesDictionary(FoamFile.Dictionary):
286
- def __getitem__(self, keyword: str) -> "FoamFieldFile.BoundaryDictionary":
287
- value = super().__getitem__(keyword)
288
- if not isinstance(value, FoamFieldFile.BoundaryDictionary):
289
- assert not isinstance(value, FoamFile.Dictionary)
290
- raise TypeError(f"boundary {keyword} is not a dictionary")
291
- return value
292
-
293
- class BoundaryDictionary(FoamFile.Dictionary):
294
- """An OpenFOAM dictionary representing a boundary condition as a mutable mapping."""
295
-
296
- def __setitem__(
297
- self,
298
- key: str,
299
- value: FoamFile._SetValue,
300
- ) -> None:
301
- if key == "value":
302
- self._setitem(key, value, assume_field=True)
303
- else:
304
- self._setitem(key, value)
305
-
306
- @property
307
- def type(self) -> str:
308
- """Alias of `self["type"]`."""
309
- ret = self["type"]
310
- if not isinstance(ret, str):
311
- raise TypeError("type is not a string")
312
- return ret
313
-
314
- @type.setter
315
- def type(self, value: str) -> None:
316
- self["type"] = value
317
-
318
- @property
319
- def value(
320
- self,
321
- ) -> Union[
322
- int,
323
- float,
324
- Sequence[Union[int, float, Sequence[Union[int, float]]]],
325
- "NDArray[np.generic]",
326
- ]:
327
- """Alias of `self["value"]`."""
328
- ret = self["value"]
329
- if not isinstance(ret, (int, float, Sequence)):
330
- raise TypeError("value is not a field")
331
- return cast(Union[int, float, Sequence[Union[int, float]]], ret)
332
-
333
- @value.setter
334
- def value(
335
- self,
336
- value: Union[
337
- int,
338
- float,
339
- Sequence[Union[int, float, Sequence[Union[int, float]]]],
340
- "NDArray[np.generic]",
341
- ],
342
- ) -> None:
343
- self["value"] = value
344
-
345
- @value.deleter
346
- def value(self) -> None:
347
- del self["value"]
348
-
349
- def __getitem__(
350
- self, keywords: Union[str, Tuple[str, ...]]
351
- ) -> Union[FoamFile.Value, FoamFile.Dictionary]:
352
- if not isinstance(keywords, tuple):
353
- keywords = (keywords,)
354
-
355
- ret = super().__getitem__(keywords)
356
- if keywords[0] == "boundaryField" and isinstance(ret, FoamFile.Dictionary):
357
- if len(keywords) == 1:
358
- ret = FoamFieldFile.BoundariesDictionary(self, keywords)
359
- elif len(keywords) == 2:
360
- ret = FoamFieldFile.BoundaryDictionary(self, keywords)
361
- return ret
362
-
363
- def __setitem__(self, keywords: Union[str, Tuple[str, ...]], value: Any) -> None:
364
- if not isinstance(keywords, tuple):
365
- keywords = (keywords,)
366
-
367
- if keywords == ("internalField",):
368
- self._setitem(keywords, value, assume_field=True)
369
- elif keywords == ("dimensions",):
370
- self._setitem(keywords, value, assume_dimensions=True)
371
- else:
372
- self._setitem(keywords, value)
373
-
374
- @property
375
- def dimensions(self) -> FoamFile.DimensionSet:
376
- """Alias of `self["dimensions"]`."""
377
- ret = self["dimensions"]
378
- if not isinstance(ret, FoamFile.DimensionSet):
379
- raise TypeError("dimensions is not a DimensionSet")
380
- return ret
381
-
382
- @dimensions.setter
383
- def dimensions(
384
- self, value: Union[FoamFile.DimensionSet, Sequence[Union[int, float]]]
385
- ) -> None:
386
- self["dimensions"] = value
387
-
388
- @property
389
- def internal_field(
390
- self,
391
- ) -> Union[
392
- int,
393
- float,
394
- Sequence[Union[int, float, Sequence[Union[int, float]]]],
395
- "NDArray[np.generic]",
396
- ]:
397
- """Alias of `self["internalField"]`."""
398
- ret = self["internalField"]
399
- if not isinstance(ret, (int, float, Sequence)):
400
- raise TypeError("internalField is not a field")
401
- return cast(Union[int, float, Sequence[Union[int, float]]], ret)
402
-
403
- @internal_field.setter
404
- def internal_field(
405
- self,
406
- value: Union[
407
- int,
408
- float,
409
- Sequence[Union[int, float, Sequence[Union[int, float]]]],
410
- "NDArray[np.generic]",
411
- ],
412
- ) -> None:
413
- self["internalField"] = value
414
-
415
- @property
416
- def boundary_field(self) -> "FoamFieldFile.BoundariesDictionary":
417
- """Alias of `self["boundaryField"]`."""
418
- ret = self["boundaryField"]
419
- if not isinstance(ret, FoamFieldFile.BoundariesDictionary):
420
- assert not isinstance(ret, FoamFile.Dictionary)
421
- raise TypeError("boundaryField is not a dictionary")
422
- return ret
@@ -1,198 +0,0 @@
1
- import sys
2
- from typing import Optional, Tuple, Union
3
-
4
- if sys.version_info >= (3, 9):
5
- from collections.abc import Mapping, MutableMapping, Sequence
6
- else:
7
- from typing import Mapping, MutableMapping, Sequence
8
-
9
- if sys.version_info >= (3, 10):
10
- from types import EllipsisType
11
- else:
12
- from typing import Any as EllipsisType
13
-
14
- from pyparsing import (
15
- Dict,
16
- Forward,
17
- Group,
18
- Keyword,
19
- LineEnd,
20
- Literal,
21
- Located,
22
- Opt,
23
- ParserElement,
24
- ParseResults,
25
- QuotedString,
26
- Word,
27
- c_style_comment,
28
- common,
29
- cpp_style_comment,
30
- identbodychars,
31
- printables,
32
- )
33
-
34
- from ._base import FoamDictionaryBase
35
-
36
- _SWITCH = (
37
- Keyword("yes") | Keyword("true") | Keyword("on") | Keyword("y") | Keyword("t")
38
- ).set_parse_action(lambda: True) | (
39
- Keyword("no") | Keyword("false") | Keyword("off") | Keyword("n") | Keyword("f")
40
- ).set_parse_action(lambda: False)
41
- _DIMENSIONS = (
42
- Literal("[").suppress() + common.number * 7 + Literal("]").suppress()
43
- ).set_parse_action(lambda tks: FoamDictionaryBase.DimensionSet(*tks))
44
-
45
-
46
- def _list_of(elem: ParserElement) -> ParserElement:
47
- return Opt(
48
- Literal("List") + Literal("<") + common.identifier + Literal(">")
49
- ).suppress() + (
50
- (
51
- Opt(common.integer).suppress()
52
- + (
53
- Literal("(").suppress()
54
- + Group((elem)[...], aslist=True)
55
- + Literal(")").suppress()
56
- )
57
- )
58
- | (
59
- common.integer + Literal("{").suppress() + elem + Literal("}").suppress()
60
- ).set_parse_action(lambda tks: [[tks[1]] * tks[0]])
61
- )
62
-
63
-
64
- def _dictionary_of(
65
- keyword: ParserElement,
66
- value: ParserElement,
67
- *,
68
- len: Union[int, EllipsisType] = ...,
69
- located: bool = False,
70
- ) -> ParserElement:
71
- subdict = Forward()
72
-
73
- entry = keyword + (
74
- (Literal("{").suppress() + subdict + Literal("}").suppress())
75
- | (value + Literal(";").suppress())
76
- )
77
-
78
- if located:
79
- entry = Located(entry)
80
-
81
- subdict <<= Dict(Group(entry)[...], asdict=not located)
82
-
83
- return Dict(Group(entry)[len], asdict=not located)
84
-
85
-
86
- _TENSOR = _list_of(common.number) | common.number
87
- _IDENTIFIER = Word(identbodychars + "$", printables.replace(";", ""))
88
- _DIMENSIONED = (Opt(_IDENTIFIER) + _DIMENSIONS + _TENSOR).set_parse_action(
89
- lambda tks: FoamDictionaryBase.Dimensioned(*reversed(tks.as_list()))
90
- )
91
- _FIELD = (Keyword("uniform").suppress() + _TENSOR) | (
92
- Keyword("nonuniform").suppress() + _list_of(_TENSOR)
93
- )
94
- _TOKEN = QuotedString('"', unquote_results=False) | _IDENTIFIER
95
- _ITEM = Forward()
96
- _ENTRY = _dictionary_of(_IDENTIFIER, _ITEM, len=1)
97
- _LIST = _list_of(_ENTRY | _ITEM)
98
- _ITEM <<= _FIELD | _LIST | _DIMENSIONED | _DIMENSIONS | common.number | _SWITCH | _TOKEN
99
-
100
- _TOKENS = (
101
- QuotedString('"', unquote_results=False) | Word(printables.replace(";", ""))
102
- )[2, ...].set_parse_action(lambda tks: " ".join(tks))
103
-
104
- _VALUE = _ITEM ^ _TOKENS
105
-
106
- _FILE = (
107
- _dictionary_of(_TOKEN, Opt(_VALUE, default=""), located=True)
108
- .ignore(c_style_comment)
109
- .ignore(cpp_style_comment)
110
- .ignore(Literal("#include") + ... + LineEnd()) # type: ignore [no-untyped-call]
111
- )
112
-
113
- Parsed = Mapping[Sequence[str], Tuple[int, Optional[FoamDictionaryBase.Value], int]]
114
-
115
-
116
- def _flatten_result(
117
- parse_result: ParseResults, *, _keywords: Sequence[str] = ()
118
- ) -> Parsed:
119
- ret: MutableMapping[
120
- Sequence[str], Tuple[int, Optional[FoamDictionaryBase.Value], int]
121
- ] = {}
122
- start = parse_result.locn_start
123
- assert isinstance(start, int)
124
- item = parse_result.value
125
- assert isinstance(item, Sequence)
126
- end = parse_result.locn_end
127
- assert isinstance(end, int)
128
- key, *values = item
129
- assert isinstance(key, str)
130
- ret[(*_keywords, key)] = (start, None, end)
131
- for value in values:
132
- if isinstance(value, ParseResults):
133
- ret.update(_flatten_result(value, _keywords=(*_keywords, key)))
134
- else:
135
- ret[(*_keywords, key)] = (start, value, end)
136
- return ret
137
-
138
-
139
- def parse(
140
- contents: str,
141
- ) -> Parsed:
142
- parse_results = _FILE.parse_string(contents, parse_all=True)
143
- ret: MutableMapping[
144
- Sequence[str], Tuple[int, Optional[FoamDictionaryBase.Value], int]
145
- ] = {}
146
- for parse_result in parse_results:
147
- ret.update(_flatten_result(parse_result))
148
- return ret
149
-
150
-
151
- def get_value(
152
- parsed: Parsed,
153
- keywords: Tuple[str, ...],
154
- ) -> Optional[FoamDictionaryBase.Value]:
155
- """Value of an entry."""
156
- _, value, _ = parsed[keywords]
157
- return value
158
-
159
-
160
- def get_entry_locn(
161
- parsed: Parsed,
162
- keywords: Tuple[str, ...],
163
- *,
164
- missing_ok: bool = False,
165
- ) -> Tuple[int, int]:
166
- """Location of an entry or where it should be inserted."""
167
- try:
168
- start, _, end = parsed[keywords]
169
- except KeyError:
170
- if missing_ok:
171
- if len(keywords) > 1:
172
- _, _, end = parsed[keywords[:-1]]
173
- end -= 1
174
- else:
175
- end = -1
176
-
177
- start = end
178
- else:
179
- raise
180
-
181
- return start, end
182
-
183
-
184
- def as_dict(parsed: Parsed) -> FoamDictionaryBase._Dict:
185
- """Return a nested dict representation of the file."""
186
- ret: FoamDictionaryBase._Dict = {}
187
- for keywords, (_, value, _) in parsed.items():
188
- r = ret
189
- for k in keywords[:-1]:
190
- assert isinstance(r, dict)
191
- v = r[k]
192
- assert isinstance(v, dict)
193
- r = v
194
-
195
- assert isinstance(r, dict)
196
- r[keywords[-1]] = {} if value is None else value
197
-
198
- return ret