_subprocess.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. # Copyright 2021 The HuggingFace Inc. team. All rights reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License
  14. """Contains utilities to easily handle subprocesses in `huggingface_hub`."""
  15. import os
  16. import subprocess
  17. import sys
  18. from collections.abc import Generator
  19. from contextlib import contextmanager
  20. from io import StringIO
  21. from pathlib import Path
  22. from typing import IO
  23. from .logging import get_logger
  24. logger = get_logger(__name__)
  25. @contextmanager
  26. def capture_output() -> Generator[StringIO, None, None]:
  27. """Capture output that is printed to terminal.
  28. Taken from https://stackoverflow.com/a/34738440
  29. Example:
  30. ```py
  31. >>> with capture_output() as output:
  32. ... print("hello world")
  33. >>> assert output.getvalue() == "hello world\n"
  34. ```
  35. """
  36. output = StringIO()
  37. previous_output = sys.stdout
  38. sys.stdout = output
  39. try:
  40. yield output
  41. finally:
  42. sys.stdout = previous_output
  43. def run_subprocess(
  44. command: str | list[str],
  45. folder: str | Path | None = None,
  46. check=True,
  47. **kwargs,
  48. ) -> subprocess.CompletedProcess:
  49. """
  50. Method to run subprocesses. Calling this will capture the `stderr` and `stdout`,
  51. please call `subprocess.run` manually in case you would like for them not to
  52. be captured.
  53. Args:
  54. command (`str` or `list[str]`):
  55. The command to execute as a string or list of strings.
  56. folder (`str`, *optional*):
  57. The folder in which to run the command. Defaults to current working
  58. directory (from `os.getcwd()`).
  59. check (`bool`, *optional*, defaults to `True`):
  60. Setting `check` to `True` will raise a `subprocess.CalledProcessError`
  61. when the subprocess has a non-zero exit code.
  62. kwargs (`dict[str]`):
  63. Keyword arguments to be passed to the `subprocess.run` underlying command.
  64. Returns:
  65. `subprocess.CompletedProcess`: The completed process.
  66. """
  67. if isinstance(command, str):
  68. command = command.split()
  69. if isinstance(folder, Path):
  70. folder = str(folder)
  71. return subprocess.run(
  72. command,
  73. capture_output=True,
  74. check=check,
  75. encoding="utf-8",
  76. errors="replace", # if not utf-8, replace char by �
  77. cwd=folder or os.getcwd(),
  78. **kwargs,
  79. )
  80. @contextmanager
  81. def run_interactive_subprocess(
  82. command: str | list[str],
  83. folder: str | Path | None = None,
  84. **kwargs,
  85. ) -> Generator[tuple[IO[str], IO[str]], None, None]:
  86. """Run a subprocess in an interactive mode in a context manager.
  87. Args:
  88. command (`str` or `list[str]`):
  89. The command to execute as a string or list of strings.
  90. folder (`str`, *optional*):
  91. The folder in which to run the command. Defaults to current working
  92. directory (from `os.getcwd()`).
  93. kwargs (`dict[str]`):
  94. Keyword arguments to be passed to the `subprocess.run` underlying command.
  95. Returns:
  96. `tuple[IO[str], IO[str]]`: A tuple with `stdin` and `stdout` to interact
  97. with the process (input and output are utf-8 encoded).
  98. Example:
  99. ```python
  100. with _interactive_subprocess("git credential-store get") as (stdin, stdout):
  101. # Write to stdin
  102. stdin.write("url=hf.co\nusername=obama\n".encode("utf-8"))
  103. stdin.flush()
  104. # Read from stdout
  105. output = stdout.read().decode("utf-8")
  106. ```
  107. """
  108. if isinstance(command, str):
  109. command = command.split()
  110. with subprocess.Popen(
  111. command,
  112. stdin=subprocess.PIPE,
  113. stdout=subprocess.PIPE,
  114. stderr=subprocess.STDOUT,
  115. encoding="utf-8",
  116. errors="replace", # if not utf-8, replace char by �
  117. cwd=folder or os.getcwd(),
  118. **kwargs,
  119. ) as process:
  120. assert process.stdin is not None, "subprocess is opened as subprocess.PIPE"
  121. assert process.stdout is not None, "subprocess is opened as subprocess.PIPE"
  122. yield process.stdin, process.stdout