foamlib 0.2.9__py3-none-any.whl → 0.2.10__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,9 +1,9 @@
1
1
  """A Python interface for interacting with OpenFOAM."""
2
2
 
3
- __version__ = "0.2.9"
3
+ __version__ = "0.2.10"
4
4
 
5
5
  from ._cases import AsyncFoamCase, FoamCase, FoamCaseBase
6
- from ._dictionaries import FoamDictionaryBase, FoamFieldFile, FoamFile
6
+ from ._files import FoamDictionaryBase, FoamFieldFile, FoamFile
7
7
 
8
8
  __all__ = [
9
9
  "FoamCase",
foamlib/_cases.py CHANGED
@@ -27,7 +27,7 @@ else:
27
27
 
28
28
  import aioshutil
29
29
 
30
- from ._dictionaries import FoamFieldFile, FoamFile
30
+ from ._files import FoamFieldFile, FoamFile
31
31
  from ._util import CalledProcessError, is_sequence, run_process, run_process_async
32
32
 
33
33
 
@@ -505,7 +505,7 @@ class AsyncFoamCase(FoamCaseBase):
505
505
  check: bool = True,
506
506
  ) -> None:
507
507
  """
508
- Run this case.
508
+ Run this case, or a specified command in the context of this case.
509
509
 
510
510
  :param cmd: The command to run. If None, run the case. If a sequence, the first element is the command and the rest are arguments. If a string, `cmd` is executed in a shell.
511
511
  :param script: If True and `cmd` is None, use an (All)run(-parallel) script if it exists for running the case. If False or no run script is found, autodetermine the command(s) needed to run the case.
@@ -1,5 +1,6 @@
1
1
  from ._base import FoamDictionaryBase
2
- from ._files import FoamFieldFile, FoamFile
2
+ from ._fields import FoamFieldFile
3
+ from ._files import FoamFile
3
4
 
4
5
  __all__ = [
5
6
  "FoamFile",
@@ -0,0 +1,158 @@
1
+ import sys
2
+ from typing import Any, Tuple, Union, cast
3
+
4
+ if sys.version_info >= (3, 9):
5
+ from collections.abc import Sequence
6
+ else:
7
+ from typing import Sequence
8
+
9
+ from ._files import FoamFile
10
+
11
+ try:
12
+ import numpy as np
13
+ from numpy.typing import NDArray
14
+ except ModuleNotFoundError:
15
+ pass
16
+
17
+
18
+ class FoamFieldFile(FoamFile):
19
+ """An OpenFOAM dictionary file representing a field as a mutable mapping."""
20
+
21
+ class BoundariesDictionary(FoamFile.Dictionary):
22
+ def __getitem__(self, keyword: str) -> "FoamFieldFile.BoundaryDictionary":
23
+ value = super().__getitem__(keyword)
24
+ if not isinstance(value, FoamFieldFile.BoundaryDictionary):
25
+ assert not isinstance(value, FoamFile.Dictionary)
26
+ raise TypeError(f"boundary {keyword} is not a dictionary")
27
+ return value
28
+
29
+ class BoundaryDictionary(FoamFile.Dictionary):
30
+ """An OpenFOAM dictionary representing a boundary condition as a mutable mapping."""
31
+
32
+ def __setitem__(
33
+ self,
34
+ key: str,
35
+ value: FoamFile._SetValue,
36
+ ) -> None:
37
+ if key == "value":
38
+ self._setitem(key, value, assume_field=True)
39
+ else:
40
+ self._setitem(key, value)
41
+
42
+ @property
43
+ def type(self) -> str:
44
+ """Alias of `self["type"]`."""
45
+ ret = self["type"]
46
+ if not isinstance(ret, str):
47
+ raise TypeError("type is not a string")
48
+ return ret
49
+
50
+ @type.setter
51
+ def type(self, value: str) -> None:
52
+ self["type"] = value
53
+
54
+ @property
55
+ def value(
56
+ self,
57
+ ) -> Union[
58
+ int,
59
+ float,
60
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
61
+ "NDArray[np.generic]",
62
+ ]:
63
+ """Alias of `self["value"]`."""
64
+ ret = self["value"]
65
+ if not isinstance(ret, (int, float, Sequence)):
66
+ raise TypeError("value is not a field")
67
+ return cast(Union[int, float, Sequence[Union[int, float]]], ret)
68
+
69
+ @value.setter
70
+ def value(
71
+ self,
72
+ value: Union[
73
+ int,
74
+ float,
75
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
76
+ "NDArray[np.generic]",
77
+ ],
78
+ ) -> None:
79
+ self["value"] = value
80
+
81
+ @value.deleter
82
+ def value(self) -> None:
83
+ del self["value"]
84
+
85
+ def __getitem__(
86
+ self, keywords: Union[str, Tuple[str, ...]]
87
+ ) -> Union[FoamFile.Value, FoamFile.Dictionary]:
88
+ if not isinstance(keywords, tuple):
89
+ keywords = (keywords,)
90
+
91
+ ret = super().__getitem__(keywords)
92
+ if keywords[0] == "boundaryField" and isinstance(ret, FoamFile.Dictionary):
93
+ if len(keywords) == 1:
94
+ ret = FoamFieldFile.BoundariesDictionary(self, keywords)
95
+ elif len(keywords) == 2:
96
+ ret = FoamFieldFile.BoundaryDictionary(self, keywords)
97
+ return ret
98
+
99
+ def __setitem__(self, keywords: Union[str, Tuple[str, ...]], value: Any) -> None:
100
+ if not isinstance(keywords, tuple):
101
+ keywords = (keywords,)
102
+
103
+ if keywords == ("internalField",):
104
+ self._setitem(keywords, value, assume_field=True)
105
+ elif keywords == ("dimensions",):
106
+ self._setitem(keywords, value, assume_dimensions=True)
107
+ else:
108
+ self._setitem(keywords, value)
109
+
110
+ @property
111
+ def dimensions(self) -> FoamFile.DimensionSet:
112
+ """Alias of `self["dimensions"]`."""
113
+ ret = self["dimensions"]
114
+ if not isinstance(ret, FoamFile.DimensionSet):
115
+ raise TypeError("dimensions is not a DimensionSet")
116
+ return ret
117
+
118
+ @dimensions.setter
119
+ def dimensions(
120
+ self, value: Union[FoamFile.DimensionSet, Sequence[Union[int, float]]]
121
+ ) -> None:
122
+ self["dimensions"] = value
123
+
124
+ @property
125
+ def internal_field(
126
+ self,
127
+ ) -> Union[
128
+ int,
129
+ float,
130
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
131
+ "NDArray[np.generic]",
132
+ ]:
133
+ """Alias of `self["internalField"]`."""
134
+ ret = self["internalField"]
135
+ if not isinstance(ret, (int, float, Sequence)):
136
+ raise TypeError("internalField is not a field")
137
+ return cast(Union[int, float, Sequence[Union[int, float]]], ret)
138
+
139
+ @internal_field.setter
140
+ def internal_field(
141
+ self,
142
+ value: Union[
143
+ int,
144
+ float,
145
+ Sequence[Union[int, float, Sequence[Union[int, float]]]],
146
+ "NDArray[np.generic]",
147
+ ],
148
+ ) -> None:
149
+ self["internalField"] = value
150
+
151
+ @property
152
+ def boundary_field(self) -> "FoamFieldFile.BoundariesDictionary":
153
+ """Alias of `self["boundaryField"]`."""
154
+ ret = self["boundaryField"]
155
+ if not isinstance(ret, FoamFieldFile.BoundariesDictionary):
156
+ assert not isinstance(ret, FoamFile.Dictionary)
157
+ raise TypeError("boundaryField is not a dictionary")
158
+ return ret
@@ -0,0 +1,203 @@
1
+ import sys
2
+ from typing import (
3
+ Any,
4
+ Tuple,
5
+ Union,
6
+ )
7
+
8
+ if sys.version_info >= (3, 9):
9
+ from collections.abc import Iterator, Mapping, MutableMapping
10
+ else:
11
+ from typing import Iterator, Mapping, MutableMapping
12
+
13
+ from ._base import FoamDictionaryBase
14
+ from ._io import FoamFileIO
15
+ from ._serialization import serialize_entry
16
+
17
+
18
+ class FoamFile(
19
+ FoamDictionaryBase,
20
+ MutableMapping[
21
+ Union[str, Tuple[str, ...]], Union["FoamFile.Value", "FoamFile.Dictionary"]
22
+ ],
23
+ FoamFileIO,
24
+ ):
25
+ """
26
+ An OpenFOAM dictionary file.
27
+
28
+ Use as a mutable mapping (i.e., like a dict) to access and modify entries.
29
+
30
+ Use as a context manager to make multiple changes to the file while saving all changes only once at the end.
31
+ """
32
+
33
+ class Dictionary(
34
+ FoamDictionaryBase,
35
+ MutableMapping[str, Union["FoamFile.Value", "FoamFile.Dictionary"]],
36
+ ):
37
+ """An OpenFOAM dictionary within a file as a mutable mapping."""
38
+
39
+ def __init__(self, _file: "FoamFile", _keywords: Tuple[str, ...]) -> None:
40
+ self._file = _file
41
+ self._keywords = _keywords
42
+
43
+ def __getitem__(
44
+ self, keyword: str
45
+ ) -> Union["FoamFile.Value", "FoamFile.Dictionary"]:
46
+ return self._file[(*self._keywords, keyword)]
47
+
48
+ def _setitem(
49
+ self,
50
+ keyword: str,
51
+ value: Any,
52
+ *,
53
+ assume_field: bool = False,
54
+ assume_dimensions: bool = False,
55
+ ) -> None:
56
+ self._file._setitem(
57
+ (*self._keywords, keyword),
58
+ value,
59
+ assume_field=assume_field,
60
+ assume_dimensions=assume_dimensions,
61
+ )
62
+
63
+ def __setitem__(self, keyword: str, value: "FoamFile._SetValue") -> None:
64
+ self._setitem(keyword, value)
65
+
66
+ def __delitem__(self, keyword: str) -> None:
67
+ del self._file[(*self._keywords, keyword)]
68
+
69
+ def __iter__(self) -> Iterator[str]:
70
+ return self._file._iter(self._keywords)
71
+
72
+ def __contains__(self, keyword: object) -> bool:
73
+ return (*self._keywords, keyword) in self._file
74
+
75
+ def __len__(self) -> int:
76
+ return len(list(iter(self)))
77
+
78
+ def update(self, *args: Any, **kwargs: Any) -> None:
79
+ with self._file:
80
+ super().update(*args, **kwargs)
81
+
82
+ def clear(self) -> None:
83
+ with self._file:
84
+ super().clear()
85
+
86
+ def __repr__(self) -> str:
87
+ return f"{type(self).__qualname__}({self._file}, {self._keywords})"
88
+
89
+ def as_dict(self) -> FoamDictionaryBase._Dict:
90
+ """Return a nested dict representation of the dictionary."""
91
+ ret = self._file.as_dict()
92
+
93
+ for k in self._keywords:
94
+ assert isinstance(ret, dict)
95
+ v = ret[k]
96
+ assert isinstance(v, dict)
97
+ ret = v
98
+
99
+ return ret
100
+
101
+ def __getitem__(
102
+ self, keywords: Union[str, Tuple[str, ...]]
103
+ ) -> Union["FoamFile.Value", "FoamFile.Dictionary"]:
104
+ if not isinstance(keywords, tuple):
105
+ keywords = (keywords,)
106
+
107
+ _, parsed = self._read()
108
+
109
+ value = parsed[keywords]
110
+
111
+ if value is ...:
112
+ return FoamFile.Dictionary(self, keywords)
113
+ else:
114
+ return value # type: ignore [return-value]
115
+
116
+ def _setitem(
117
+ self,
118
+ keywords: Union[str, Tuple[str, ...]],
119
+ value: "FoamFile._SetValue",
120
+ *,
121
+ assume_field: bool = False,
122
+ assume_dimensions: bool = False,
123
+ ) -> None:
124
+ if not isinstance(keywords, tuple):
125
+ keywords = (keywords,)
126
+
127
+ contents, parsed = self._read()
128
+
129
+ if isinstance(value, Mapping):
130
+ with self:
131
+ if isinstance(value, FoamDictionaryBase):
132
+ value = value.as_dict()
133
+
134
+ start, end = parsed.entry_location(keywords, missing_ok=True)
135
+
136
+ self._write(
137
+ f"{contents[:start]}\n{serialize_entry(keywords[-1], {})}\n{contents[end:]}"
138
+ )
139
+
140
+ for k, v in value.items():
141
+ self[(*keywords, k)] = v
142
+ else:
143
+ start, end = parsed.entry_location(keywords, missing_ok=True)
144
+
145
+ self._write(
146
+ f"{contents[:start]}\n{serialize_entry(keywords[-1], value, assume_field=assume_field, assume_dimensions=assume_dimensions)}\n{contents[end:]}"
147
+ )
148
+
149
+ def __setitem__(
150
+ self,
151
+ keywords: Union[str, Tuple[str, ...]],
152
+ value: "FoamFile._SetValue",
153
+ ) -> None:
154
+ self._setitem(keywords, value)
155
+
156
+ def __delitem__(self, keywords: Union[str, Tuple[str, ...]]) -> None:
157
+ if not isinstance(keywords, tuple):
158
+ keywords = (keywords,)
159
+
160
+ contents, parsed = self._read()
161
+
162
+ start, end = parsed.entry_location(keywords)
163
+
164
+ self._write(contents[:start] + contents[end:])
165
+
166
+ def _iter(self, keywords: Union[str, Tuple[str, ...]] = ()) -> Iterator[str]:
167
+ if not isinstance(keywords, tuple):
168
+ keywords = (keywords,)
169
+
170
+ _, parsed = self._read()
171
+
172
+ yield from (k[-1] for k in parsed if k[:-1] == keywords)
173
+
174
+ def __iter__(self) -> Iterator[str]:
175
+ return self._iter()
176
+
177
+ def __contains__(self, keywords: object) -> bool:
178
+ if not isinstance(keywords, tuple):
179
+ keywords = (keywords,)
180
+ _, parsed = self._read()
181
+ return keywords in parsed
182
+
183
+ def __len__(self) -> int:
184
+ return len(list(iter(self)))
185
+
186
+ def update(self, *args: Any, **kwargs: Any) -> None:
187
+ with self:
188
+ super().update(*args, **kwargs)
189
+
190
+ def clear(self) -> None:
191
+ with self:
192
+ super().clear()
193
+
194
+ def __fspath__(self) -> str:
195
+ return str(self.path)
196
+
197
+ def __repr__(self) -> str:
198
+ return f"{type(self).__name__}({self.path})"
199
+
200
+ def as_dict(self) -> FoamDictionaryBase._Dict:
201
+ """Return a nested dict representation of the file."""
202
+ _, parsed = self._read()
203
+ return parsed.as_dict()
foamlib/_files/_io.py ADDED
@@ -0,0 +1,73 @@
1
+ import sys
2
+ from copy import deepcopy
3
+ from pathlib import Path
4
+ from types import TracebackType
5
+ from typing import (
6
+ Optional,
7
+ Tuple,
8
+ Type,
9
+ Union,
10
+ )
11
+
12
+ if sys.version_info >= (3, 11):
13
+ from typing import Self
14
+ else:
15
+ from typing_extensions import Self
16
+
17
+ from ._parsing import Parsed
18
+
19
+
20
+ class FoamFileIO:
21
+ def __init__(self, path: Union[str, Path]) -> None:
22
+ self.path = Path(path).absolute()
23
+ if self.path.is_dir():
24
+ raise IsADirectoryError(self.path)
25
+ elif not self.path.is_file():
26
+ raise FileNotFoundError(self.path)
27
+
28
+ self.__contents: Optional[str] = None
29
+ self.__parsed: Optional[Parsed] = None
30
+ self.__defer_io = 0
31
+ self.__dirty = False
32
+
33
+ def __enter__(self) -> Self:
34
+ if self.__defer_io == 0:
35
+ self._read()
36
+ self.__defer_io += 1
37
+ return self
38
+
39
+ def __exit__(
40
+ self,
41
+ exc_type: Optional[Type[BaseException]],
42
+ exc_val: Optional[BaseException],
43
+ exc_tb: Optional[TracebackType],
44
+ ) -> None:
45
+ self.__defer_io -= 1
46
+ if self.__defer_io == 0 and self.__dirty:
47
+ assert self.__contents is not None
48
+ self._write(self.__contents)
49
+ assert not self.__dirty
50
+
51
+ def _read(self) -> Tuple[str, Parsed]:
52
+ if not self.__defer_io:
53
+ contents = self.path.read_text()
54
+ if contents != self.__contents:
55
+ self.__contents = contents
56
+ self.__parsed = None
57
+
58
+ assert self.__contents is not None
59
+
60
+ if self.__parsed is None:
61
+ parsed = Parsed(self.__contents)
62
+ self.__parsed = parsed
63
+
64
+ return self.__contents, deepcopy(self.__parsed)
65
+
66
+ def _write(self, contents: str) -> None:
67
+ self.__contents = contents
68
+ self.__parsed = None
69
+ if not self.__defer_io:
70
+ self.path.write_text(contents)
71
+ self.__dirty = False
72
+ else:
73
+ self.__dirty = True
@@ -1,10 +1,10 @@
1
1
  import sys
2
- from typing import Optional, Tuple, Union
2
+ from typing import Tuple, Union
3
3
 
4
4
  if sys.version_info >= (3, 9):
5
- from collections.abc import Mapping, MutableMapping, Sequence
5
+ from collections.abc import Iterator, Mapping, MutableMapping, Sequence
6
6
  else:
7
- from typing import Mapping, MutableMapping, Sequence
7
+ from typing import Iterator, Mapping, MutableMapping, Sequence
8
8
 
9
9
  if sys.version_info >= (3, 10):
10
10
  from types import EllipsisType
@@ -33,15 +33,6 @@ from pyparsing import (
33
33
 
34
34
  from ._base import FoamDictionaryBase
35
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
36
 
46
37
  def _list_of(elem: ParserElement) -> ParserElement:
47
38
  return Opt(
@@ -83,6 +74,15 @@ def _dictionary_of(
83
74
  return Dict(Group(entry)[len], asdict=not located)
84
75
 
85
76
 
77
+ _SWITCH = (
78
+ Keyword("yes") | Keyword("true") | Keyword("on") | Keyword("y") | Keyword("t")
79
+ ).set_parse_action(lambda: True) | (
80
+ Keyword("no") | Keyword("false") | Keyword("off") | Keyword("n") | Keyword("f")
81
+ ).set_parse_action(lambda: False)
82
+ _DIMENSIONS = (
83
+ Literal("[").suppress() + common.number * 7 + Literal("]").suppress()
84
+ ).set_parse_action(lambda tks: FoamDictionaryBase.DimensionSet(*tks))
85
+
86
86
  _TENSOR = _list_of(common.number) | common.number
87
87
  _IDENTIFIER = Word(identbodychars + "$", printables.replace(";", ""))
88
88
  _DIMENSIONED = (Opt(_IDENTIFIER) + _DIMENSIONS + _TENSOR).set_parse_action(
@@ -110,89 +110,87 @@ _FILE = (
110
110
  .ignore(Literal("#include") + ... + LineEnd()) # type: ignore [no-untyped-call]
111
111
  )
112
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
113
 
177
- start = end
178
- else:
179
- raise
114
+ class Parsed(Mapping[Tuple[str, ...], Union[FoamDictionaryBase.Value, EllipsisType]]):
115
+ def __init__(self, contents: str) -> None:
116
+ self._parsed: MutableMapping[
117
+ Tuple[str, ...],
118
+ Tuple[int, Union[FoamDictionaryBase.Value, EllipsisType], int],
119
+ ] = {}
120
+ for parse_result in _FILE.parse_string(contents, parse_all=True):
121
+ self._parsed.update(self._flatten_result(parse_result))
122
+
123
+ @staticmethod
124
+ def _flatten_result(
125
+ parse_result: ParseResults, *, _keywords: Tuple[str, ...] = ()
126
+ ) -> Mapping[
127
+ Tuple[str, ...], Tuple[int, Union[FoamDictionaryBase.Value, EllipsisType], int]
128
+ ]:
129
+ ret: MutableMapping[
130
+ Tuple[str, ...],
131
+ Tuple[int, Union[FoamDictionaryBase.Value, EllipsisType], int],
132
+ ] = {}
133
+ start = parse_result.locn_start
134
+ assert isinstance(start, int)
135
+ item = parse_result.value
136
+ assert isinstance(item, Sequence)
137
+ end = parse_result.locn_end
138
+ assert isinstance(end, int)
139
+ key, *values = item
140
+ assert isinstance(key, str)
141
+ ret[(*_keywords, key)] = (start, ..., end)
142
+ for value in values:
143
+ if isinstance(value, ParseResults):
144
+ ret.update(Parsed._flatten_result(value, _keywords=(*_keywords, key)))
145
+ else:
146
+ ret[(*_keywords, key)] = (start, value, end)
147
+ return ret
148
+
149
+ def __getitem__(
150
+ self, keywords: Tuple[str, ...]
151
+ ) -> Union[FoamDictionaryBase.Value, EllipsisType]:
152
+ _, value, _ = self._parsed[keywords]
153
+ return value
154
+
155
+ def __contains__(self, keywords: object) -> bool:
156
+ return keywords in self._parsed
157
+
158
+ def __iter__(self) -> Iterator[Tuple[str, ...]]:
159
+ return iter(self._parsed)
160
+
161
+ def __len__(self) -> int:
162
+ return len(self._parsed)
163
+
164
+ def entry_location(
165
+ self, keywords: Tuple[str, ...], *, missing_ok: bool = False
166
+ ) -> Tuple[int, int]:
167
+ try:
168
+ start, _, end = self._parsed[keywords]
169
+ except KeyError:
170
+ if missing_ok:
171
+ if len(keywords) > 1:
172
+ _, _, end = self._parsed[keywords[:-1]]
173
+ end -= 1
174
+ else:
175
+ end = -1
176
+
177
+ start = end
178
+ else:
179
+ raise
180
180
 
181
- return start, end
181
+ return start, end
182
182
 
183
+ def as_dict(self) -> FoamDictionaryBase._Dict:
184
+ ret: FoamDictionaryBase._Dict = {}
185
+ for keywords, (_, value, _) in self._parsed.items():
186
+ r = ret
187
+ for k in keywords[:-1]:
188
+ assert isinstance(r, dict)
189
+ v = r[k]
190
+ assert isinstance(v, dict)
191
+ r = v
183
192
 
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
193
  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
194
+ r[keywords[-1]] = {} if value is ... else value # type: ignore [assignment]
197
195
 
198
- return ret
196
+ return ret
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foamlib
3
- Version: 0.2.9
3
+ Version: 0.2.10
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
@@ -0,0 +1,16 @@
1
+ foamlib/__init__.py,sha256=gNl0z905tzKAvIMYD8-SNPEEsHJmL-vCFFpXm4LXHxI,338
2
+ foamlib/_cases.py,sha256=BTbYqTjYgqzu7y3cHuhngSHdWEwy-Zn7rN71DWsw3dE,21018
3
+ foamlib/_util.py,sha256=PBTpBwt_j1GXASncSDZUR8pH2u_h8UyJXm8GeFKebTY,2552
4
+ foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ foamlib/_files/__init__.py,sha256=Gd3Zz5k4X8pGa5d_RjYfFyMrz663Y6tutMzvqbTKPR8,180
6
+ foamlib/_files/_base.py,sha256=0GkUmj268EmrGfB553JGLuNM6xq8DTIYK2NdN1rvFYU,1836
7
+ foamlib/_files/_fields.py,sha256=y_CpRp1aCFg2zguYbuzJA9RtyoRSu6bSUWauiR3vhxM,5106
8
+ foamlib/_files/_files.py,sha256=z9yHEqfUKCQkg0-RW21JUb3AWuznO3_8j1W2kR9B6qE,6097
9
+ foamlib/_files/_io.py,sha256=alxMyxQh0zb6BZYqomZwOo9dQujMpRuS5yfpIKBEnaM,1976
10
+ foamlib/_files/_parsing.py,sha256=v8RhsVUFOaw2v1EtLigDe-qNI993i_ZUAme6I2HGslg,6020
11
+ foamlib/_files/_serialization.py,sha256=tbg63f0Nro89iqN2fsEnRzVPoxOVxYDZA4H9YhUiGtw,3776
12
+ foamlib-0.2.10.dist-info/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
13
+ foamlib-0.2.10.dist-info/METADATA,sha256=_EMLC2XN4idlySBRDtbs74k7syYO_anh855_vC3F9lo,4651
14
+ foamlib-0.2.10.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
15
+ foamlib-0.2.10.dist-info/top_level.txt,sha256=ZdVYtetXGwPwyfL-WhlhbTFQGAwKX5P_gXxtH9JYFPI,8
16
+ foamlib-0.2.10.dist-info/RECORD,,
@@ -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,14 +0,0 @@
1
- foamlib/__init__.py,sha256=xipBAyZhJ9168-UsgVjKmQdIbtsqDB3a3sLY93n3sBY,344
2
- foamlib/_cases.py,sha256=SjDTVauOlcrtyvnt5uxWGSu2RYZ0lhKRmvMnuRnRuzI,20973
3
- foamlib/_util.py,sha256=PBTpBwt_j1GXASncSDZUR8pH2u_h8UyJXm8GeFKebTY,2552
4
- foamlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- foamlib/_dictionaries/__init__.py,sha256=vxGpA7uaEbJwKFGOYdLFR6R9jcUj_HW60YzZBMjNXRo,160
6
- foamlib/_dictionaries/_base.py,sha256=0GkUmj268EmrGfB553JGLuNM6xq8DTIYK2NdN1rvFYU,1836
7
- foamlib/_dictionaries/_files.py,sha256=P-FzR3Db6T_WQDh_RF07aLgyp0UUJxxD3i7_WBk1CMM,13048
8
- foamlib/_dictionaries/_parsing.py,sha256=kzpuY_KlvkyMXIamxVwVvYSMpMY5sXCbIZ2ARNdWJtg,5512
9
- foamlib/_dictionaries/_serialization.py,sha256=tbg63f0Nro89iqN2fsEnRzVPoxOVxYDZA4H9YhUiGtw,3776
10
- foamlib-0.2.9.dist-info/LICENSE.txt,sha256=5Dte9TUnLZzPRs4NQzl-Jc2-Ljd-t_v0ZR5Ng5r0UsY,35131
11
- foamlib-0.2.9.dist-info/METADATA,sha256=GAw9nlyxHbi7XxpJEIKIjLPal8j__tcNOaurCoG6PJQ,4650
12
- foamlib-0.2.9.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
13
- foamlib-0.2.9.dist-info/top_level.txt,sha256=ZdVYtetXGwPwyfL-WhlhbTFQGAwKX5P_gXxtH9JYFPI,8
14
- foamlib-0.2.9.dist-info/RECORD,,
File without changes
File without changes