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.
Files changed (52) hide show
  1. {foamlib-0.8.6 → foamlib-0.8.7}/PKG-INFO +2 -1
  2. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/__init__.py +1 -1
  3. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/_async.py +3 -0
  4. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/_run.py +55 -26
  5. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/_slurm.py +3 -3
  6. foamlib-0.8.7/foamlib/_cases/_subprocess.py +188 -0
  7. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/_sync.py +3 -0
  8. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/_util.py +41 -1
  9. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_files/_parsing.py +2 -2
  10. {foamlib-0.8.6 → foamlib-0.8.7}/pyproject.toml +1 -0
  11. foamlib-0.8.6/foamlib/_cases/_subprocess.py +0 -152
  12. {foamlib-0.8.6 → foamlib-0.8.7}/.devcontainer.json +0 -0
  13. {foamlib-0.8.6 → foamlib-0.8.7}/.dockerignore +0 -0
  14. {foamlib-0.8.6 → foamlib-0.8.7}/.git-blame-ignore-revs +0 -0
  15. {foamlib-0.8.6 → foamlib-0.8.7}/.github/dependabot.yml +0 -0
  16. {foamlib-0.8.6 → foamlib-0.8.7}/.github/workflows/ci.yml +0 -0
  17. {foamlib-0.8.6 → foamlib-0.8.7}/.github/workflows/docker.yml +0 -0
  18. {foamlib-0.8.6 → foamlib-0.8.7}/.github/workflows/dockerhub-description.yml +0 -0
  19. {foamlib-0.8.6 → foamlib-0.8.7}/.github/workflows/pypi-publish.yml +0 -0
  20. {foamlib-0.8.6 → foamlib-0.8.7}/.gitignore +0 -0
  21. {foamlib-0.8.6 → foamlib-0.8.7}/.readthedocs.yaml +0 -0
  22. {foamlib-0.8.6 → foamlib-0.8.7}/Dockerfile +0 -0
  23. {foamlib-0.8.6 → foamlib-0.8.7}/LICENSE.txt +0 -0
  24. {foamlib-0.8.6 → foamlib-0.8.7}/README.md +0 -0
  25. {foamlib-0.8.6 → foamlib-0.8.7}/benchmark.png +0 -0
  26. {foamlib-0.8.6 → foamlib-0.8.7}/docs/Makefile +0 -0
  27. {foamlib-0.8.6 → foamlib-0.8.7}/docs/cases.rst +0 -0
  28. {foamlib-0.8.6 → foamlib-0.8.7}/docs/conf.py +0 -0
  29. {foamlib-0.8.6 → foamlib-0.8.7}/docs/files.rst +0 -0
  30. {foamlib-0.8.6 → foamlib-0.8.7}/docs/index.rst +0 -0
  31. {foamlib-0.8.6 → foamlib-0.8.7}/docs/make.bat +0 -0
  32. {foamlib-0.8.6 → foamlib-0.8.7}/docs/ruff.toml +0 -0
  33. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/__init__.py +0 -0
  34. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_cases/_base.py +0 -0
  35. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_files/__init__.py +0 -0
  36. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_files/_files.py +0 -0
  37. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_files/_io.py +0 -0
  38. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_files/_serialization.py +0 -0
  39. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/_files/_types.py +0 -0
  40. {foamlib-0.8.6 → foamlib-0.8.7}/foamlib/py.typed +0 -0
  41. {foamlib-0.8.6 → foamlib-0.8.7}/logo.png +0 -0
  42. {foamlib-0.8.6 → foamlib-0.8.7}/tests/__init__.py +0 -0
  43. {foamlib-0.8.6 → foamlib-0.8.7}/tests/ruff.toml +0 -0
  44. {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_cases/__init__.py +0 -0
  45. {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_cases/test_cavity.py +0 -0
  46. {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_cases/test_cavity_async.py +0 -0
  47. {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_cases/test_flange.py +0 -0
  48. {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_cases/test_flange_async.py +0 -0
  49. {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_files/__init__.py +0 -0
  50. {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_files/test_dumps.py +0 -0
  51. {foamlib-0.8.6 → foamlib-0.8.7}/tests/test_files/test_files.py +0 -0
  52. {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.6
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'
@@ -1,6 +1,6 @@
1
1
  """A Python interface for interacting with OpenFOAM."""
2
2
 
3
- __version__ = "0.8.6"
3
+ __version__ = "0.8.7"
4
4
 
5
5
  from ._cases import (
6
6
  AsyncFoamCase,
@@ -99,6 +99,9 @@ class AsyncFoamCase(FoamCaseRunBase):
99
99
  cpus: int,
100
100
  **kwargs: Any,
101
101
  ) -> None:
102
+ if isinstance(cmd, str):
103
+ cmd = [*AsyncFoamCase._SHELL, cmd]
104
+
102
105
  async with AsyncFoamCase._cpus(cpus):
103
106
  await run_async(cmd, **kwargs)
104
107
 
@@ -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[bytes], int | IO[bytes]], None, None]:
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("ab") as stdout:
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
- "/bin/sh",
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 cpus >= 1:
35
- if isinstance(cmd, str):
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
+ )
@@ -58,6 +58,9 @@ class FoamCase(FoamCaseRunBase):
58
58
  cpus: int,
59
59
  **kwargs: Any,
60
60
  ) -> None:
61
+ if isinstance(cmd, str):
62
+ cmd = [*FoamCase._SHELL, cmd]
63
+
61
64
  run_sync(cmd, **kwargs)
62
65
 
63
66
  @staticmethod
@@ -2,7 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  import functools
4
4
  import sys
5
- from typing import TYPE_CHECKING, Any, AsyncContextManager, Callable, Generic, TypeVar
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() # type: ignore [no-untyped-call]
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() # type: ignore [no-untyped-call]
201
+ keyword_entry |= directive + data_entry + LineEnd().suppress()
202
202
 
203
203
  if located:
204
204
  keyword_entry = Located(keyword_entry)
@@ -34,6 +34,7 @@ dependencies = [
34
34
  "numpy>=1,<3",
35
35
  "pyparsing>=3.1.2,<4",
36
36
  "typing-extensions>=4,<5; python_version<'3.11'",
37
+ "rich>=13,<14",
37
38
  ]
38
39
 
39
40
  dynamic = ["version"]
@@ -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