foamlib 0.8.6__tar.gz → 0.8.7__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.
- {foamlib-0.8.6 → foamlib-0.8.7}/PKG-INFO +2 -1
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/__init__.py +1 -1
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/_async.py +3 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/_run.py +55 -26
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/_slurm.py +3 -3
- foamlib-0.8.7/foamlib/_cases/_subprocess.py +188 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/_sync.py +3 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/_util.py +41 -1
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_files/_parsing.py +2 -2
- {foamlib-0.8.6 → foamlib-0.8.7}/pyproject.toml +1 -0
- foamlib-0.8.6/foamlib/_cases/_subprocess.py +0 -152
- {foamlib-0.8.6 → foamlib-0.8.7}/.devcontainer.json +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/.dockerignore +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/.git-blame-ignore-revs +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/.github/dependabot.yml +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/.github/workflows/ci.yml +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/.github/workflows/docker.yml +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/.github/workflows/dockerhub-description.yml +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/.github/workflows/pypi-publish.yml +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/.gitignore +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/.readthedocs.yaml +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/Dockerfile +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/LICENSE.txt +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/README.md +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/benchmark.png +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/docs/Makefile +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/docs/cases.rst +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/docs/conf.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/docs/files.rst +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/docs/index.rst +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/docs/make.bat +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/docs/ruff.toml +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/__init__.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/_base.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_files/__init__.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_files/_files.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_files/_io.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_files/_serialization.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_files/_types.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/py.typed +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/logo.png +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/tests/__init__.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/tests/ruff.toml +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_cases/__init__.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_cases/test_cavity.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_cases/test_cavity_async.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_cases/test_flange.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_cases/test_flange_async.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_files/__init__.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_files/test_dumps.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_files/test_files.py +0 -0
- {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_files/test_parsing.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: foamlib
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.7
|
4
4
|
Summary: A Python interface for interacting with OpenFOAM
|
5
5
|
Project-URL: Homepage, https://github.com/gerlero/foamlib
|
6
6
|
Project-URL: Repository, https://github.com/gerlero/foamlib
|
@@ -29,6 +29,7 @@ Requires-Dist: aioshutil<2,>=1
|
|
29
29
|
Requires-Dist: numpy<3,>=1
|
30
30
|
Requires-Dist: numpy<3,>=1.25.0; python_version >= '3.10'
|
31
31
|
Requires-Dist: pyparsing<4,>=3.1.2
|
32
|
+
Requires-Dist: rich<14,>=13
|
32
33
|
Requires-Dist: typing-extensions<5,>=4; python_version < '3.11'
|
33
34
|
Provides-Extra: dev
|
34
35
|
Requires-Dist: mypy<2,>=1; extra == 'dev'
|
@@ -10,13 +10,16 @@ from contextlib import contextmanager
|
|
10
10
|
from pathlib import Path
|
11
11
|
from typing import IO, TYPE_CHECKING, Any
|
12
12
|
|
13
|
+
from rich.progress import Progress
|
14
|
+
|
15
|
+
from ._util import SingletonContextManager
|
16
|
+
|
13
17
|
if sys.version_info >= (3, 9):
|
14
18
|
from collections.abc import (
|
15
19
|
Callable,
|
16
20
|
Collection,
|
17
21
|
Coroutine,
|
18
22
|
Generator,
|
19
|
-
Mapping,
|
20
23
|
Sequence,
|
21
24
|
)
|
22
25
|
from collections.abc import Set as AbstractSet
|
@@ -27,7 +30,6 @@ else:
|
|
27
30
|
Collection,
|
28
31
|
Coroutine,
|
29
32
|
Generator,
|
30
|
-
Mapping,
|
31
33
|
Sequence,
|
32
34
|
)
|
33
35
|
|
@@ -68,6 +70,10 @@ class FoamCaseRunBase(FoamCaseBase):
|
|
68
70
|
|
69
71
|
return ret
|
70
72
|
|
73
|
+
_SHELL = ("bash", "-c")
|
74
|
+
|
75
|
+
__progress = SingletonContextManager(Progress)
|
76
|
+
|
71
77
|
def __delitem__(self, key: int | float | str) -> None:
|
72
78
|
shutil.rmtree(self[key].path)
|
73
79
|
|
@@ -255,38 +261,60 @@ class FoamCaseRunBase(FoamCaseBase):
|
|
255
261
|
|
256
262
|
return script
|
257
263
|
|
258
|
-
def __env(self, *, shell: bool) -> Mapping[str, str] | None:
|
259
|
-
sip_workaround = os.environ.get(
|
260
|
-
"FOAM_LD_LIBRARY_PATH", ""
|
261
|
-
) and not os.environ.get("DYLD_LIBRARY_PATH", "")
|
262
|
-
|
263
|
-
if not shell or sip_workaround:
|
264
|
-
env = os.environ.copy()
|
265
|
-
|
266
|
-
if not shell:
|
267
|
-
env["PWD"] = str(self.path)
|
268
|
-
|
269
|
-
if sip_workaround:
|
270
|
-
env["DYLD_LIBRARY_PATH"] = env["FOAM_LD_LIBRARY_PATH"]
|
271
|
-
|
272
|
-
return env
|
273
|
-
return None
|
274
|
-
|
275
264
|
@contextmanager
|
276
265
|
def __output(
|
277
266
|
self, cmd: Sequence[str | os.PathLike[str]] | str, *, log: bool
|
278
|
-
) -> Generator[tuple[int | IO[
|
267
|
+
) -> Generator[tuple[int | IO[str], int | IO[str]], None, None]:
|
279
268
|
if log:
|
280
269
|
if isinstance(cmd, str):
|
281
270
|
name = shlex.split(cmd)[0]
|
282
271
|
else:
|
283
272
|
name = Path(cmd[0]).name if isinstance(cmd[0], os.PathLike) else cmd[0]
|
284
273
|
|
285
|
-
with (self.path / f"log.{name}").open("
|
274
|
+
with (self.path / f"log.{name}").open("a") as stdout:
|
286
275
|
yield stdout, STDOUT
|
287
276
|
else:
|
288
277
|
yield DEVNULL, DEVNULL
|
289
278
|
|
279
|
+
@contextmanager
|
280
|
+
def __process_stdout(
|
281
|
+
self, cmd: Sequence[str | os.PathLike[str]] | str
|
282
|
+
) -> Generator[Callable[[str], None], None, None]:
|
283
|
+
if isinstance(cmd, str):
|
284
|
+
name = shlex.split(cmd)[0]
|
285
|
+
else:
|
286
|
+
name = Path(cmd[0]).name if isinstance(cmd[0], os.PathLike) else cmd[0]
|
287
|
+
|
288
|
+
try:
|
289
|
+
with self.control_dict as control_dict:
|
290
|
+
if control_dict["stopAt"] == "endTime":
|
291
|
+
control_dict_end_time = control_dict["endTime"]
|
292
|
+
if isinstance(control_dict_end_time, (int, float)):
|
293
|
+
end_time = control_dict_end_time
|
294
|
+
else:
|
295
|
+
end_time = None
|
296
|
+
else:
|
297
|
+
end_time = None
|
298
|
+
except (KeyError, FileNotFoundError):
|
299
|
+
end_time = None
|
300
|
+
|
301
|
+
with self.__progress as progress:
|
302
|
+
task = progress.add_task(f"({self.name}) Running {name}...", total=None)
|
303
|
+
|
304
|
+
def process_stdout(line: str) -> None:
|
305
|
+
if line.startswith("Time = "):
|
306
|
+
try:
|
307
|
+
time = float(line.split()[2])
|
308
|
+
except ValueError:
|
309
|
+
progress.update(task)
|
310
|
+
else:
|
311
|
+
progress.update(task, completed=time, total=end_time)
|
312
|
+
else:
|
313
|
+
progress.update(task)
|
314
|
+
|
315
|
+
yield process_stdout
|
316
|
+
progress.update(task, completed=1, total=1)
|
317
|
+
|
290
318
|
def __mkrundir(self) -> Path:
|
291
319
|
d = Path(os.environ["FOAM_RUN"], "foamlib")
|
292
320
|
d.mkdir(parents=True, exist_ok=True)
|
@@ -379,15 +407,16 @@ class FoamCaseRunBase(FoamCaseBase):
|
|
379
407
|
if cpus is None:
|
380
408
|
cpus = 1
|
381
409
|
|
382
|
-
with self.__output(cmd, log=log) as (stdout, stderr)
|
410
|
+
with self.__output(cmd, log=log) as (stdout, stderr), self.__process_stdout(
|
411
|
+
cmd
|
412
|
+
) as process_stdout:
|
383
413
|
if parallel:
|
384
414
|
if isinstance(cmd, str):
|
385
415
|
cmd = [
|
386
416
|
"mpiexec",
|
387
417
|
"-n",
|
388
418
|
str(cpus),
|
389
|
-
|
390
|
-
"-c",
|
419
|
+
*FoamCaseRunBase._SHELL,
|
391
420
|
f"{cmd} -parallel",
|
392
421
|
]
|
393
422
|
else:
|
@@ -396,11 +425,11 @@ class FoamCaseRunBase(FoamCaseBase):
|
|
396
425
|
yield self._run(
|
397
426
|
cmd,
|
398
427
|
cpus=cpus,
|
428
|
+
case=self,
|
399
429
|
check=check,
|
400
|
-
cwd=self.path,
|
401
|
-
env=self.__env(shell=isinstance(cmd, str)),
|
402
430
|
stdout=stdout,
|
403
431
|
stderr=stderr,
|
432
|
+
process_stdout=process_stdout,
|
404
433
|
**kwargs,
|
405
434
|
)
|
406
435
|
|
@@ -31,10 +31,10 @@ class AsyncSlurmFoamCase(AsyncFoamCase):
|
|
31
31
|
await AsyncFoamCase._run(cmd, cpus=cpus, **kwargs)
|
32
32
|
return
|
33
33
|
|
34
|
-
if
|
35
|
-
|
36
|
-
cmd = ["/bin/sh", "-c", cmd]
|
34
|
+
if isinstance(cmd, str):
|
35
|
+
cmd = [*AsyncSlurmFoamCase._SHELL, cmd]
|
37
36
|
|
37
|
+
if cpus >= 1:
|
38
38
|
if cpus == 1:
|
39
39
|
cmd = ["srun", *cmd]
|
40
40
|
|
@@ -0,0 +1,188 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import os
|
5
|
+
import selectors
|
6
|
+
import subprocess
|
7
|
+
import sys
|
8
|
+
from io import StringIO
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import IO
|
11
|
+
|
12
|
+
if sys.version_info >= (3, 9):
|
13
|
+
from collections.abc import Callable, Mapping, Sequence
|
14
|
+
else:
|
15
|
+
from typing import Callable, Mapping, Sequence
|
16
|
+
|
17
|
+
CompletedProcess = subprocess.CompletedProcess
|
18
|
+
|
19
|
+
|
20
|
+
class CalledProcessError(subprocess.CalledProcessError):
|
21
|
+
def __str__(self) -> str:
|
22
|
+
if self.stderr:
|
23
|
+
if isinstance(self.stderr, bytes):
|
24
|
+
return super().__str__() + "\n" + self.stderr.decode()
|
25
|
+
if isinstance(self.stderr, str):
|
26
|
+
return super().__str__() + "\n" + self.stderr
|
27
|
+
return super().__str__()
|
28
|
+
|
29
|
+
|
30
|
+
DEVNULL = subprocess.DEVNULL
|
31
|
+
PIPE = subprocess.PIPE
|
32
|
+
STDOUT = subprocess.STDOUT
|
33
|
+
|
34
|
+
|
35
|
+
def _env(case: os.PathLike[str]) -> Mapping[str, str]:
|
36
|
+
env = os.environ.copy()
|
37
|
+
|
38
|
+
env["PWD"] = str(Path(case))
|
39
|
+
|
40
|
+
if os.environ.get("FOAM_LD_LIBRARY_PATH", "") and not os.environ.get(
|
41
|
+
"DYLD_LIBRARY_PATH", ""
|
42
|
+
):
|
43
|
+
env["DYLD_LIBRARY_PATH"] = env["FOAM_LD_LIBRARY_PATH"]
|
44
|
+
|
45
|
+
return env
|
46
|
+
|
47
|
+
|
48
|
+
def run_sync(
|
49
|
+
cmd: Sequence[str | os.PathLike[str]],
|
50
|
+
*,
|
51
|
+
case: os.PathLike[str],
|
52
|
+
check: bool = True,
|
53
|
+
stdout: int | IO[str] = DEVNULL,
|
54
|
+
stderr: int | IO[str] = STDOUT,
|
55
|
+
process_stdout: Callable[[str], None] = lambda _: None,
|
56
|
+
) -> CompletedProcess[str]:
|
57
|
+
if sys.version_info < (3, 8):
|
58
|
+
cmd = [str(arg) for arg in cmd]
|
59
|
+
|
60
|
+
with subprocess.Popen(
|
61
|
+
cmd,
|
62
|
+
cwd=case,
|
63
|
+
env=_env(case),
|
64
|
+
stdout=PIPE,
|
65
|
+
stderr=PIPE,
|
66
|
+
text=True,
|
67
|
+
) as proc:
|
68
|
+
assert proc.stdout is not None
|
69
|
+
assert proc.stderr is not None
|
70
|
+
|
71
|
+
output = StringIO() if stdout is PIPE else None
|
72
|
+
error = StringIO()
|
73
|
+
|
74
|
+
if stderr is STDOUT:
|
75
|
+
stderr = stdout
|
76
|
+
|
77
|
+
with selectors.DefaultSelector() as selector:
|
78
|
+
selector.register(proc.stdout, selectors.EVENT_READ)
|
79
|
+
selector.register(proc.stderr, selectors.EVENT_READ)
|
80
|
+
open_streams = {proc.stdout, proc.stderr}
|
81
|
+
while open_streams:
|
82
|
+
for key, _ in selector.select():
|
83
|
+
assert key.fileobj in open_streams
|
84
|
+
line = key.fileobj.readline() # type: ignore [union-attr]
|
85
|
+
if not line:
|
86
|
+
selector.unregister(key.fileobj)
|
87
|
+
open_streams.remove(key.fileobj) # type: ignore [arg-type]
|
88
|
+
elif key.fileobj is proc.stdout:
|
89
|
+
process_stdout(line)
|
90
|
+
if output is not None:
|
91
|
+
output.write(line)
|
92
|
+
if stdout not in (DEVNULL, PIPE):
|
93
|
+
assert not isinstance(stdout, int)
|
94
|
+
stdout.write(line)
|
95
|
+
else:
|
96
|
+
assert key.fileobj is proc.stderr
|
97
|
+
error.write(line)
|
98
|
+
if stderr not in (DEVNULL, PIPE):
|
99
|
+
assert not isinstance(stderr, int)
|
100
|
+
stderr.write(line)
|
101
|
+
|
102
|
+
assert proc.returncode is not None
|
103
|
+
|
104
|
+
if check and proc.returncode != 0:
|
105
|
+
raise CalledProcessError(
|
106
|
+
returncode=proc.returncode,
|
107
|
+
cmd=cmd,
|
108
|
+
output=output.getvalue() if output is not None else None,
|
109
|
+
stderr=error.getvalue(),
|
110
|
+
)
|
111
|
+
|
112
|
+
return CompletedProcess(
|
113
|
+
cmd,
|
114
|
+
returncode=proc.returncode,
|
115
|
+
stdout=output.getvalue() if output is not None else None,
|
116
|
+
stderr=error.getvalue(),
|
117
|
+
)
|
118
|
+
|
119
|
+
|
120
|
+
async def run_async(
|
121
|
+
cmd: Sequence[str | os.PathLike[str]],
|
122
|
+
*,
|
123
|
+
case: os.PathLike[str],
|
124
|
+
check: bool = True,
|
125
|
+
stdout: int | IO[str] = DEVNULL,
|
126
|
+
stderr: int | IO[str] = STDOUT,
|
127
|
+
process_stdout: Callable[[str], None] = lambda _: None,
|
128
|
+
) -> CompletedProcess[str]:
|
129
|
+
if sys.version_info < (3, 8):
|
130
|
+
cmd = [str(arg) for arg in cmd]
|
131
|
+
|
132
|
+
proc = await asyncio.create_subprocess_exec(
|
133
|
+
*cmd,
|
134
|
+
cwd=case,
|
135
|
+
env=_env(case),
|
136
|
+
stdout=PIPE,
|
137
|
+
stderr=PIPE,
|
138
|
+
)
|
139
|
+
|
140
|
+
if stderr is STDOUT:
|
141
|
+
stderr = stdout
|
142
|
+
|
143
|
+
output = StringIO() if stdout is PIPE else None
|
144
|
+
error = StringIO()
|
145
|
+
|
146
|
+
async def tee_stdout() -> None:
|
147
|
+
while True:
|
148
|
+
assert proc.stdout is not None
|
149
|
+
line = (await proc.stdout.readline()).decode()
|
150
|
+
if not line:
|
151
|
+
break
|
152
|
+
process_stdout(line)
|
153
|
+
if output is not None:
|
154
|
+
output.write(line)
|
155
|
+
if stdout not in (DEVNULL, PIPE):
|
156
|
+
assert not isinstance(stdout, int)
|
157
|
+
stdout.write(line)
|
158
|
+
|
159
|
+
async def tee_stderr() -> None:
|
160
|
+
while True:
|
161
|
+
assert proc.stderr is not None
|
162
|
+
line = (await proc.stderr.readline()).decode()
|
163
|
+
if not line:
|
164
|
+
break
|
165
|
+
error.write(line)
|
166
|
+
if stderr not in (DEVNULL, PIPE):
|
167
|
+
assert not isinstance(stderr, int)
|
168
|
+
stderr.write(line)
|
169
|
+
|
170
|
+
await asyncio.gather(tee_stdout(), tee_stderr())
|
171
|
+
|
172
|
+
await proc.wait()
|
173
|
+
assert proc.returncode is not None
|
174
|
+
|
175
|
+
if check and proc.returncode != 0:
|
176
|
+
raise CalledProcessError(
|
177
|
+
returncode=proc.returncode,
|
178
|
+
cmd=cmd,
|
179
|
+
output=output.getvalue() if output is not None else None,
|
180
|
+
stderr=error.getvalue(),
|
181
|
+
)
|
182
|
+
|
183
|
+
return CompletedProcess(
|
184
|
+
cmd,
|
185
|
+
returncode=proc.returncode,
|
186
|
+
stdout=output.getvalue() if output is not None else None,
|
187
|
+
stderr=error.getvalue(),
|
188
|
+
)
|
@@ -2,7 +2,17 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import functools
|
4
4
|
import sys
|
5
|
-
|
5
|
+
import threading
|
6
|
+
from typing import (
|
7
|
+
TYPE_CHECKING,
|
8
|
+
Any,
|
9
|
+
AsyncContextManager,
|
10
|
+
Callable,
|
11
|
+
ContextManager,
|
12
|
+
Generic,
|
13
|
+
TypeVar,
|
14
|
+
cast,
|
15
|
+
)
|
6
16
|
|
7
17
|
if TYPE_CHECKING:
|
8
18
|
from types import TracebackType
|
@@ -54,3 +64,33 @@ def awaitableasynccontextmanager(
|
|
54
64
|
return _AwaitableAsyncContextManager(cm(*args, **kwargs))
|
55
65
|
|
56
66
|
return f
|
67
|
+
|
68
|
+
|
69
|
+
class SingletonContextManager(Generic[R]):
|
70
|
+
def __init__(self, factory: Callable[[], ContextManager[R]]) -> None:
|
71
|
+
self._factory = factory
|
72
|
+
self._users = 0
|
73
|
+
self._cm: ContextManager[R] | None = None
|
74
|
+
self._ret: R | None = None
|
75
|
+
self._lock = threading.Lock()
|
76
|
+
|
77
|
+
def __enter__(self) -> R:
|
78
|
+
with self._lock:
|
79
|
+
if self._users == 0:
|
80
|
+
self._cm = self._factory()
|
81
|
+
self._ret = self._cm.__enter__()
|
82
|
+
self._users += 1
|
83
|
+
return cast("R", self._ret)
|
84
|
+
|
85
|
+
def __exit__(
|
86
|
+
self,
|
87
|
+
exc_type: type[BaseException] | None,
|
88
|
+
exc_val: BaseException | None,
|
89
|
+
exc_tb: TracebackType | None,
|
90
|
+
) -> bool | None:
|
91
|
+
with self._lock:
|
92
|
+
self._users -= 1
|
93
|
+
if self._users == 0:
|
94
|
+
assert self._cm is not None
|
95
|
+
return self._cm.__exit__(exc_type, exc_val, exc_tb)
|
96
|
+
return False
|
@@ -167,7 +167,7 @@ def _dict_of(
|
|
167
167
|
|
168
168
|
if directive is not None:
|
169
169
|
assert data_entry is not None
|
170
|
-
keyword_entry |= directive + data_entry + LineEnd().suppress()
|
170
|
+
keyword_entry |= directive + data_entry + LineEnd().suppress()
|
171
171
|
|
172
172
|
if located:
|
173
173
|
keyword_entry = Located(keyword_entry)
|
@@ -198,7 +198,7 @@ def _keyword_entry_of(
|
|
198
198
|
|
199
199
|
if directive is not None:
|
200
200
|
assert data_entry is not None
|
201
|
-
keyword_entry |= directive + data_entry + LineEnd().suppress()
|
201
|
+
keyword_entry |= directive + data_entry + LineEnd().suppress()
|
202
202
|
|
203
203
|
if located:
|
204
204
|
keyword_entry = Located(keyword_entry)
|
@@ -1,152 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import asyncio
|
4
|
-
import subprocess
|
5
|
-
import sys
|
6
|
-
from io import BytesIO
|
7
|
-
from typing import IO, TYPE_CHECKING
|
8
|
-
|
9
|
-
if TYPE_CHECKING:
|
10
|
-
import os
|
11
|
-
|
12
|
-
if sys.version_info >= (3, 9):
|
13
|
-
from collections.abc import Mapping, Sequence
|
14
|
-
else:
|
15
|
-
from typing import Mapping, Sequence
|
16
|
-
|
17
|
-
CompletedProcess = subprocess.CompletedProcess
|
18
|
-
|
19
|
-
|
20
|
-
class CalledProcessError(subprocess.CalledProcessError):
|
21
|
-
def __str__(self) -> str:
|
22
|
-
if self.stderr:
|
23
|
-
if isinstance(self.stderr, bytes):
|
24
|
-
return super().__str__() + "\n" + self.stderr.decode()
|
25
|
-
if isinstance(self.stderr, str):
|
26
|
-
return super().__str__() + "\n" + self.stderr
|
27
|
-
return super().__str__()
|
28
|
-
|
29
|
-
|
30
|
-
DEVNULL = subprocess.DEVNULL
|
31
|
-
PIPE = subprocess.PIPE
|
32
|
-
STDOUT = subprocess.STDOUT
|
33
|
-
|
34
|
-
|
35
|
-
def run_sync(
|
36
|
-
cmd: Sequence[str | os.PathLike[str]] | str,
|
37
|
-
*,
|
38
|
-
check: bool = True,
|
39
|
-
cwd: os.PathLike[str] | None = None,
|
40
|
-
env: Mapping[str, str] | None = None,
|
41
|
-
stdout: int | IO[bytes] | None = None,
|
42
|
-
stderr: int | IO[bytes] | None = None,
|
43
|
-
) -> CompletedProcess[bytes]:
|
44
|
-
if not isinstance(cmd, str) and sys.version_info < (3, 8):
|
45
|
-
cmd = [str(arg) for arg in cmd]
|
46
|
-
|
47
|
-
proc = subprocess.Popen(
|
48
|
-
cmd,
|
49
|
-
cwd=cwd,
|
50
|
-
env=env,
|
51
|
-
stdout=stdout,
|
52
|
-
stderr=PIPE,
|
53
|
-
shell=isinstance(cmd, str),
|
54
|
-
)
|
55
|
-
|
56
|
-
if stderr == STDOUT:
|
57
|
-
stderr = stdout
|
58
|
-
if stderr not in (PIPE, DEVNULL):
|
59
|
-
stderr_copy = BytesIO()
|
60
|
-
|
61
|
-
assert not isinstance(stderr, int)
|
62
|
-
if stderr is None:
|
63
|
-
stderr = sys.stderr.buffer
|
64
|
-
|
65
|
-
assert proc.stderr is not None
|
66
|
-
for line in proc.stderr:
|
67
|
-
stderr.write(line)
|
68
|
-
stderr_copy.write(line)
|
69
|
-
|
70
|
-
output, _ = proc.communicate()
|
71
|
-
assert not _
|
72
|
-
error = stderr_copy.getvalue()
|
73
|
-
else:
|
74
|
-
output, error = proc.communicate()
|
75
|
-
|
76
|
-
assert proc.returncode is not None
|
77
|
-
|
78
|
-
if check and proc.returncode != 0:
|
79
|
-
raise CalledProcessError(
|
80
|
-
returncode=proc.returncode,
|
81
|
-
cmd=cmd,
|
82
|
-
output=output,
|
83
|
-
stderr=error,
|
84
|
-
)
|
85
|
-
|
86
|
-
return CompletedProcess(
|
87
|
-
cmd, returncode=proc.returncode, stdout=output, stderr=error
|
88
|
-
)
|
89
|
-
|
90
|
-
|
91
|
-
async def run_async(
|
92
|
-
cmd: Sequence[str | os.PathLike[str]] | str,
|
93
|
-
*,
|
94
|
-
check: bool = True,
|
95
|
-
cwd: os.PathLike[str] | None = None,
|
96
|
-
env: Mapping[str, str] | None = None,
|
97
|
-
stdout: int | IO[bytes] | None = None,
|
98
|
-
stderr: int | IO[bytes] | None = None,
|
99
|
-
) -> CompletedProcess[bytes]:
|
100
|
-
if isinstance(cmd, str):
|
101
|
-
proc = await asyncio.create_subprocess_shell(
|
102
|
-
cmd,
|
103
|
-
cwd=cwd,
|
104
|
-
env=env,
|
105
|
-
stdout=stdout,
|
106
|
-
stderr=PIPE,
|
107
|
-
)
|
108
|
-
|
109
|
-
else:
|
110
|
-
if sys.version_info < (3, 8):
|
111
|
-
cmd = [str(arg) for arg in cmd]
|
112
|
-
proc = await asyncio.create_subprocess_exec(
|
113
|
-
*cmd,
|
114
|
-
cwd=cwd,
|
115
|
-
env=env,
|
116
|
-
stdout=stdout,
|
117
|
-
stderr=PIPE,
|
118
|
-
)
|
119
|
-
|
120
|
-
if stderr == STDOUT:
|
121
|
-
stderr = stdout
|
122
|
-
if stderr not in (PIPE, DEVNULL):
|
123
|
-
stderr_copy = BytesIO()
|
124
|
-
|
125
|
-
assert not isinstance(stderr, int)
|
126
|
-
if stderr is None:
|
127
|
-
stderr = sys.stderr.buffer
|
128
|
-
|
129
|
-
assert proc.stderr is not None
|
130
|
-
async for line in proc.stderr:
|
131
|
-
stderr.write(line)
|
132
|
-
stderr_copy.write(line)
|
133
|
-
|
134
|
-
output, _ = await proc.communicate()
|
135
|
-
assert not _
|
136
|
-
error = stderr_copy.getvalue()
|
137
|
-
else:
|
138
|
-
output, error = await proc.communicate()
|
139
|
-
|
140
|
-
assert proc.returncode is not None
|
141
|
-
|
142
|
-
if check and proc.returncode != 0:
|
143
|
-
raise CalledProcessError(
|
144
|
-
returncode=proc.returncode,
|
145
|
-
cmd=cmd,
|
146
|
-
output=output,
|
147
|
-
stderr=error,
|
148
|
-
)
|
149
|
-
|
150
|
-
return CompletedProcess(
|
151
|
-
cmd, returncode=proc.returncode, stdout=output, stderr=error
|
152
|
-
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|