foamlib 0.3.16__tar.gz → 0.3.18__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {foamlib-0.3.16 → foamlib-0.3.18}/PKG-INFO +1 -1
  2. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib/__init__.py +8 -3
  3. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib/_cases/__init__.py +3 -0
  4. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib/_cases/_async.py +29 -2
  5. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib/_cases/_base.py +19 -0
  6. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib/_cases/_sync.py +23 -2
  7. foamlib-0.3.18/foamlib/_cases/_util.py +35 -0
  8. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib/_files/_files.py +129 -29
  9. foamlib-0.3.18/foamlib/_util.py +18 -0
  10. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib.egg-info/PKG-INFO +1 -1
  11. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib.egg-info/SOURCES.txt +1 -0
  12. foamlib-0.3.16/foamlib/_util.py +0 -119
  13. {foamlib-0.3.16 → foamlib-0.3.18}/LICENSE.txt +0 -0
  14. {foamlib-0.3.16 → foamlib-0.3.18}/README.md +0 -0
  15. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib/_files/__init__.py +0 -0
  16. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib/_files/_base.py +0 -0
  17. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib/_files/_io.py +0 -0
  18. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib/_files/_parsing.py +0 -0
  19. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib/_files/_serialization.py +0 -0
  20. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib/py.typed +0 -0
  21. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib.egg-info/dependency_links.txt +0 -0
  22. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib.egg-info/requires.txt +0 -0
  23. {foamlib-0.3.16 → foamlib-0.3.18}/foamlib.egg-info/top_level.txt +0 -0
  24. {foamlib-0.3.16 → foamlib-0.3.18}/pyproject.toml +0 -0
  25. {foamlib-0.3.16 → foamlib-0.3.18}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foamlib
3
- Version: 0.3.16
3
+ Version: 0.3.18
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
@@ -1,10 +1,15 @@
1
1
  """A Python interface for interacting with OpenFOAM."""
2
2
 
3
- __version__ = "0.3.16"
3
+ __version__ = "0.3.18"
4
4
 
5
- from ._cases import AsyncFoamCase, FoamCase, FoamCaseBase
5
+ from ._cases import (
6
+ AsyncFoamCase,
7
+ CalledProcessError,
8
+ CalledProcessWarning,
9
+ FoamCase,
10
+ FoamCaseBase,
11
+ )
6
12
  from ._files import FoamDict, FoamFieldFile, FoamFile
7
- from ._util import CalledProcessError, CalledProcessWarning
8
13
 
9
14
  __all__ = [
10
15
  "FoamCase",
@@ -1,9 +1,12 @@
1
1
  from ._async import AsyncFoamCase
2
2
  from ._base import FoamCaseBase
3
3
  from ._sync import FoamCase
4
+ from ._util import CalledProcessError, CalledProcessWarning
4
5
 
5
6
  __all__ = [
6
7
  "FoamCaseBase",
7
8
  "FoamCase",
8
9
  "AsyncFoamCase",
10
+ "CalledProcessError",
11
+ "CalledProcessWarning",
9
12
  ]
@@ -15,8 +15,9 @@ else:
15
15
 
16
16
  import aioshutil
17
17
 
18
- from .._util import run_process_async
18
+ from .._util import is_sequence
19
19
  from ._base import FoamCaseBase
20
+ from ._util import check_returncode
20
21
 
21
22
 
22
23
  class AsyncFoamCase(FoamCaseBase):
@@ -89,7 +90,33 @@ class AsyncFoamCase(FoamCaseBase):
89
90
  *,
90
91
  check: bool = True,
91
92
  ) -> None:
92
- await run_process_async(cmd, cwd=self.path, check=check)
93
+ if not is_sequence(cmd):
94
+ proc = await asyncio.create_subprocess_shell(
95
+ str(cmd),
96
+ cwd=self.path,
97
+ env=self._env(shell=True),
98
+ stdout=asyncio.subprocess.DEVNULL,
99
+ stderr=asyncio.subprocess.PIPE if check else asyncio.subprocess.DEVNULL,
100
+ )
101
+
102
+ else:
103
+ if sys.version_info < (3, 8):
104
+ cmd = (str(arg) for arg in cmd)
105
+ proc = await asyncio.create_subprocess_exec(
106
+ *cmd,
107
+ cwd=self.path,
108
+ env=self._env(shell=False),
109
+ stdout=asyncio.subprocess.DEVNULL,
110
+ stderr=asyncio.subprocess.PIPE if check else asyncio.subprocess.DEVNULL,
111
+ )
112
+
113
+ stdout, stderr = await proc.communicate()
114
+
115
+ assert stdout is None
116
+ assert proc.returncode is not None
117
+
118
+ if check:
119
+ check_returncode(proc.returncode, cmd, stderr.decode())
93
120
 
94
121
  async def run(
95
122
  self,
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import sys
2
3
  from pathlib import Path
3
4
  from typing import (
@@ -227,6 +228,24 @@ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
227
228
 
228
229
  return script
229
230
 
231
+ def _env(self, *, shell: bool) -> Optional[Mapping[str, str]]:
232
+ sip_workaround = os.environ.get(
233
+ "FOAM_LD_LIBRARY_PATH", ""
234
+ ) and not os.environ.get("DYLD_LIBRARY_PATH", "")
235
+
236
+ if not shell or sip_workaround:
237
+ env = os.environ.copy()
238
+
239
+ if not shell:
240
+ env["PWD"] = str(self.path)
241
+
242
+ if sip_workaround:
243
+ env["DYLD_LIBRARY_PATH"] = env["FOAM_LD_LIBRARY_PATH"]
244
+
245
+ return env
246
+ else:
247
+ return None
248
+
230
249
  def _run_cmds(
231
250
  self,
232
251
  cmd: Optional[Union[Sequence[Union[str, Path]], str, Path]] = None,
@@ -1,4 +1,5 @@
1
1
  import shutil
2
+ import subprocess
2
3
  import sys
3
4
  from pathlib import Path
4
5
  from typing import (
@@ -11,8 +12,9 @@ if sys.version_info >= (3, 9):
11
12
  else:
12
13
  from typing import Sequence
13
14
 
14
- from .._util import run_process
15
+ from .._util import is_sequence
15
16
  from ._base import FoamCaseBase
17
+ from ._util import check_returncode
16
18
 
17
19
 
18
20
  class FoamCase(FoamCaseBase):
@@ -55,7 +57,26 @@ class FoamCase(FoamCaseBase):
55
57
  *,
56
58
  check: bool = True,
57
59
  ) -> None:
58
- run_process(cmd, cwd=self.path, check=check)
60
+ shell = not is_sequence(cmd)
61
+
62
+ if sys.version_info < (3, 8):
63
+ if shell:
64
+ cmd = str(cmd)
65
+ else:
66
+ cmd = (str(arg) for arg in cmd)
67
+
68
+ proc = subprocess.run(
69
+ cmd,
70
+ cwd=self.path,
71
+ env=self._env(shell=shell),
72
+ stdout=subprocess.DEVNULL,
73
+ stderr=subprocess.PIPE if check else subprocess.DEVNULL,
74
+ text=True,
75
+ shell=shell,
76
+ )
77
+
78
+ if check:
79
+ check_returncode(proc.returncode, cmd, proc.stderr)
59
80
 
60
81
  def run(
61
82
  self,
@@ -0,0 +1,35 @@
1
+ import subprocess
2
+ import sys
3
+ from pathlib import Path
4
+ from typing import Optional, Union
5
+ from warnings import warn
6
+
7
+ if sys.version_info >= (3, 9):
8
+ from collections.abc import Sequence
9
+ else:
10
+ from typing import Sequence
11
+
12
+
13
+ class CalledProcessError(subprocess.CalledProcessError):
14
+ """Exception raised when a process fails and `check=True`."""
15
+
16
+ def __str__(self) -> str:
17
+ msg = super().__str__()
18
+ if self.stderr:
19
+ msg += f"\n{self.stderr}"
20
+ return msg
21
+
22
+
23
+ class CalledProcessWarning(Warning):
24
+ """Warning raised when a process prints to stderr and `check=True`."""
25
+
26
+
27
+ def check_returncode(
28
+ retcode: int,
29
+ cmd: Union[Sequence[Union[str, Path]], str, Path],
30
+ stderr: Optional[str],
31
+ ) -> None:
32
+ if retcode != 0:
33
+ raise CalledProcessError(retcode, cmd, None, stderr)
34
+ elif stderr:
35
+ warn(f"Command {cmd} printed to stderr.\n{stderr}", CalledProcessWarning)
@@ -6,6 +6,7 @@ if sys.version_info >= (3, 9):
6
6
  else:
7
7
  from typing import Iterator, Mapping, MutableMapping, Sequence
8
8
 
9
+ from .._util import is_sequence
9
10
  from ._base import FoamDict
10
11
  from ._io import FoamFileIO
11
12
  from ._serialization import Kind, dumpb
@@ -24,7 +25,7 @@ class FoamFile(
24
25
  FoamFileIO,
25
26
  ):
26
27
  """
27
- An OpenFOAM dictionary file.
28
+ An OpenFOAM data file.
28
29
 
29
30
  Use as a mutable mapping (i.e., like a dict) to access and modify entries.
30
31
 
@@ -88,6 +89,74 @@ class FoamFile(
88
89
 
89
90
  return ret
90
91
 
92
+ class Header(SubDict):
93
+ """The header of an OpenFOAM file."""
94
+
95
+ @property
96
+ def version(self) -> float:
97
+ """Alias of `self["version"]`."""
98
+ ret = self["version"]
99
+ if not isinstance(ret, float):
100
+ raise TypeError("version is not a float")
101
+ return ret
102
+
103
+ @version.setter
104
+ def version(self, data: float) -> None:
105
+ self["version"] = data
106
+
107
+ @property
108
+ def format(self) -> str:
109
+ """Alias of `self["format"]`."""
110
+ ret = self["format"]
111
+ if not isinstance(ret, str):
112
+ raise TypeError("format is not a string")
113
+ return ret
114
+
115
+ @format.setter
116
+ def format(self, data: str) -> None:
117
+ self["format"] = data
118
+
119
+ @property
120
+ def class_(self) -> str:
121
+ """Alias of `self["class"]`."""
122
+ ret = self["class"]
123
+ if not isinstance(ret, str):
124
+ raise TypeError("class is not a string")
125
+ return ret
126
+
127
+ @class_.setter
128
+ def class_(self, data: str) -> None:
129
+ self["class"] = data
130
+
131
+ @property
132
+ def location(self) -> str:
133
+ """Alias of `self["location"]`."""
134
+ ret = self["location"]
135
+ if not isinstance(ret, str):
136
+ raise TypeError("location is not a string")
137
+ return ret
138
+
139
+ @location.setter
140
+ def location(self, data: str) -> None:
141
+ self["location"] = data
142
+
143
+ @property
144
+ def object(self) -> str:
145
+ """Alias of `self["object"]`."""
146
+ ret = self["object"]
147
+ if not isinstance(ret, str):
148
+ raise TypeError("object is not a string")
149
+ return ret
150
+
151
+ @property
152
+ def header(self) -> "FoamFile.Header":
153
+ """Alias of `self["FoamFile"]`."""
154
+ ret = self["FoamFile"]
155
+ if not isinstance(ret, FoamFile.Header):
156
+ assert not isinstance(ret, FoamFile.SubDict)
157
+ raise TypeError("FoamFile is not a dictionary")
158
+ return ret
159
+
91
160
  def __getitem__(
92
161
  self, keywords: Union[str, Tuple[str, ...]]
93
162
  ) -> Union["FoamFile.Data", "FoamFile.SubDict"]:
@@ -99,7 +168,10 @@ class FoamFile(
99
168
  value = parsed[keywords]
100
169
 
101
170
  if value is ...:
102
- return FoamFile.SubDict(self, keywords)
171
+ if keywords == ("FoamFile",):
172
+ return FoamFile.Header(self, keywords)
173
+ else:
174
+ return FoamFile.SubDict(self, keywords)
103
175
  else:
104
176
  return value
105
177
 
@@ -115,23 +187,23 @@ class FoamFile(
115
187
  assume_field: bool = False,
116
188
  assume_dimensions: bool = False,
117
189
  ) -> None:
118
- if not isinstance(keywords, tuple):
119
- keywords = (keywords,)
120
-
121
- kind = Kind.DEFAULT
122
- if keywords == ("internalField",) or (
123
- len(keywords) == 3
124
- and keywords[0] == "boundaryField"
125
- and keywords[2] == "value"
126
- ):
127
- kind = Kind.BINARY_FIELD if self._binary else Kind.FIELD
128
- elif keywords == ("dimensions",):
129
- kind = Kind.DIMENSIONS
130
-
131
- contents, parsed = self._read()
132
-
133
- if isinstance(data, Mapping):
134
- with self:
190
+ with self:
191
+ if not isinstance(keywords, tuple):
192
+ keywords = (keywords,)
193
+
194
+ kind = Kind.DEFAULT
195
+ if keywords == ("internalField",) or (
196
+ len(keywords) == 3
197
+ and keywords[0] == "boundaryField"
198
+ and keywords[2] == "value"
199
+ ):
200
+ kind = Kind.BINARY_FIELD if self._binary else Kind.FIELD
201
+ elif keywords == ("dimensions",):
202
+ kind = Kind.DIMENSIONS
203
+
204
+ contents, parsed = self._read()
205
+
206
+ if isinstance(data, Mapping):
135
207
  if isinstance(data, FoamDict):
136
208
  data = data.as_dict()
137
209
 
@@ -147,16 +219,44 @@ class FoamFile(
147
219
 
148
220
  for k, v in data.items():
149
221
  self[(*keywords, k)] = v
150
- else:
151
- start, end = parsed.entry_location(keywords, missing_ok=True)
152
-
153
- self._write(
154
- contents[:start]
155
- + b"\n"
156
- + dumpb({keywords[-1]: data}, kind=kind)
157
- + b"\n"
158
- + contents[end:]
159
- )
222
+
223
+ elif not self and keywords[0] != "FoamFile":
224
+ self["FoamFile"] = {
225
+ "version": 2.0,
226
+ "format": "ascii",
227
+ "class": "dictionary",
228
+ "location": f'"{self.path.parent.name}"',
229
+ "object": self.path.name,
230
+ }
231
+ self[keywords] = data
232
+
233
+ elif (
234
+ kind == Kind.FIELD or kind == Kind.BINARY_FIELD
235
+ ) and self.header.class_ == "dictionary":
236
+ if not is_sequence(data):
237
+ class_ = "volScalarField"
238
+ elif (len(data) == 3 and not is_sequence(data[0])) or len(data[0]) == 3:
239
+ class_ = "volVectorField"
240
+ elif (len(data) == 6 and not is_sequence(data[0])) or len(data[0]) == 6:
241
+ class_ = "volSymmTensorField"
242
+ elif (len(data) == 9 and not is_sequence(data[0])) or len(data[0]) == 9:
243
+ class_ = "volTensorField"
244
+ else:
245
+ class_ = "volScalarField"
246
+
247
+ self.header.class_ = class_
248
+ self[keywords] = data
249
+
250
+ else:
251
+ start, end = parsed.entry_location(keywords, missing_ok=True)
252
+
253
+ self._write(
254
+ contents[:start]
255
+ + b"\n"
256
+ + dumpb({keywords[-1]: data}, kind=kind)
257
+ + b"\n"
258
+ + contents[end:]
259
+ )
160
260
 
161
261
  def __delitem__(self, keywords: Union[str, Tuple[str, ...]]) -> None:
162
262
  if not isinstance(keywords, tuple):
@@ -0,0 +1,18 @@
1
+ import sys
2
+ from typing import Any
3
+
4
+ if sys.version_info >= (3, 9):
5
+ from collections.abc import Sequence
6
+ else:
7
+ from typing import Sequence
8
+
9
+ if sys.version_info >= (3, 10):
10
+ from typing import TypeGuard
11
+ else:
12
+ from typing_extensions import TypeGuard
13
+
14
+
15
+ def is_sequence(
16
+ value: Any,
17
+ ) -> TypeGuard[Sequence[Any]]:
18
+ return isinstance(value, Sequence) and not isinstance(value, str)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foamlib
3
- Version: 0.3.16
3
+ Version: 0.3.18
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
@@ -13,6 +13,7 @@ foamlib/_cases/__init__.py
13
13
  foamlib/_cases/_async.py
14
14
  foamlib/_cases/_base.py
15
15
  foamlib/_cases/_sync.py
16
+ foamlib/_cases/_util.py
16
17
  foamlib/_files/__init__.py
17
18
  foamlib/_files/_base.py
18
19
  foamlib/_files/_files.py
@@ -1,119 +0,0 @@
1
- import asyncio
2
- import os
3
- import subprocess
4
- import sys
5
- from pathlib import Path
6
- from typing import Any, Optional, Union
7
- from warnings import warn
8
-
9
- if sys.version_info >= (3, 9):
10
- from collections.abc import Mapping, Sequence
11
- else:
12
- from typing import Mapping, Sequence
13
-
14
- if sys.version_info >= (3, 10):
15
- from typing import TypeGuard
16
- else:
17
- from typing_extensions import TypeGuard
18
-
19
-
20
- def is_sequence(
21
- value: Any,
22
- ) -> TypeGuard[Sequence[Any]]:
23
- return isinstance(value, Sequence) and not isinstance(value, str)
24
-
25
-
26
- class CalledProcessError(subprocess.CalledProcessError):
27
- """Exception raised when a process fails and `check=True`."""
28
-
29
- def __str__(self) -> str:
30
- msg = super().__str__()
31
- if self.stderr:
32
- msg += f"\n{self.stderr}"
33
- return msg
34
-
35
-
36
- class CalledProcessWarning(Warning):
37
- """Warning raised when a process prints to stderr and `check=True`."""
38
-
39
-
40
- def _check(
41
- retcode: int,
42
- cmd: Union[Sequence[Union[str, Path]], str, Path],
43
- stderr: Optional[str],
44
- ) -> None:
45
- if retcode != 0:
46
- raise CalledProcessError(retcode, cmd, None, stderr)
47
- elif stderr:
48
- warn(f"Command {cmd} printed to stderr.\n{stderr}", CalledProcessWarning)
49
-
50
-
51
- def _env(cwd: Optional[Union[str, Path]] = None) -> Optional[Mapping[str, str]]:
52
- if cwd is not None:
53
- env = os.environ.copy()
54
- env["PWD"] = str(cwd)
55
- return env
56
- else:
57
- return None
58
-
59
-
60
- def run_process(
61
- cmd: Union[Sequence[Union[str, Path]], str, Path],
62
- *,
63
- check: bool = True,
64
- cwd: Union[None, str, Path] = None,
65
- ) -> None:
66
- shell = not is_sequence(cmd)
67
-
68
- if sys.version_info < (3, 8):
69
- if shell:
70
- cmd = str(cmd)
71
- else:
72
- cmd = (str(arg) for arg in cmd)
73
-
74
- proc = subprocess.run(
75
- cmd,
76
- cwd=cwd,
77
- env=_env(cwd) if not shell else None,
78
- stdout=subprocess.DEVNULL,
79
- stderr=subprocess.PIPE if check else subprocess.DEVNULL,
80
- text=True,
81
- shell=shell,
82
- )
83
-
84
- if check:
85
- _check(proc.returncode, cmd, proc.stderr)
86
-
87
-
88
- async def run_process_async(
89
- cmd: Union[Sequence[Union[str, Path]], str, Path],
90
- *,
91
- check: bool = True,
92
- cwd: Union[None, str, Path] = None,
93
- ) -> None:
94
- if not is_sequence(cmd):
95
- proc = await asyncio.create_subprocess_shell(
96
- str(cmd),
97
- cwd=cwd,
98
- stdout=asyncio.subprocess.DEVNULL,
99
- stderr=asyncio.subprocess.PIPE if check else asyncio.subprocess.DEVNULL,
100
- )
101
-
102
- else:
103
- if sys.version_info < (3, 8):
104
- cmd = (str(arg) for arg in cmd)
105
- proc = await asyncio.create_subprocess_exec(
106
- *cmd,
107
- cwd=cwd,
108
- env=_env(cwd),
109
- stdout=asyncio.subprocess.DEVNULL,
110
- stderr=asyncio.subprocess.PIPE if check else asyncio.subprocess.DEVNULL,
111
- )
112
-
113
- stdout, stderr = await proc.communicate()
114
-
115
- assert stdout is None
116
- assert proc.returncode is not None
117
-
118
- if check:
119
- _check(proc.returncode, cmd, stderr.decode())
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes