foamlib 0.2.2__py3-none-any.whl → 0.2.4__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,7 +1,7 @@
1
- __version__ = "0.2.2"
1
+ __version__ = "0.2.4"
2
2
 
3
3
  from ._cases import FoamCase, AsyncFoamCase, FoamCaseBase
4
- from ._dictionaries import FoamFile, FoamFieldFile
4
+ from ._dictionaries import FoamFile, FoamFieldFile, FoamDictionaryBase
5
5
 
6
6
  __all__ = [
7
7
  "FoamCase",
@@ -9,4 +9,5 @@ __all__ = [
9
9
  "FoamCaseBase",
10
10
  "FoamFile",
11
11
  "FoamFieldFile",
12
+ "FoamDictionaryBase",
12
13
  ]
@@ -0,0 +1,8 @@
1
+ from ._files import FoamFile, FoamFieldFile
2
+ from ._base import FoamDictionaryBase
3
+
4
+ __all__ = [
5
+ "FoamFile",
6
+ "FoamFieldFile",
7
+ "FoamDictionaryBase",
8
+ ]
@@ -0,0 +1,43 @@
1
+ from abc import abstractmethod
2
+ from dataclasses import dataclass
3
+ from typing import Dict, NamedTuple, Optional, Sequence, Union
4
+
5
+
6
+ class FoamDictionaryBase:
7
+ class DimensionSet(NamedTuple):
8
+ mass: Union[int, float] = 0
9
+ length: Union[int, float] = 0
10
+ time: Union[int, float] = 0
11
+ temperature: Union[int, float] = 0
12
+ moles: Union[int, float] = 0
13
+ current: Union[int, float] = 0
14
+ luminous_intensity: Union[int, float] = 0
15
+
16
+ def __repr__(self) -> str:
17
+ return f"{type(self).__qualname__}({', '.join(f'{n}={v}' for n, v in zip(self._fields, self) if v != 0)})"
18
+
19
+ @dataclass
20
+ class Dimensioned:
21
+ value: Union[int, float, Sequence[Union[int, float]]] = 0
22
+ dimensions: Union[
23
+ "FoamDictionaryBase.DimensionSet", Sequence[Union[int, float]]
24
+ ] = ()
25
+ name: Optional[str] = None
26
+
27
+ def __post_init__(self) -> None:
28
+ if not isinstance(self.dimensions, FoamDictionaryBase.DimensionSet):
29
+ self.dimensions = FoamDictionaryBase.DimensionSet(*self.dimensions)
30
+
31
+ Value = Union[str, int, float, bool, Dimensioned, DimensionSet, Sequence["Value"]]
32
+ """
33
+ A value that can be stored in an OpenFOAM dictionary.
34
+ """
35
+
36
+ _Dict = Dict[str, Union["FoamDictionaryBase.Value", "_Dict"]]
37
+
38
+ @abstractmethod
39
+ def as_dict(self) -> _Dict:
40
+ """
41
+ Return a nested dict representation of the dictionary.
42
+ """
43
+ raise NotImplementedError
@@ -0,0 +1,410 @@
1
+ from pathlib import Path
2
+ from typing import (
3
+ Any,
4
+ Iterator,
5
+ Mapping,
6
+ MutableMapping,
7
+ Optional,
8
+ Sequence,
9
+ Tuple,
10
+ Union,
11
+ cast,
12
+ )
13
+
14
+ from typing_extensions import Self
15
+
16
+ from ._base import FoamDictionaryBase
17
+ from ._parsing import Parsed, as_dict, get_entry_locn, get_value, parse
18
+ from ._serialization import serialize_entry
19
+
20
+ try:
21
+ import numpy as np
22
+ from numpy.typing import NDArray
23
+ except ModuleNotFoundError:
24
+ pass
25
+
26
+
27
+ class _FoamFileBase:
28
+ def __init__(self, path: Union[str, Path]) -> None:
29
+ self.path = Path(path).absolute()
30
+ if self.path.is_dir():
31
+ raise IsADirectoryError(self.path)
32
+ elif not self.path.is_file():
33
+ raise FileNotFoundError(self.path)
34
+
35
+ self.__contents: Optional[str] = None
36
+ self.__parsed: Optional[Parsed] = None
37
+ self.__defer_io = 0
38
+ self.__dirty = False
39
+
40
+ def __enter__(self) -> Self:
41
+ if self.__defer_io == 0:
42
+ self._read()
43
+ self.__defer_io += 1
44
+ return self
45
+
46
+ def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
47
+ self.__defer_io -= 1
48
+ if self.__defer_io == 0 and self.__dirty:
49
+ assert self.__contents is not None
50
+ self._write(self.__contents)
51
+ assert not self.__dirty
52
+
53
+ def _read(self) -> Tuple[str, Parsed]:
54
+ if not self.__defer_io:
55
+ contents = self.path.read_text()
56
+ if contents != self.__contents:
57
+ self.__contents = contents
58
+ self.__parsed = None
59
+
60
+ assert self.__contents is not None
61
+
62
+ if self.__parsed is None:
63
+ self.__parsed = parse(self.__contents)
64
+
65
+ return self.__contents, self.__parsed
66
+
67
+ def _write(self, contents: str) -> None:
68
+ self.__contents = contents
69
+ self.__parsed = None
70
+ if not self.__defer_io:
71
+ self.path.write_text(contents)
72
+ self.__dirty = False
73
+ else:
74
+ self.__dirty = True
75
+
76
+
77
+ class FoamFile(
78
+ _FoamFileBase,
79
+ FoamDictionaryBase,
80
+ MutableMapping[
81
+ Union[str, Tuple[str, ...]], Union["FoamFile.Value", "FoamFile.Dictionary"]
82
+ ],
83
+ ):
84
+ """
85
+ An OpenFOAM dictionary file.
86
+
87
+ Use as a mutable mapping (i.e., like a dict) to access and modify entries.
88
+
89
+ Use as a context manager to make multiple changes to the file while saving all changes only once at the end.
90
+ """
91
+
92
+ class Dictionary(
93
+ FoamDictionaryBase,
94
+ MutableMapping[str, Union["FoamFile.Value", "FoamFile.Dictionary"]],
95
+ ):
96
+ """
97
+ An OpenFOAM dictionary within a file as a mutable mapping.
98
+ """
99
+
100
+ def __init__(self, _file: "FoamFile", _keywords: Sequence[str]) -> None:
101
+ self._file = _file
102
+ self._keywords = _keywords
103
+
104
+ def __getitem__(
105
+ self, keyword: str
106
+ ) -> Union["FoamFile.Value", "FoamFile.Dictionary"]:
107
+ return self._file[(*self._keywords, keyword)]
108
+
109
+ def _setitem(
110
+ self,
111
+ keyword: str,
112
+ value: Any,
113
+ *,
114
+ assume_field: bool = False,
115
+ assume_dimensions: bool = False,
116
+ ) -> None:
117
+ self._file._setitem(
118
+ (*self._keywords, keyword),
119
+ value,
120
+ assume_field=assume_field,
121
+ assume_dimensions=assume_dimensions,
122
+ )
123
+
124
+ def __setitem__(self, keyword: str, value: Any) -> None:
125
+ self._setitem(keyword, value)
126
+
127
+ def __delitem__(self, keyword: str) -> None:
128
+ del self._file[(*self._keywords, keyword)]
129
+
130
+ def __iter__(self) -> Iterator[str]:
131
+ return self._file._iter(tuple(self._keywords))
132
+
133
+ def __contains__(self, keyword: object) -> bool:
134
+ return (*self._keywords, keyword) in self._file
135
+
136
+ def __len__(self) -> int:
137
+ return len(list(iter(self)))
138
+
139
+ def update(self, *args: Any, **kwargs: Any) -> None:
140
+ with self._file:
141
+ super().update(*args, **kwargs)
142
+
143
+ def clear(self) -> None:
144
+ with self._file:
145
+ super().clear()
146
+
147
+ def __repr__(self) -> str:
148
+ return f"{type(self).__qualname__}({self._file}, {self._keywords})"
149
+
150
+ def as_dict(self) -> FoamDictionaryBase._Dict:
151
+ """
152
+ Return a nested dict representation of the dictionary.
153
+ """
154
+ ret = self._file.as_dict()
155
+
156
+ for k in self._keywords:
157
+ assert isinstance(ret, dict)
158
+ v = ret[k]
159
+ assert isinstance(v, dict)
160
+ ret = v
161
+
162
+ return ret
163
+
164
+ def __getitem__(
165
+ self, keywords: Union[str, Tuple[str, ...]]
166
+ ) -> Union["FoamFile.Value", "FoamFile.Dictionary"]:
167
+ if not isinstance(keywords, tuple):
168
+ keywords = (keywords,)
169
+
170
+ _, parsed = self._read()
171
+
172
+ value = get_value(parsed, keywords)
173
+
174
+ if value is None:
175
+ return FoamFile.Dictionary(self, keywords)
176
+ else:
177
+ return value
178
+
179
+ def _setitem(
180
+ self,
181
+ keywords: Union[str, Tuple[str, ...]],
182
+ value: Any,
183
+ *,
184
+ assume_field: bool = False,
185
+ assume_dimensions: bool = False,
186
+ ) -> None:
187
+ if not isinstance(keywords, tuple):
188
+ keywords = (keywords,)
189
+
190
+ contents, parsed = self._read()
191
+
192
+ if isinstance(value, Mapping):
193
+ with self:
194
+ if isinstance(value, FoamDictionaryBase):
195
+ value = value.as_dict()
196
+
197
+ start, end = get_entry_locn(parsed, keywords, missing_ok=True)
198
+
199
+ self._write(
200
+ f"{contents[:start]}\n{serialize_entry(keywords[-1], {})}\n{contents[end:]}"
201
+ )
202
+
203
+ for k, v in value.items():
204
+ self[(*keywords, k)] = v
205
+ else:
206
+ start, end = get_entry_locn(parsed, keywords, missing_ok=True)
207
+
208
+ self._write(
209
+ f"{contents[:start]}\n{serialize_entry(keywords[-1], value, assume_field=assume_field, assume_dimensions=assume_dimensions)}\n{contents[end:]}"
210
+ )
211
+
212
+ def __setitem__(self, keywords: Union[str, Tuple[str, ...]], value: Any) -> None:
213
+ self._setitem(keywords, value)
214
+
215
+ def __delitem__(self, keywords: Union[str, Tuple[str, ...]]) -> None:
216
+ if not isinstance(keywords, tuple):
217
+ keywords = (keywords,)
218
+
219
+ contents, parsed = self._read()
220
+
221
+ start, end = get_entry_locn(parsed, keywords)
222
+
223
+ self._write(contents[:start] + contents[end:])
224
+
225
+ def _iter(self, keywords: Union[str, Tuple[str, ...]] = ()) -> Iterator[str]:
226
+ if not isinstance(keywords, tuple):
227
+ keywords = (keywords,)
228
+
229
+ contents = self.path.read_text()
230
+ parsed = parse(contents)
231
+
232
+ yield from (k[-1] for k in parsed if k[:-1] == keywords)
233
+
234
+ def __iter__(self) -> Iterator[str]:
235
+ return self._iter()
236
+
237
+ def __contains__(self, keywords: object) -> bool:
238
+ if not isinstance(keywords, tuple):
239
+ keywords = (keywords,)
240
+ _, parsed = self._read()
241
+ return keywords in parsed
242
+
243
+ def __len__(self) -> int:
244
+ return len(list(iter(self)))
245
+
246
+ def update(self, *args: Any, **kwargs: Any) -> None:
247
+ with self:
248
+ super().update(*args, **kwargs)
249
+
250
+ def clear(self) -> None:
251
+ with self:
252
+ super().clear()
253
+
254
+ def __fspath__(self) -> str:
255
+ return str(self.path)
256
+
257
+ def __repr__(self) -> str:
258
+ return f"{type(self).__name__}({self.path})"
259
+
260
+ def as_dict(self) -> FoamDictionaryBase._Dict:
261
+ """
262
+ Return a nested dict representation of the file.
263
+ """
264
+ _, parsed = self._read()
265
+ return as_dict(parsed)
266
+
267
+
268
+ class FoamFieldFile(FoamFile):
269
+ """An OpenFOAM dictionary file representing a field as a mutable mapping."""
270
+
271
+ class BoundariesDictionary(FoamFile.Dictionary):
272
+ def __getitem__(self, keyword: str) -> "FoamFieldFile.BoundaryDictionary":
273
+ return cast(FoamFieldFile.BoundaryDictionary, super().__getitem__(keyword))
274
+
275
+ class BoundaryDictionary(FoamFile.Dictionary):
276
+ """An OpenFOAM dictionary representing a boundary condition as a mutable mapping."""
277
+
278
+ def __setitem__(self, key: str, value: Any) -> None:
279
+ if key == "value":
280
+ self._setitem(key, value, assume_field=True)
281
+ else:
282
+ self._setitem(key, value)
283
+
284
+ @property
285
+ def type(self) -> str:
286
+ """
287
+ Alias of `self["type"]`.
288
+ """
289
+ ret = self["type"]
290
+ if not isinstance(ret, str):
291
+ raise TypeError("type is not a string")
292
+ return ret
293
+
294
+ @type.setter
295
+ def type(self, value: str) -> None:
296
+ self["type"] = value
297
+
298
+ @property
299
+ def value(
300
+ self,
301
+ ) -> Union[
302
+ int,
303
+ float,
304
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
305
+ "NDArray[np.generic]",
306
+ ]:
307
+ """
308
+ Alias of `self["value"]`.
309
+ """
310
+ ret = self["value"]
311
+ if not isinstance(ret, (int, float, Sequence)):
312
+ raise TypeError("value is not a field")
313
+ return cast(Union[int, float, Sequence[Union[int, float]]], ret)
314
+
315
+ @value.setter
316
+ def value(
317
+ self,
318
+ value: Union[
319
+ int,
320
+ float,
321
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
322
+ "NDArray[np.generic]",
323
+ ],
324
+ ) -> None:
325
+ self["value"] = value
326
+
327
+ @value.deleter
328
+ def value(self) -> None:
329
+ del self["value"]
330
+
331
+ def __getitem__(
332
+ self, keywords: Union[str, Tuple[str, ...]]
333
+ ) -> Union[FoamFile.Value, FoamFile.Dictionary]:
334
+ if not isinstance(keywords, tuple):
335
+ keywords = (keywords,)
336
+
337
+ ret = super().__getitem__(keywords)
338
+ if keywords[0] == "boundaryField" and isinstance(ret, FoamFile.Dictionary):
339
+ if len(keywords) == 1:
340
+ ret = FoamFieldFile.BoundariesDictionary(self, keywords)
341
+ elif len(keywords) == 2:
342
+ ret = FoamFieldFile.BoundaryDictionary(self, keywords)
343
+ return ret
344
+
345
+ def __setitem__(self, keywords: Union[str, Tuple[str, ...]], value: Any) -> None:
346
+ if not isinstance(keywords, tuple):
347
+ keywords = (keywords,)
348
+
349
+ if keywords == ("internalField",):
350
+ self._setitem(keywords, value, assume_field=True)
351
+ elif keywords == ("dimensions",):
352
+ self._setitem(keywords, value, assume_dimensions=True)
353
+ else:
354
+ self._setitem(keywords, value)
355
+
356
+ @property
357
+ def dimensions(self) -> FoamFile.DimensionSet:
358
+ """
359
+ Alias of `self["dimensions"]`.
360
+ """
361
+ ret = self["dimensions"]
362
+ if not isinstance(ret, FoamFile.DimensionSet):
363
+ raise TypeError("dimensions is not a DimensionSet")
364
+ return ret
365
+
366
+ @dimensions.setter
367
+ def dimensions(
368
+ self, value: Union[FoamFile.DimensionSet, Sequence[Union[int, float]]]
369
+ ) -> None:
370
+ self["dimensions"] = value
371
+
372
+ @property
373
+ def internal_field(
374
+ self,
375
+ ) -> Union[
376
+ int,
377
+ float,
378
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
379
+ "NDArray[np.generic]",
380
+ ]:
381
+ """
382
+ Alias of `self["internalField"]`.
383
+ """
384
+ ret = self["internalField"]
385
+ if not isinstance(ret, (int, float, Sequence)):
386
+ raise TypeError("internalField is not a field")
387
+ return cast(Union[int, float, Sequence[Union[int, float]]], ret)
388
+
389
+ @internal_field.setter
390
+ def internal_field(
391
+ self,
392
+ value: Union[
393
+ int,
394
+ float,
395
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
396
+ "NDArray[np.generic]",
397
+ ],
398
+ ) -> None:
399
+ self["internalField"] = value
400
+
401
+ @property
402
+ def boundary_field(self) -> "FoamFieldFile.BoundariesDictionary":
403
+ """
404
+ Alias of `self["boundaryField"]`.
405
+ """
406
+ ret = self["boundaryField"]
407
+ if not isinstance(ret, FoamFieldFile.BoundariesDictionary):
408
+ assert not isinstance(ret, FoamFile.Dictionary)
409
+ raise TypeError("boundaryField is not a dictionary")
410
+ return ret
@@ -0,0 +1,177 @@
1
+ from typing import Mapping, MutableMapping, Optional, Sequence, Tuple
2
+
3
+ from pyparsing import (
4
+ Dict,
5
+ Forward,
6
+ Group,
7
+ Keyword,
8
+ LineEnd,
9
+ Literal,
10
+ Located,
11
+ Opt,
12
+ ParseResults,
13
+ ParserElement,
14
+ QuotedString,
15
+ Word,
16
+ c_style_comment,
17
+ common,
18
+ cpp_style_comment,
19
+ identbodychars,
20
+ printables,
21
+ )
22
+
23
+ from ._base import FoamDictionaryBase
24
+
25
+ _YES = Keyword("yes").set_parse_action(lambda: True)
26
+ _NO = Keyword("no").set_parse_action(lambda: False)
27
+ _DIMENSIONS = (
28
+ Literal("[").suppress() + common.number * 7 + Literal("]").suppress()
29
+ ).set_parse_action(lambda tks: FoamDictionaryBase.DimensionSet(*tks))
30
+
31
+
32
+ def _list_of(elem: ParserElement) -> ParserElement:
33
+ return Opt(
34
+ Literal("List") + Literal("<") + common.identifier + Literal(">")
35
+ ).suppress() + (
36
+ (
37
+ Opt(common.integer).suppress()
38
+ + (
39
+ Literal("(").suppress()
40
+ + Group((elem)[...], aslist=True)
41
+ + Literal(")").suppress()
42
+ )
43
+ )
44
+ | (
45
+ common.integer + Literal("{").suppress() + elem + Literal("}").suppress()
46
+ ).set_parse_action(lambda tks: [[tks[1]] * tks[0]])
47
+ )
48
+
49
+
50
+ _TENSOR = _list_of(common.number) | common.number
51
+ _IDENTIFIER = Word(identbodychars + "$", printables.replace(";", ""))
52
+ _DIMENSIONED = (Opt(_IDENTIFIER) + _DIMENSIONS + _TENSOR).set_parse_action(
53
+ lambda tks: FoamDictionaryBase.Dimensioned(*reversed(tks.as_list()))
54
+ )
55
+ _FIELD = (Keyword("uniform").suppress() + _TENSOR) | (
56
+ Keyword("nonuniform").suppress() + _list_of(_TENSOR)
57
+ )
58
+ _TOKEN = QuotedString('"', unquote_results=False) | _IDENTIFIER
59
+ _ITEM = Forward()
60
+ _LIST = _list_of(_ITEM)
61
+ _ITEM <<= (
62
+ _FIELD | _LIST | _DIMENSIONED | _DIMENSIONS | common.number | _YES | _NO | _TOKEN
63
+ )
64
+ _TOKENS = (
65
+ QuotedString('"', unquote_results=False) | Word(printables.replace(";", ""))
66
+ )[2, ...].set_parse_action(lambda tks: " ".join(tks))
67
+
68
+ _VALUE = _ITEM ^ _TOKENS
69
+
70
+ _ENTRY = Forward()
71
+ _DICTIONARY = Dict(Group(_ENTRY)[...])
72
+ _ENTRY <<= Located(
73
+ _TOKEN
74
+ + (
75
+ (Literal("{").suppress() + _DICTIONARY + Literal("}").suppress())
76
+ | (Opt(_VALUE, default="") + Literal(";").suppress())
77
+ )
78
+ )
79
+ _FILE = (
80
+ _DICTIONARY.ignore(c_style_comment)
81
+ .ignore(cpp_style_comment)
82
+ .ignore(Literal("#include") + ... + LineEnd()) # type: ignore [no-untyped-call]
83
+ )
84
+
85
+ Parsed = Mapping[Sequence[str], Tuple[int, Optional[FoamDictionaryBase.Value], int]]
86
+
87
+
88
+ def _flatten_result(
89
+ parse_result: ParseResults, *, _keywords: Sequence[str] = ()
90
+ ) -> Parsed:
91
+ ret: MutableMapping[
92
+ Sequence[str], Tuple[int, Optional[FoamDictionaryBase.Value], int]
93
+ ] = {}
94
+ start = parse_result.locn_start
95
+ assert isinstance(start, int)
96
+ item = parse_result.value
97
+ assert isinstance(item, Sequence)
98
+ end = parse_result.locn_end
99
+ assert isinstance(end, int)
100
+ key, *values = item
101
+ assert isinstance(key, str)
102
+ ret[(*_keywords, key)] = (start, None, end)
103
+ for value in values:
104
+ if isinstance(value, ParseResults):
105
+ ret.update(_flatten_result(value, _keywords=(*_keywords, key)))
106
+ else:
107
+ ret[(*_keywords, key)] = (start, value, end)
108
+ return ret
109
+
110
+
111
+ def parse(
112
+ contents: str,
113
+ ) -> Parsed:
114
+ parse_results = _FILE.parse_string(contents, parse_all=True)
115
+ ret: MutableMapping[
116
+ Sequence[str], Tuple[int, Optional[FoamDictionaryBase.Value], int]
117
+ ] = {}
118
+ for parse_result in parse_results:
119
+ ret.update(_flatten_result(parse_result))
120
+ return ret
121
+
122
+
123
+ def get_value(
124
+ parsed: Parsed,
125
+ keywords: Tuple[str, ...],
126
+ ) -> Optional[FoamDictionaryBase.Value]:
127
+ """
128
+ Value of an entry.
129
+ """
130
+ _, value, _ = parsed[keywords]
131
+ return value
132
+
133
+
134
+ def get_entry_locn(
135
+ parsed: Parsed,
136
+ keywords: Tuple[str, ...],
137
+ *,
138
+ missing_ok: bool = False,
139
+ ) -> Tuple[int, int]:
140
+ """
141
+ Location of an entry or where it should be inserted.
142
+ """
143
+ try:
144
+ start, _, end = parsed[keywords]
145
+ except KeyError:
146
+ if missing_ok:
147
+ if len(keywords) > 1:
148
+ _, _, end = parsed[keywords[:-1]]
149
+ end -= 1
150
+ else:
151
+ end = -1
152
+
153
+ start = end
154
+ else:
155
+ raise
156
+
157
+ return start, end
158
+
159
+
160
+ def as_dict(parsed: Parsed) -> FoamDictionaryBase._Dict:
161
+ """
162
+ Return a nested dict representation of the file.
163
+ """
164
+ ret: FoamDictionaryBase._Dict = {}
165
+ for keywords, (_, value, _) in parsed.items():
166
+
167
+ r = ret
168
+ for k in keywords[:-1]:
169
+ assert isinstance(r, dict)
170
+ v = r[k]
171
+ assert isinstance(v, dict)
172
+ r = v
173
+
174
+ assert isinstance(r, dict)
175
+ r[keywords[-1]] = {} if value is None else value
176
+
177
+ return ret
@@ -0,0 +1,123 @@
1
+ from contextlib import suppress
2
+ from typing import Any, Mapping, Sequence
3
+
4
+ from ._base import FoamDictionaryBase
5
+
6
+ try:
7
+ import numpy as np
8
+ except ModuleNotFoundError:
9
+ numpy = False
10
+ else:
11
+ numpy = True
12
+
13
+
14
+ def _is_sequence(value: Any) -> bool:
15
+ return (
16
+ isinstance(value, Sequence)
17
+ and not isinstance(value, str)
18
+ or numpy
19
+ and isinstance(value, np.ndarray)
20
+ )
21
+
22
+
23
+ def _serialize_bool(value: Any) -> str:
24
+ if value is True:
25
+ return "yes"
26
+ elif value is False:
27
+ return "no"
28
+ else:
29
+ raise TypeError(f"Not a bool: {type(value)}")
30
+
31
+
32
+ def _serialize_list(value: Any) -> str:
33
+ if _is_sequence(value):
34
+ return f"({' '.join(_serialize_value(v) for v in value)})"
35
+ else:
36
+ raise TypeError(f"Not a valid sequence: {type(value)}")
37
+
38
+
39
+ def _serialize_field(value: Any) -> str:
40
+ if _is_sequence(value):
41
+ try:
42
+ s = _serialize_list(value)
43
+ except TypeError:
44
+ raise TypeError(f"Not a valid field: {type(value)}") from None
45
+ else:
46
+ if len(value) < 10:
47
+ return f"uniform {s}"
48
+ else:
49
+ if isinstance(value[0], (int, float)):
50
+ kind = "scalar"
51
+ elif len(value[0]) == 3:
52
+ kind = "vector"
53
+ elif len(value[0]) == 6:
54
+ kind = "symmTensor"
55
+ elif len(value[0]) == 9:
56
+ kind = "tensor"
57
+ else:
58
+ raise TypeError(
59
+ f"Unsupported sequence length for field: {len(value[0])}"
60
+ )
61
+ return f"nonuniform List<{kind}> {len(value)}{s}"
62
+ else:
63
+ return f"uniform {value}"
64
+
65
+
66
+ def _serialize_dimensions(value: Any) -> str:
67
+ if _is_sequence(value) and len(value) == 7:
68
+ return f"[{' '.join(str(v) for v in value)}]"
69
+ else:
70
+ raise TypeError(f"Not a valid dimension set: {type(value)}")
71
+
72
+
73
+ def _serialize_dimensioned(value: Any) -> str:
74
+ if isinstance(value, FoamDictionaryBase.Dimensioned):
75
+ if value.name is not None:
76
+ return f"{value.name} {_serialize_dimensions(value.dimensions)} {_serialize_value(value.value)}"
77
+ else:
78
+ return f"{_serialize_dimensions(value.dimensions)} {_serialize_value(value.value)}"
79
+ else:
80
+ raise TypeError(f"Not a valid dimensioned value: {type(value)}")
81
+
82
+
83
+ def _serialize_value(
84
+ value: Any, *, assume_field: bool = False, assume_dimensions: bool = False
85
+ ) -> str:
86
+ if isinstance(value, FoamDictionaryBase.DimensionSet) or assume_dimensions:
87
+ with suppress(TypeError):
88
+ return _serialize_dimensions(value)
89
+
90
+ if assume_field:
91
+ with suppress(TypeError):
92
+ return _serialize_field(value)
93
+
94
+ with suppress(TypeError):
95
+ return _serialize_dimensioned(value)
96
+
97
+ with suppress(TypeError):
98
+ return _serialize_list(value)
99
+
100
+ with suppress(TypeError):
101
+ return _serialize_bool(value)
102
+
103
+ return str(value)
104
+
105
+
106
+ def _serialize_dictionary(value: Any) -> str:
107
+ if isinstance(value, Mapping):
108
+ return "\n".join(serialize_entry(k, v) for k, v in value.items())
109
+ else:
110
+ raise TypeError(f"Not a valid dictionary: {type(value)}")
111
+
112
+
113
+ def serialize_entry(
114
+ keyword: str,
115
+ value: Any,
116
+ *,
117
+ assume_field: bool = False,
118
+ assume_dimensions: bool = False,
119
+ ) -> str:
120
+ try:
121
+ return f"{keyword}\n{{\n{_serialize_dictionary(value)}\n}}"
122
+ except TypeError:
123
+ return f"{keyword} {_serialize_value(value, assume_field=assume_field, assume_dimensions=assume_dimensions)};"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foamlib
3
- Version: 0.2.2
3
+ Version: 0.2.4
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
@@ -27,6 +27,7 @@ Description-Content-Type: text/markdown
27
27
  License-File: LICENSE.txt
28
28
  Requires-Dist: aioshutil <2,>=1
29
29
  Requires-Dist: pyparsing <4,>=3
30
+ Requires-Dist: typing-extensions <5,>=4
30
31
  Provides-Extra: docs
31
32
  Requires-Dist: sphinx <8,>=7 ; extra == 'docs'
32
33
  Requires-Dist: sphinx-rtd-theme ; extra == 'docs'
@@ -0,0 +1,14 @@
1
+ foamlib/__init__.py,sha256=BPC3la1UvhtGOS7utfBE5tccqz8mxqzd7Fn0zFC5yz0,287
2
+ foamlib/_cases.py,sha256=4f3c5BXnsHPhFvgXNjUcGGHyu7I0WZT6zxlvGhb9kMY,21213
3
+ foamlib/_subprocesses.py,sha256=5vqdQvpN_2v4GgDqxi-s88NGhZ6doFxkh0XY89ZWuHA,1926
4
+ foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ foamlib/_dictionaries/__init__.py,sha256=6UWBGe1t7cq-d6WWQrVm0Xpi7Whpkr-mkTWgAM4NwcE,160
6
+ foamlib/_dictionaries/_base.py,sha256=H8XfiaX1LD6OWwZ9m61SaKgI-_szF1udyEfiLurrCB8,1493
7
+ foamlib/_dictionaries/_files.py,sha256=1Ve1FG2VVKLyj6yZGe4Vy8EhEOtyORQtJMUK-cbPNt4,12424
8
+ foamlib/_dictionaries/_parsing.py,sha256=65kwMU6b4WmMngOR5ED8IBvMa59FQqTRmd9o0xPnWJM,4768
9
+ foamlib/_dictionaries/_serialization.py,sha256=viHpQKggSBfxq9V6veVcCCLNq7wdJ-2g5maecaN2beU,3612
10
+ foamlib-0.2.4.dist-info/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
11
+ foamlib-0.2.4.dist-info/METADATA,sha256=Z5aFcGYwPssHy9DTrQqZ4Lsw6cJ1QjW9UFiqe0Uuf58,4640
12
+ foamlib-0.2.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
13
+ foamlib-0.2.4.dist-info/top_level.txt,sha256=ZdVYtetXGwPwyfL-WhlhbTFQGAwKX5P_gXxtH9JYFPI,8
14
+ foamlib-0.2.4.dist-info/RECORD,,
foamlib/_dictionaries.py DELETED
@@ -1,491 +0,0 @@
1
- from pathlib import Path
2
- from dataclasses import dataclass
3
- from contextlib import suppress
4
- from typing import (
5
- Any,
6
- Union,
7
- Sequence,
8
- Iterator,
9
- Optional,
10
- Mapping,
11
- MutableMapping,
12
- NamedTuple,
13
- cast,
14
- )
15
-
16
- from pyparsing import (
17
- Dict,
18
- Forward,
19
- Group,
20
- Keyword,
21
- LineEnd,
22
- Literal,
23
- Opt,
24
- ParserElement,
25
- QuotedString,
26
- Word,
27
- c_style_comment,
28
- common,
29
- cpp_style_comment,
30
- identbodychars,
31
- printables,
32
- )
33
-
34
- try:
35
- import numpy as np
36
- from numpy.typing import NDArray
37
- except ModuleNotFoundError:
38
- numpy = False
39
- else:
40
- numpy = True
41
-
42
- from ._subprocesses import run_process, CalledProcessError
43
-
44
-
45
- class _FoamDictionary(MutableMapping[str, Union["FoamFile.Value", "_FoamDictionary"]]):
46
-
47
- def __init__(self, _file: "FoamFile", _keywords: Sequence[str]) -> None:
48
- self._file = _file
49
- self._keywords = _keywords
50
-
51
- def _cmd(self, args: Sequence[str], *, key: Optional[str] = None) -> str:
52
- keywords = self._keywords
53
-
54
- if key is not None:
55
- keywords = [*self._keywords, key]
56
-
57
- if keywords:
58
- args = ["-entry", "/".join(keywords), *args]
59
-
60
- try:
61
- return (
62
- run_process(
63
- ["foamDictionary", *args, "-precision", "15", self._file.path],
64
- )
65
- .stdout.decode()
66
- .strip()
67
- )
68
- except CalledProcessError as e:
69
- stderr = e.stderr.decode()
70
- if "Cannot find entry" in stderr:
71
- raise KeyError(key) from None
72
- else:
73
- raise RuntimeError(
74
- f"{e.cmd} failed with return code {e.returncode}\n{e.stderr.decode()}"
75
- ) from None
76
-
77
- def __getitem__(self, key: str) -> Union["FoamFile.Value", "_FoamDictionary"]:
78
- value = _DICTIONARY.parse_file(self._file.path, parse_all=True).as_dict()
79
-
80
- for key in [*self._keywords, key]:
81
- value = value[key]
82
-
83
- if isinstance(value, dict):
84
- return _FoamDictionary(self._file, [*self._keywords, key])
85
- else:
86
- return value
87
-
88
- def _setitem(
89
- self,
90
- key: str,
91
- value: Any,
92
- *,
93
- assume_field: bool = False,
94
- assume_dimensions: bool = False,
95
- ) -> None:
96
- if isinstance(value, _FoamDictionary):
97
- value = value._cmd(["-value"])
98
- elif isinstance(value, Mapping):
99
- self._cmd(["-set", "{}"], key=key)
100
- subdict = self[key]
101
- assert isinstance(subdict, _FoamDictionary)
102
- for k, v in value.items():
103
- subdict[k] = v
104
- return
105
- else:
106
- value = serialize(
107
- value, assume_field=assume_field, assume_dimensions=assume_dimensions
108
- )
109
-
110
- if len(value) < 1000:
111
- self._cmd(["-set", value], key=key)
112
- else:
113
- self._cmd(["-set", "_foamlib_value_"], key=key)
114
- contents = self._file.path.read_text()
115
- contents = contents.replace("_foamlib_value_", value, 1)
116
- self._file.path.write_text(contents)
117
-
118
- def __setitem__(self, key: str, value: Any) -> None:
119
- self._setitem(key, value)
120
-
121
- def __delitem__(self, key: str) -> None:
122
- if key not in self:
123
- raise KeyError(key)
124
- self._cmd(["-remove"], key=key)
125
-
126
- def __iter__(self) -> Iterator[str]:
127
- value = _DICTIONARY.parse_file(self._file.path, parse_all=True).as_dict()
128
-
129
- for key in self._keywords:
130
- value = value[key]
131
-
132
- yield from value
133
-
134
- def __len__(self) -> int:
135
- return len(list(iter(self)))
136
-
137
- def __repr__(self) -> str:
138
- return f"FoamFile.Dictionary({self._file}, {self._keywords})"
139
-
140
-
141
- class FoamFile(_FoamDictionary):
142
- """An OpenFOAM dictionary file as a mutable mapping."""
143
-
144
- Dictionary = _FoamDictionary
145
-
146
- class DimensionSet(NamedTuple):
147
- mass: Union[int, float] = 0
148
- length: Union[int, float] = 0
149
- time: Union[int, float] = 0
150
- temperature: Union[int, float] = 0
151
- moles: Union[int, float] = 0
152
- current: Union[int, float] = 0
153
- luminous_intensity: Union[int, float] = 0
154
-
155
- def __repr__(self) -> str:
156
- return f"{type(self).__qualname__}({', '.join(f'{n}={v}' for n, v in zip(self._fields, self) if v != 0)})"
157
-
158
- @dataclass
159
- class Dimensioned:
160
- value: Union[int, float, Sequence[Union[int, float]]] = 0
161
- dimensions: Union["FoamFile.DimensionSet", Sequence[Union[int, float]]] = ()
162
- name: Optional[str] = None
163
-
164
- def __post_init__(self) -> None:
165
- if not isinstance(self.dimensions, FoamFile.DimensionSet):
166
- self.dimensions = FoamFile.DimensionSet(*self.dimensions)
167
-
168
- Value = Union[str, int, float, bool, Dimensioned, DimensionSet, Sequence["Value"]]
169
- """
170
- A value that can be stored in an OpenFOAM dictionary.
171
- """
172
-
173
- def __init__(self, path: Union[str, Path]) -> None:
174
- super().__init__(self, [])
175
- self.path = Path(path).absolute()
176
- if self.path.is_dir():
177
- raise IsADirectoryError(self.path)
178
- elif not self.path.is_file():
179
- raise FileNotFoundError(self.path)
180
-
181
- def __fspath__(self) -> str:
182
- return str(self.path)
183
-
184
- def __repr__(self) -> str:
185
- return f"{type(self).__name__}({self.path})"
186
-
187
-
188
- class FoamFieldFile(FoamFile):
189
- """An OpenFOAM dictionary file representing a field as a mutable mapping."""
190
-
191
- class BoundariesDictionary(_FoamDictionary):
192
- def __getitem__(
193
- self, key: str
194
- ) -> Union["FoamFile.Value", "FoamFieldFile.BoundaryDictionary"]:
195
- ret = super().__getitem__(key)
196
- if isinstance(ret, _FoamDictionary):
197
- ret = FoamFieldFile.BoundaryDictionary(
198
- self._file, [*self._keywords, key]
199
- )
200
- return ret
201
-
202
- def __repr__(self) -> str:
203
- return f"{type(self).__qualname__}({self._file}, {self._keywords})"
204
-
205
- class BoundaryDictionary(_FoamDictionary):
206
- """An OpenFOAM dictionary representing a boundary condition as a mutable mapping."""
207
-
208
- def __setitem__(self, key: str, value: Any) -> None:
209
- if key == "value":
210
- self._setitem(key, value, assume_field=True)
211
- else:
212
- self._setitem(key, value)
213
-
214
- @property
215
- def type(self) -> str:
216
- """
217
- Alias of `self["type"]`.
218
- """
219
- ret = self["type"]
220
- if not isinstance(ret, str):
221
- raise TypeError("type is not a string")
222
- return ret
223
-
224
- @type.setter
225
- def type(self, value: str) -> None:
226
- self["type"] = value
227
-
228
- @property
229
- def value(
230
- self,
231
- ) -> Union[
232
- int,
233
- float,
234
- Sequence[Union[int, float, Sequence[Union[int, float]]]],
235
- "NDArray[np.generic]",
236
- ]:
237
- """
238
- Alias of `self["value"]`.
239
- """
240
- ret = self["value"]
241
- if not isinstance(ret, (int, float, Sequence)):
242
- raise TypeError("value is not a field")
243
- return cast(Union[int, float, Sequence[Union[int, float]]], ret)
244
-
245
- @value.setter
246
- def value(
247
- self,
248
- value: Union[
249
- int,
250
- float,
251
- Sequence[Union[int, float, Sequence[Union[int, float]]]],
252
- "NDArray[np.generic]",
253
- ],
254
- ) -> None:
255
- self["value"] = value
256
-
257
- @value.deleter
258
- def value(self) -> None:
259
- del self["value"]
260
-
261
- def __repr__(self) -> str:
262
- return f"{type(self).__qualname__}({self._file}, {self._keywords})"
263
-
264
- def __getitem__(self, key: str) -> Union[FoamFile.Value, _FoamDictionary]:
265
- ret = super().__getitem__(key)
266
- if key == "boundaryField" and isinstance(ret, _FoamDictionary):
267
- ret = FoamFieldFile.BoundariesDictionary(self, [key])
268
- return ret
269
-
270
- def __setitem__(self, key: str, value: Any) -> None:
271
- if key == "internalField":
272
- self._setitem(key, value, assume_field=True)
273
- elif key == "dimensions":
274
- self._setitem(key, value, assume_dimensions=True)
275
- else:
276
- self._setitem(key, value)
277
-
278
- @property
279
- def dimensions(self) -> FoamFile.DimensionSet:
280
- """
281
- Alias of `self["dimensions"]`.
282
- """
283
- ret = self["dimensions"]
284
- if not isinstance(ret, FoamFile.DimensionSet):
285
- raise TypeError("dimensions is not a DimensionSet")
286
- return ret
287
-
288
- @dimensions.setter
289
- def dimensions(
290
- self, value: Union[FoamFile.DimensionSet, Sequence[Union[int, float]]]
291
- ) -> None:
292
- self["dimensions"] = value
293
-
294
- @property
295
- def internal_field(
296
- self,
297
- ) -> Union[
298
- int,
299
- float,
300
- Sequence[Union[int, float, Sequence[Union[int, float]]]],
301
- "NDArray[np.generic]",
302
- ]:
303
- """
304
- Alias of `self["internalField"]`.
305
- """
306
- ret = self["internalField"]
307
- if not isinstance(ret, (int, float, Sequence)):
308
- raise TypeError("internalField is not a field")
309
- return cast(Union[int, float, Sequence[Union[int, float]]], ret)
310
-
311
- @internal_field.setter
312
- def internal_field(
313
- self,
314
- value: Union[
315
- int,
316
- float,
317
- Sequence[Union[int, float, Sequence[Union[int, float]]]],
318
- "NDArray[np.generic]",
319
- ],
320
- ) -> None:
321
- self["internalField"] = value
322
-
323
- @property
324
- def boundary_field(self) -> "FoamFieldFile.BoundariesDictionary":
325
- """
326
- Alias of `self["boundaryField"]`.
327
- """
328
- ret = self["boundaryField"]
329
- if not isinstance(ret, FoamFieldFile.BoundariesDictionary):
330
- assert not isinstance(ret, _FoamDictionary)
331
- raise TypeError("boundaryField is not a dictionary")
332
- return ret
333
-
334
-
335
- _YES = Keyword("yes").set_parse_action(lambda: True)
336
- _NO = Keyword("no").set_parse_action(lambda: False)
337
- _DIMENSIONS = (
338
- Literal("[").suppress() + common.number * 7 + Literal("]").suppress()
339
- ).set_parse_action(lambda tks: FoamFile.DimensionSet(*tks))
340
-
341
-
342
- def _list_of(elem: ParserElement) -> ParserElement:
343
- return Opt(
344
- Literal("List") + Literal("<") + common.identifier + Literal(">")
345
- ).suppress() + (
346
- (
347
- Opt(common.integer).suppress()
348
- + (
349
- Literal("(").suppress()
350
- + Group((elem)[...]).set_parse_action(lambda tks: tks.as_list())
351
- + Literal(")").suppress()
352
- )
353
- )
354
- | (
355
- common.integer + Literal("{").suppress() + elem + Literal("}").suppress()
356
- ).set_parse_action(lambda tks: [[tks[1]] * tks[0]])
357
- )
358
-
359
-
360
- _TENSOR = _list_of(common.number) | common.number
361
- _IDENTIFIER = Word(identbodychars + "$", identbodychars + "({,./:^!)}")
362
- _DIMENSIONED = (Opt(_IDENTIFIER) + _DIMENSIONS + _TENSOR).set_parse_action(
363
- lambda tks: FoamFile.Dimensioned(*reversed(tks.as_list()))
364
- )
365
- _FIELD = (Keyword("uniform").suppress() + _TENSOR) | (
366
- Keyword("nonuniform").suppress() + _list_of(_TENSOR)
367
- )
368
- _TOKEN = QuotedString('"', unquote_results=False) | _IDENTIFIER
369
- _DICTIONARY = Forward()
370
- _SUBDICT = Literal("{").suppress() + _DICTIONARY + Literal("}").suppress()
371
- _ITEM = Forward()
372
- _LIST = _list_of(_ITEM)
373
- _ITEM <<= (
374
- _FIELD
375
- | _LIST
376
- | _SUBDICT
377
- | _DIMENSIONED
378
- | _DIMENSIONS
379
- | common.number
380
- | _YES
381
- | _NO
382
- | _TOKEN
383
- )
384
- _TOKENS = (
385
- QuotedString('"', unquote_results=False) | Word(printables.replace(";", ""))
386
- )[2, ...].set_parse_action(lambda tks: " ".join(tks))
387
-
388
- _VALUE = _ITEM ^ _TOKENS
389
-
390
- _ENTRY = _IDENTIFIER + (
391
- (Opt(_VALUE, default=None) + Literal(";").suppress()) | _SUBDICT
392
- )
393
- _DICTIONARY <<= (
394
- Dict(Group(_ENTRY)[...])
395
- .set_parse_action(lambda tks: {} if not tks else tks)
396
- .ignore(c_style_comment)
397
- .ignore(cpp_style_comment)
398
- .ignore(Literal("#include") + ... + LineEnd()) # type: ignore [no-untyped-call]
399
- )
400
-
401
-
402
- def _serialize_bool(value: Any) -> str:
403
- if value is True:
404
- return "yes"
405
- elif value is False:
406
- return "no"
407
- else:
408
- raise TypeError(f"Not a bool: {type(value)}")
409
-
410
-
411
- def _is_sequence(value: Any) -> bool:
412
- return (
413
- isinstance(value, Sequence)
414
- and not isinstance(value, str)
415
- or numpy
416
- and isinstance(value, np.ndarray)
417
- )
418
-
419
-
420
- def _serialize_list(value: Any) -> str:
421
- if _is_sequence(value):
422
- return f"({' '.join(serialize(v) for v in value)})"
423
- else:
424
- raise TypeError(f"Not a valid sequence: {type(value)}")
425
-
426
-
427
- def _serialize_field(value: Any) -> str:
428
- if _is_sequence(value):
429
- try:
430
- s = _serialize_list(value)
431
- except TypeError:
432
- raise TypeError(f"Not a valid field: {type(value)}") from None
433
- else:
434
- if len(value) < 10:
435
- return f"uniform {s}"
436
- else:
437
- if isinstance(value[0], (int, float)):
438
- kind = "scalar"
439
- elif len(value[0]) == 3:
440
- kind = "vector"
441
- elif len(value[0]) == 6:
442
- kind = "symmTensor"
443
- elif len(value[0]) == 9:
444
- kind = "tensor"
445
- else:
446
- raise TypeError(
447
- f"Unsupported sequence length for field: {len(value[0])}"
448
- )
449
- return f"nonuniform List<{kind}> {len(value)}{s}"
450
- else:
451
- return f"uniform {value}"
452
-
453
-
454
- def _serialize_dimensions(value: Any) -> str:
455
- if _is_sequence(value) and len(value) == 7:
456
- return f"[{' '.join(str(v) for v in value)}]"
457
- else:
458
- raise TypeError(f"Not a valid dimension set: {type(value)}")
459
-
460
-
461
- def _serialize_dimensioned(value: Any) -> str:
462
- if isinstance(value, FoamFile.Dimensioned):
463
- if value.name is not None:
464
- return f"{value.name} {_serialize_dimensions(value.dimensions)} {serialize(value.value)}"
465
- else:
466
- return f"{_serialize_dimensions(value.dimensions)} {serialize(value.value)}"
467
- else:
468
- raise TypeError(f"Not a valid dimensioned value: {type(value)}")
469
-
470
-
471
- def serialize(
472
- value: Any, *, assume_field: bool = False, assume_dimensions: bool = False
473
- ) -> str:
474
- if isinstance(value, FoamFile.DimensionSet) or assume_dimensions:
475
- with suppress(TypeError):
476
- return _serialize_dimensions(value)
477
-
478
- if assume_field:
479
- with suppress(TypeError):
480
- return _serialize_field(value)
481
-
482
- with suppress(TypeError):
483
- return _serialize_dimensioned(value)
484
-
485
- with suppress(TypeError):
486
- return _serialize_list(value)
487
-
488
- with suppress(TypeError):
489
- return _serialize_bool(value)
490
-
491
- return str(value)
@@ -1,10 +0,0 @@
1
- foamlib/__init__.py,sha256=EFlApZJu-niDqPz_q3gjFdJ4ScRjPAjKC1gfKtXLMDQ,241
2
- foamlib/_cases.py,sha256=4f3c5BXnsHPhFvgXNjUcGGHyu7I0WZT6zxlvGhb9kMY,21213
3
- foamlib/_dictionaries.py,sha256=5hElrfAk_2SjINqmFE1dYK4H7lg8rRTzO-vwzMWj5QM,14874
4
- foamlib/_subprocesses.py,sha256=5vqdQvpN_2v4GgDqxi-s88NGhZ6doFxkh0XY89ZWuHA,1926
5
- foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- foamlib-0.2.2.dist-info/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
7
- foamlib-0.2.2.dist-info/METADATA,sha256=3J72tulIUFzK32O6aGy_LDzNI1LXWzcLM-9yVRVeJVY,4600
8
- foamlib-0.2.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
9
- foamlib-0.2.2.dist-info/top_level.txt,sha256=ZdVYtetXGwPwyfL-WhlhbTFQGAwKX5P_gXxtH9JYFPI,8
10
- foamlib-0.2.2.dist-info/RECORD,,