fancy-subprocess 2.2__tar.gz → 2.4__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.
@@ -122,6 +122,7 @@ celerybeat.pid
122
122
  # Environments
123
123
  .env
124
124
  .venv
125
+ .just_venv_*
125
126
  env/
126
127
  venv/
127
128
  ENV/
@@ -0,0 +1,27 @@
1
+ default:
2
+ just --list --justfile "{{justfile()}}"
3
+
4
+ [private]
5
+ verify_with_impl python_minor_version $UV_PROJECT_ENVIRONMENT:
6
+ uv sync --python 3.{{python_minor_version}}
7
+
8
+ uv run -m mypy .
9
+ uv run ty check .
10
+ uv run ruff check --target-version py3{{python_minor_version}}
11
+ uv run ruff format --check --target-version py3{{python_minor_version}}
12
+
13
+ verify_with python_minor_version="14": (verify_with_impl python_minor_version ".just_venv_3_"+python_minor_version)
14
+
15
+ verify: (verify_with "10") (verify_with "11") (verify_with "12") (verify_with "13") (verify_with "14")
16
+
17
+ format:
18
+ uv run ruff check --select I --fix
19
+ uv run ruff format
20
+
21
+ build_no_verify:
22
+ uv build
23
+
24
+ build: verify build_no_verify
25
+
26
+ publish: build
27
+ uv publish
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fancy-subprocess
3
- Version: 2.2
3
+ Version: 2.4
4
4
  Summary: subprocess.run() with formatted output, detailed error messages and retry capabilities
5
5
  Project-URL: Homepage, https://github.com/petamas/python-fancy-subprocess
6
6
  Project-URL: Bug Tracker, https://github.com/petamas/python-fancy-subprocess/issues
@@ -17,8 +17,9 @@ Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
20
21
  Requires-Python: >=3.10
21
- Requires-Dist: ntstatus<2,>=1.1
22
+ Requires-Dist: ntstatus<3,>=2.0
22
23
  Requires-Dist: oslex<2,>=0.1.3
23
24
  Requires-Dist: pathext<2,>=1.5
24
25
  Requires-Dist: typeguard<5,>=4.4.2
@@ -40,7 +41,7 @@ Key differences compared to `subprocess.run()`:
40
41
  - The output of the command is always captured, but it is also immediately printed using `print_output`.
41
42
  - The exit code of the command is checked, and an exception is raised on failure, like `subprocess.run(check=True)`, but the list of exit codes treated as success is customizable, and the raised exception is `RunError` instead of `CalledProcessError`.
42
43
  - `OSError` is never raised, it gets converted to `RunError`.
43
- - `RunResult` is returned instead of `CompletedProcess` on success.
44
+ - `RunResult` is returned instead of `CompletedProcess` on success.
44
45
 
45
46
  Arguments (all of them except `cmd` are optional):
46
47
  - `cmd: Sequence[str | Path]` - Command to run. See `subprocess.run()`'s documentation for the interpretation of `cmd[0]`. It is recommended to use `fancy_subprocess.which()` to produce `cmd[0]`.
@@ -224,7 +225,7 @@ except fancy_subprocess.RunError as e:
224
225
  print(e)
225
226
  ```
226
227
 
227
- Running the script on Windows will produce the following output (-1072103376 is the signed integer interpretation of 0xC0190030, i.e. `STATUS_LOG_CORRUPTION_DETECTED`):
228
+ Running the script on Windows will produce the following output (-1072103376 is the signed integer interpretation of 0xC0190030, i.e. `STATUS_LOG_CORRUPTION_DETECTED`):
228
229
 
229
230
  ```
230
231
  Demonstrating failure...
@@ -282,6 +283,17 @@ Exception FileNotFoundError with message "[Errno 2] No such file or directory: '
282
283
 
283
284
  Calls `sys.stdout.reconfigure()` and `sys.stderr.reconfigure()` with the provided parameters. Raises `TypeError` if either `sys.stdout` or `sys.stderr` is not an instance of `io.TextIOWrapper`.
284
285
 
286
+ ### `fancy_subprocess.stringify_exit_code()`
287
+
288
+ Takes an exit code, and tries to format it in a way to help users understand what went wrong. If it succeeds, returns the explanation as a string. If not, returns `None`.
289
+
290
+ Some examples:
291
+ - On POSIX platforms, decodes signals to their string representation.
292
+ - On Windows, if the code matches an NTSTATUS error, it returns the name of the NTSTATUS.
293
+ - On Windows, if it is a 32-bit value, returns it as a hex number (because that's usually how you can find them on Google).
294
+
295
+ Used as part of the error message in `RunError`.
296
+
285
297
  ## Licensing
286
298
 
287
299
  This library is licensed under the MIT license.
@@ -13,7 +13,7 @@ Key differences compared to `subprocess.run()`:
13
13
  - The output of the command is always captured, but it is also immediately printed using `print_output`.
14
14
  - The exit code of the command is checked, and an exception is raised on failure, like `subprocess.run(check=True)`, but the list of exit codes treated as success is customizable, and the raised exception is `RunError` instead of `CalledProcessError`.
15
15
  - `OSError` is never raised, it gets converted to `RunError`.
16
- - `RunResult` is returned instead of `CompletedProcess` on success.
16
+ - `RunResult` is returned instead of `CompletedProcess` on success.
17
17
 
18
18
  Arguments (all of them except `cmd` are optional):
19
19
  - `cmd: Sequence[str | Path]` - Command to run. See `subprocess.run()`'s documentation for the interpretation of `cmd[0]`. It is recommended to use `fancy_subprocess.which()` to produce `cmd[0]`.
@@ -197,7 +197,7 @@ except fancy_subprocess.RunError as e:
197
197
  print(e)
198
198
  ```
199
199
 
200
- Running the script on Windows will produce the following output (-1072103376 is the signed integer interpretation of 0xC0190030, i.e. `STATUS_LOG_CORRUPTION_DETECTED`):
200
+ Running the script on Windows will produce the following output (-1072103376 is the signed integer interpretation of 0xC0190030, i.e. `STATUS_LOG_CORRUPTION_DETECTED`):
201
201
 
202
202
  ```
203
203
  Demonstrating failure...
@@ -255,6 +255,17 @@ Exception FileNotFoundError with message "[Errno 2] No such file or directory: '
255
255
 
256
256
  Calls `sys.stdout.reconfigure()` and `sys.stderr.reconfigure()` with the provided parameters. Raises `TypeError` if either `sys.stdout` or `sys.stderr` is not an instance of `io.TextIOWrapper`.
257
257
 
258
+ ### `fancy_subprocess.stringify_exit_code()`
259
+
260
+ Takes an exit code, and tries to format it in a way to help users understand what went wrong. If it succeeds, returns the explanation as a string. If not, returns `None`.
261
+
262
+ Some examples:
263
+ - On POSIX platforms, decodes signals to their string representation.
264
+ - On Windows, if the code matches an NTSTATUS error, it returns the name of the NTSTATUS.
265
+ - On Windows, if it is a 32-bit value, returns it as a hex number (because that's usually how you can find them on Google).
266
+
267
+ Used as part of the error message in `RunError`.
268
+
258
269
  ## Licensing
259
270
 
260
271
  This library is licensed under the MIT license.
@@ -1,4 +1,5 @@
1
1
  from fancy_subprocess._compat import *
2
+ from fancy_subprocess._exit_code import *
2
3
  from fancy_subprocess._print import *
3
4
  from fancy_subprocess._reconfigure import *
4
5
  from fancy_subprocess._run_core import *
@@ -6,13 +6,16 @@ __all__ = [
6
6
  'which',
7
7
  ]
8
8
 
9
- from pathext import checked_which, which
9
+ from pathext import checked_which
10
+ from pathext import which
10
11
 
11
- from fancy_subprocess._run_core import RunError, RunResult
12
+ from fancy_subprocess._run_core import RunError
13
+ from fancy_subprocess._run_core import RunResult
12
14
 
13
15
  RunProcessError = RunError
14
16
  RunProcessResult = RunResult
15
17
 
18
+
16
19
  def SILENCE(msg: str) -> None:
17
20
  """
18
21
  Helper function that takes a string, and does nothing with it. Meant to be passed as the print_message or print_output argument of run() and related functions to silence the corresponding output stream.
@@ -0,0 +1,53 @@
1
+ __all__ = [
2
+ 'stringify_exit_code',
3
+ ]
4
+
5
+ import sys
6
+ from typing import Optional
7
+
8
+ if sys.platform == 'win32':
9
+ from ntstatus import NtStatus
10
+ from ntstatus import NtStatusSeverity
11
+ from ntstatus import ThirtyTwoBits
12
+ else:
13
+ import signal
14
+
15
+ def _signal_name(signal_value: int) -> Optional[str]:
16
+ try:
17
+ return signal.Signals(signal_value).name
18
+ except ValueError:
19
+ return None
20
+
21
+
22
+ def stringify_exit_code(exit_code: int) -> Optional[str]:
23
+ if sys.platform == 'win32':
24
+ # Windows
25
+ if exit_code == 3:
26
+ # abort() results in exit code 3: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/abort
27
+ # While exit code 3 does not necessarily mean aborted (because applications may use it as a generic error code),
28
+ # it's common enough to be worth handling. "?" included to signal the uncertainty.
29
+ return 'aborted?'
30
+
31
+ if not ThirtyTwoBits.check(exit_code):
32
+ return None
33
+
34
+ try:
35
+ status = NtStatus.decode(exit_code)
36
+ if NtStatus.severity(status) != NtStatusSeverity.STATUS_SEVERITY_SUCCESS:
37
+ return status.name
38
+ except ValueError:
39
+ pass
40
+
41
+ return f'0x{ThirtyTwoBits(exit_code).unsigned_value:08X}'
42
+ else:
43
+ # POSIX
44
+ if exit_code < 0:
45
+ return _signal_name(-exit_code) or 'unknown signal'
46
+ elif exit_code == 126:
47
+ return 'COULD_NOT_EXECUTE'
48
+ elif exit_code == 127:
49
+ return 'COMMAND_NOT_FOUND'
50
+ elif exit_code in range(129, 160):
51
+ return _signal_name(exit_code - 128) or 'unknown signal'
52
+
53
+ return None
@@ -16,24 +16,29 @@ PrintFunction = Callable[[str], None]
16
16
 
17
17
  Indent = int | str
18
18
 
19
+
19
20
  def silenced_print(line: str) -> None:
20
21
  pass
21
22
 
23
+
22
24
  def indented_print(line: str, indent: Optional[Indent] = None) -> None:
23
25
  if indent is None:
24
- real_indent = 4*' '
26
+ real_indent = 4 * ' '
25
27
  elif isinstance(indent, int):
26
- real_indent = indent*' '
28
+ real_indent = indent * ' '
27
29
  else:
28
30
  real_indent = indent
29
31
 
30
32
  print(f'{real_indent}{line}', flush=True)
31
33
 
34
+
32
35
  def indented_print_factory(indent: Optional[Indent] = None) -> PrintFunction:
33
36
  return lambda line: indented_print(line, indent)
34
37
 
38
+
35
39
  def default_print(line: str) -> None:
36
40
  indented_print(line, indent='')
37
41
 
42
+
38
43
  def error_print(line: str) -> None:
39
44
  print(line, file=sys.stderr, flush=True)
@@ -4,10 +4,12 @@ __all__ = [
4
4
 
5
5
  import io
6
6
  import sys
7
- from typing import Optional, TypedDict
7
+ from typing import Optional
8
+ from typing import TypedDict
8
9
 
9
10
  from typing_extensions import Unpack
10
11
 
12
+
11
13
  class ReconfigureParams(TypedDict, total=False):
12
14
  encoding: Optional[str]
13
15
  errors: Optional[str]
@@ -15,6 +17,7 @@ class ReconfigureParams(TypedDict, total=False):
15
17
  line_buffering: Optional[bool]
16
18
  write_through: Optional[bool]
17
19
 
20
+
18
21
  def _reconfigure_standard_stream(stream: object, name: str, **kwargs: Unpack[ReconfigureParams]) -> None:
19
22
  if stream is None:
20
23
  raise TypeError(f'{name} is None')
@@ -24,6 +27,7 @@ def _reconfigure_standard_stream(stream: object, name: str, **kwargs: Unpack[Rec
24
27
 
25
28
  stream.reconfigure(**kwargs)
26
29
 
30
+
27
31
  def reconfigure_standard_output_streams(**kwargs: Unpack[ReconfigureParams]) -> None:
28
32
  """
29
33
  Calls `sys.stdout.reconfigure()` and `sys.stderr.reconfigure()` with the provided parameters. Raises `TypeError` if either `sys.stdout` or `sys.stderr` is not an instance of `io.TextIOWrapper`.
@@ -15,9 +15,17 @@ from typing import Optional
15
15
 
16
16
  from typing_extensions import Unpack
17
17
 
18
- from fancy_subprocess._print import default_print, PrintFunction, silenced_print
19
- from fancy_subprocess._run_param import AnyExitCode, check_run_params, RunParams, Success
20
- from fancy_subprocess._utils import oslex_join, stringify_exit_code, value_or
18
+ from fancy_subprocess._exit_code import stringify_exit_code
19
+ from fancy_subprocess._print import PrintFunction
20
+ from fancy_subprocess._print import default_print
21
+ from fancy_subprocess._print import silenced_print
22
+ from fancy_subprocess._run_param import AnyExitCode
23
+ from fancy_subprocess._run_param import RunParams
24
+ from fancy_subprocess._run_param import Success
25
+ from fancy_subprocess._run_param import check_run_params
26
+ from fancy_subprocess._utils import oslex_join
27
+ from fancy_subprocess._utils import value_or
28
+
21
29
 
22
30
  @dataclass(kw_only=True, frozen=True)
23
31
  class RunResult:
@@ -32,6 +40,7 @@ class RunResult:
32
40
  exit_code: int = 0
33
41
  output: str = ''
34
42
 
43
+
35
44
  @dataclass(kw_only=True, frozen=True)
36
45
  class RunError(Exception):
37
46
  """
@@ -96,6 +105,7 @@ class RunError(Exception):
96
105
  def __str__(self) -> str:
97
106
  return self.message
98
107
 
108
+
99
109
  def run(
100
110
  cmd: Sequence[str | Path],
101
111
  *,
@@ -113,7 +123,7 @@ def run(
113
123
  - The output of the command is always captured, but it is also immediately printed using `print_output`.
114
124
  - The exit code of the command is checked, and an exception is raised on failure, like `subprocess.run(check=True)`, but the list of exit codes treated as success is customizable, and the raised exception is `RunError` instead of `CalledProcessError`.
115
125
  - `OSError` is never raised, it gets converted to `RunError`.
116
- - `RunResult` is returned instead of `CompletedProcess` on success.
126
+ - `RunResult` is returned instead of `CompletedProcess` on success.
117
127
 
118
128
  Arguments (all of them except `cmd` are optional):
119
129
  - `cmd: Sequence[str | Path]` - Command to run. See `subprocess.run()`'s documentation for the interpretation of `cmd[0]`. It is recommended to use `fancy_subprocess.which()` to produce `cmd[0]`.
@@ -148,7 +158,7 @@ def run(
148
158
  success: Success = value_or(kwargs.get('success'), [0])
149
159
  flush_before_subprocess = value_or(kwargs.get('flush_before_subprocess'), True)
150
160
  trim_output_lines = value_or(kwargs.get('trim_output_lines'), True)
151
- max_output_size = value_or(kwargs.get('max_output_size'), 10*1000*1000)
161
+ max_output_size = value_or(kwargs.get('max_output_size'), 10 * 1000 * 1000)
152
162
  retry = value_or(kwargs.get('retry'), 0)
153
163
  retry_initial_sleep_seconds = value_or(kwargs.get('retry_initial_sleep_seconds'), 10)
154
164
  retry_backoff = value_or(kwargs.get('retry_backoff'), 2)
@@ -169,8 +179,8 @@ def run(
169
179
  print_output = value_or(print_output, default_print)
170
180
 
171
181
  env = dict(os.environ)
172
- if sys.platform=='win32':
173
- env.update((key.upper(), value) for key,value in env_overrides.items())
182
+ if sys.platform == 'win32':
183
+ env.update((key.upper(), value) for key, value in env_overrides.items())
174
184
  else:
175
185
  env.update(env_overrides)
176
186
 
@@ -183,8 +193,19 @@ def run(
183
193
 
184
194
  output = ''
185
195
  try:
186
- with subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, cwd=cwd, env=env, encoding=encoding, errors=errors) as proc:
187
- assert proc.stdout is not None # passing stdout=subprocess.PIPE guarantees this
196
+ with subprocess.Popen(
197
+ cmd,
198
+ stdin=subprocess.DEVNULL,
199
+ stdout=subprocess.PIPE,
200
+ stderr=subprocess.STDOUT,
201
+ text=True,
202
+ bufsize=1,
203
+ cwd=cwd,
204
+ env=env,
205
+ encoding=encoding,
206
+ errors=errors,
207
+ ) as proc:
208
+ assert proc.stdout is not None # passing stdout=subprocess.PIPE guarantees this
188
209
 
189
210
  for line in iter(proc.stdout.readline, ''):
190
211
  line = line.removesuffix('\n')
@@ -196,8 +217,8 @@ def run(
196
217
  print_output(line)
197
218
 
198
219
  output += line + '\n'
199
- if len(output)>max_output_size+1:
200
- output = output[-max_output_size-1:] # drop the beginning of the string
220
+ if len(output) > max_output_size + 1:
221
+ output = output[-max_output_size - 1 :] # drop the beginning of the string
201
222
 
202
223
  proc.wait()
203
224
  result = RunResult(exit_code=proc.returncode, output=output.removesuffix('\n'))
@@ -215,7 +236,7 @@ def run(
215
236
  return attempt_run()
216
237
  except RunError as e:
217
238
  print_message(str(e))
218
- if attempts_left!=1:
239
+ if attempts_left != 1:
219
240
  plural = 's'
220
241
  else:
221
242
  plural = ''
@@ -9,13 +9,16 @@ __all__ = [
9
9
  'Success',
10
10
  ]
11
11
 
12
- from collections.abc import Mapping, Sequence
12
+ from collections.abc import Mapping
13
+ from collections.abc import Sequence
13
14
  from pathlib import Path
14
- from typing import Optional, TypedDict
15
+ from typing import Optional
16
+ from typing import TypedDict
15
17
 
16
18
  import typeguard
17
19
  from typing_extensions import Unpack
18
20
 
21
+
19
22
  class AnyExitCode:
20
23
  """
21
24
  Use an instance of this class (eg. fancy_subprocess.ANY_EXIT_CODE) as the 'success' argument to make run() and related functions treat any exit code as success.
@@ -23,12 +26,14 @@ class AnyExitCode:
23
26
 
24
27
  pass
25
28
 
29
+
26
30
  ANY_EXIT_CODE = AnyExitCode()
27
31
 
28
32
  Success = Sequence[int] | AnyExitCode
29
33
 
30
34
  EnvOverrides = Mapping[str, str]
31
35
 
36
+
32
37
  class RunParams(TypedDict, total=False):
33
38
  message_quiet: Optional[bool]
34
39
  output_quiet: Optional[bool]
@@ -46,11 +51,14 @@ class RunParams(TypedDict, total=False):
46
51
  errors: Optional[str]
47
52
  replace_fffd_with_question_mark: Optional[bool]
48
53
 
54
+
49
55
  def check_run_params(**kwargs: Unpack[RunParams]) -> None:
50
56
  try:
51
57
  typeguard.check_type(kwargs, RunParams, collection_check_strategy=typeguard.CollectionCheckStrategy.ALL_ITEMS)
52
58
  except typeguard.TypeCheckError as e:
53
- raise ValueError(str(e)) from None # we don't wanna expose the stacktrace from typeguard to be able to replace it with another library if needed
59
+ # we don't wanna expose the stacktrace from typeguard to be able to replace it with another library if needed
60
+ raise ValueError(str(e)) from None
61
+
54
62
 
55
63
  def change_default_run_params(params: RunParams, **new_defaults: Unpack[RunParams]) -> None:
56
64
  check_run_params(**params)
@@ -59,7 +67,8 @@ def change_default_run_params(params: RunParams, **new_defaults: Unpack[RunParam
59
67
  for key in new_defaults.keys():
60
68
  if params.get(key) is None:
61
69
  # It's safe to ignore the TypedDict-related checks here because of the check_run_params() calls
62
- params[key] = new_defaults[key] # type: ignore[literal-required]
70
+ params[key] = new_defaults[key] # type: ignore[literal-required] # ty: ignore[invalid-key]
71
+
63
72
 
64
73
  def force_run_params(params: RunParams, **forced_values: Unpack[RunParams]) -> None:
65
74
  check_run_params(**params)
@@ -70,4 +79,4 @@ def force_run_params(params: RunParams, **forced_values: Unpack[RunParams]) -> N
70
79
  raise ValueError(f'Trying to override forced keyword parameter {key} is disallowed')
71
80
  else:
72
81
  # It's safe to ignore the TypedDict-related checks here because of the check_run_params() calls
73
- params[key] = forced_values[key] # type: ignore[literal-required]
82
+ params[key] = forced_values[key] # type: ignore[literal-required] # ty: ignore[invalid-key]
@@ -9,10 +9,16 @@ from typing import Optional
9
9
 
10
10
  from typing_extensions import Unpack
11
11
 
12
- from fancy_subprocess._print import Indent, indented_print_factory, PrintFunction, silenced_print
13
- from fancy_subprocess._run_core import run, RunResult
14
- from fancy_subprocess._run_param import check_run_params, force_run_params, RunParams
15
- from fancy_subprocess._utils import oslex_join
12
+ from fancy_subprocess._print import Indent
13
+ from fancy_subprocess._print import PrintFunction
14
+ from fancy_subprocess._print import indented_print_factory
15
+ from fancy_subprocess._print import silenced_print
16
+ from fancy_subprocess._run_core import RunResult
17
+ from fancy_subprocess._run_core import run
18
+ from fancy_subprocess._run_param import RunParams
19
+ from fancy_subprocess._run_param import check_run_params
20
+ from fancy_subprocess._run_param import force_run_params
21
+
16
22
 
17
23
  def run_silenced(
18
24
  cmd: Sequence[str | Path],
@@ -42,6 +48,7 @@ def run_silenced(
42
48
  **forwarded_args,
43
49
  )
44
50
 
51
+
45
52
  def run_indented(
46
53
  cmd: Sequence[str | Path],
47
54
  *,
@@ -0,0 +1,23 @@
1
+ __all__ = [
2
+ 'value_or',
3
+ ]
4
+
5
+ from collections.abc import Sequence
6
+ from pathlib import Path
7
+ from typing import TypeVar
8
+
9
+ import oslex
10
+
11
+ T = TypeVar('T')
12
+ U = TypeVar('U')
13
+
14
+
15
+ def value_or(value: T | None, default: U) -> T | U:
16
+ if value is None:
17
+ return default
18
+ else:
19
+ return value
20
+
21
+
22
+ def oslex_join(cmd: Sequence[str | Path]) -> str:
23
+ return oslex.join([str(arg) for arg in cmd])
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fancy-subprocess"
7
- version = "2.2"
7
+ version = "2.4"
8
8
  authors = [
9
9
  { name="Tamás PEREGI", email="petamas@gmail.com" },
10
10
  ]
@@ -17,6 +17,7 @@ classifiers = [
17
17
  "Programming Language :: Python :: 3.11",
18
18
  "Programming Language :: Python :: 3.12",
19
19
  "Programming Language :: Python :: 3.13",
20
+ "Programming Language :: Python :: 3.14",
20
21
  "License :: OSI Approved :: MIT License",
21
22
  "Operating System :: OS Independent",
22
23
  "Operating System :: MacOS",
@@ -26,7 +27,7 @@ classifiers = [
26
27
  license = "MIT"
27
28
  license-files = ["LICENSE"]
28
29
  dependencies = [
29
- "ntstatus>=1.1,<2",
30
+ "ntstatus>=2.0,<3",
30
31
  "oslex>=0.1.3,<2",
31
32
  "pathext>=1.5,<2",
32
33
  "typeguard>=4.4.2,<5",
@@ -36,3 +37,40 @@ dependencies = [
36
37
  [project.urls]
37
38
  "Homepage" = "https://github.com/petamas/python-fancy-subprocess"
38
39
  "Bug Tracker" = "https://github.com/petamas/python-fancy-subprocess/issues"
40
+
41
+ [dependency-groups]
42
+ dev = [
43
+ "mypy>=1.14.1,<1.15",
44
+ "ruff>=0.15.12,<0.16",
45
+ "ty>=0.0.34,<0.1",
46
+ ]
47
+
48
+
49
+ [tool.mypy]
50
+ strict = true
51
+
52
+
53
+ [tool.ruff]
54
+ line-length = 120
55
+
56
+ [tool.ruff.format]
57
+ quote-style = "single"
58
+
59
+ [tool.ruff.lint]
60
+ extend-select = [
61
+ "I", # isort
62
+ ]
63
+ isort.force-single-line = true
64
+
65
+ [tool.ruff.lint.per-file-ignores]
66
+ # Allow star imports in __init__.py files
67
+ "__init__.py" = [
68
+ "F403", # undefined-local-with-import-star
69
+ "F405", # undefined-local-with-import-star-usage
70
+ ]
71
+
72
+
73
+ [tool.ty]
74
+ rules.undefined-reveal = "ignore"
75
+ analysis.respect-type-ignore-comments = false
76
+ terminal.error-on-warning = true
@@ -0,0 +1,261 @@
1
+ version = 1
2
+ revision = 3
3
+ requires-python = ">=3.10"
4
+
5
+ [[package]]
6
+ name = "fancy-subprocess"
7
+ version = "2.4"
8
+ source = { editable = "." }
9
+ dependencies = [
10
+ { name = "ntstatus" },
11
+ { name = "oslex" },
12
+ { name = "pathext" },
13
+ { name = "typeguard" },
14
+ { name = "typing-extensions" },
15
+ ]
16
+
17
+ [package.dev-dependencies]
18
+ dev = [
19
+ { name = "mypy" },
20
+ { name = "ruff" },
21
+ { name = "ty" },
22
+ ]
23
+
24
+ [package.metadata]
25
+ requires-dist = [
26
+ { name = "ntstatus", specifier = ">=2.0,<3" },
27
+ { name = "oslex", specifier = ">=0.1.3,<2" },
28
+ { name = "pathext", specifier = ">=1.5,<2" },
29
+ { name = "typeguard", specifier = ">=4.4.2,<5" },
30
+ { name = "typing-extensions", specifier = ">=4.14,<5" },
31
+ ]
32
+
33
+ [package.metadata.requires-dev]
34
+ dev = [
35
+ { name = "mypy", specifier = ">=1.14.1,<1.15" },
36
+ { name = "ruff", specifier = ">=0.15.12,<0.16" },
37
+ { name = "ty", specifier = ">=0.0.34,<0.1" },
38
+ ]
39
+
40
+ [[package]]
41
+ name = "more-itertools"
42
+ version = "10.8.0"
43
+ source = { registry = "https://pypi.org/simple" }
44
+ sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" }
45
+ wheels = [
46
+ { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" },
47
+ ]
48
+
49
+ [[package]]
50
+ name = "mslex"
51
+ version = "1.3.0"
52
+ source = { registry = "https://pypi.org/simple" }
53
+ sdist = { url = "https://files.pythonhosted.org/packages/e0/97/7022667073c99a0fe028f2e34b9bf76b49a611afd21b02527fbfd92d4cd5/mslex-1.3.0.tar.gz", hash = "sha256:641c887d1d3db610eee2af37a8e5abda3f70b3006cdfd2d0d29dc0d1ae28a85d", size = 11583, upload-time = "2024-10-16T13:16:18.523Z" }
54
+ wheels = [
55
+ { url = "https://files.pythonhosted.org/packages/64/f2/66bd65ca0139675a0d7b18f0bada6e12b51a984e41a76dbe44761bf1b3ee/mslex-1.3.0-py3-none-any.whl", hash = "sha256:c7074b347201b3466fc077c5692fbce9b5f62a63a51f537a53fbbd02eff2eea4", size = 7820, upload-time = "2024-10-16T13:16:17.566Z" },
56
+ ]
57
+
58
+ [[package]]
59
+ name = "mypy"
60
+ version = "1.14.1"
61
+ source = { registry = "https://pypi.org/simple" }
62
+ dependencies = [
63
+ { name = "mypy-extensions" },
64
+ { name = "tomli", marker = "python_full_version < '3.11'" },
65
+ { name = "typing-extensions" },
66
+ ]
67
+ sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051, upload-time = "2024-12-30T16:39:07.335Z" }
68
+ wheels = [
69
+ { url = "https://files.pythonhosted.org/packages/9b/7a/87ae2adb31d68402da6da1e5f30c07ea6063e9f09b5e7cfc9dfa44075e74/mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb", size = 11211002, upload-time = "2024-12-30T16:37:22.435Z" },
70
+ { url = "https://files.pythonhosted.org/packages/e1/23/eada4c38608b444618a132be0d199b280049ded278b24cbb9d3fc59658e4/mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0", size = 10358400, upload-time = "2024-12-30T16:37:53.526Z" },
71
+ { url = "https://files.pythonhosted.org/packages/43/c9/d6785c6f66241c62fd2992b05057f404237deaad1566545e9f144ced07f5/mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d", size = 12095172, upload-time = "2024-12-30T16:37:50.332Z" },
72
+ { url = "https://files.pythonhosted.org/packages/c3/62/daa7e787770c83c52ce2aaf1a111eae5893de9e004743f51bfcad9e487ec/mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b", size = 12828732, upload-time = "2024-12-30T16:37:29.96Z" },
73
+ { url = "https://files.pythonhosted.org/packages/1b/a2/5fb18318a3637f29f16f4e41340b795da14f4751ef4f51c99ff39ab62e52/mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427", size = 13012197, upload-time = "2024-12-30T16:38:05.037Z" },
74
+ { url = "https://files.pythonhosted.org/packages/28/99/e153ce39105d164b5f02c06c35c7ba958aaff50a2babba7d080988b03fe7/mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f", size = 9780836, upload-time = "2024-12-30T16:37:19.726Z" },
75
+ { url = "https://files.pythonhosted.org/packages/da/11/a9422850fd506edbcdc7f6090682ecceaf1f87b9dd847f9df79942da8506/mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c", size = 11120432, upload-time = "2024-12-30T16:37:11.533Z" },
76
+ { url = "https://files.pythonhosted.org/packages/b6/9e/47e450fd39078d9c02d620545b2cb37993a8a8bdf7db3652ace2f80521ca/mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1", size = 10279515, upload-time = "2024-12-30T16:37:40.724Z" },
77
+ { url = "https://files.pythonhosted.org/packages/01/b5/6c8d33bd0f851a7692a8bfe4ee75eb82b6983a3cf39e5e32a5d2a723f0c1/mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8", size = 12025791, upload-time = "2024-12-30T16:36:58.73Z" },
78
+ { url = "https://files.pythonhosted.org/packages/f0/4c/e10e2c46ea37cab5c471d0ddaaa9a434dc1d28650078ac1b56c2d7b9b2e4/mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f", size = 12749203, upload-time = "2024-12-30T16:37:03.741Z" },
79
+ { url = "https://files.pythonhosted.org/packages/88/55/beacb0c69beab2153a0f57671ec07861d27d735a0faff135a494cd4f5020/mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1", size = 12885900, upload-time = "2024-12-30T16:37:57.948Z" },
80
+ { url = "https://files.pythonhosted.org/packages/a2/75/8c93ff7f315c4d086a2dfcde02f713004357d70a163eddb6c56a6a5eff40/mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae", size = 9777869, upload-time = "2024-12-30T16:37:33.428Z" },
81
+ { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668, upload-time = "2024-12-30T16:38:02.211Z" },
82
+ { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060, upload-time = "2024-12-30T16:37:46.131Z" },
83
+ { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167, upload-time = "2024-12-30T16:37:43.534Z" },
84
+ { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341, upload-time = "2024-12-30T16:37:36.249Z" },
85
+ { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991, upload-time = "2024-12-30T16:37:06.743Z" },
86
+ { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016, upload-time = "2024-12-30T16:37:15.02Z" },
87
+ { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097, upload-time = "2024-12-30T16:37:25.144Z" },
88
+ { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728, upload-time = "2024-12-30T16:38:08.634Z" },
89
+ { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965, upload-time = "2024-12-30T16:38:12.132Z" },
90
+ { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660, upload-time = "2024-12-30T16:38:17.342Z" },
91
+ { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198, upload-time = "2024-12-30T16:38:32.839Z" },
92
+ { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276, upload-time = "2024-12-30T16:38:20.828Z" },
93
+ { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905, upload-time = "2024-12-30T16:38:42.021Z" },
94
+ ]
95
+
96
+ [[package]]
97
+ name = "mypy-extensions"
98
+ version = "1.1.0"
99
+ source = { registry = "https://pypi.org/simple" }
100
+ sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
101
+ wheels = [
102
+ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
103
+ ]
104
+
105
+ [[package]]
106
+ name = "ntstatus"
107
+ version = "2.0"
108
+ source = { registry = "https://pypi.org/simple" }
109
+ sdist = { url = "https://files.pythonhosted.org/packages/d1/5d/e1caf3dc856bf17421707d43db640b25c51547a045966d08460feb4eab32/ntstatus-2.0.tar.gz", hash = "sha256:b5956961d3e723ed2e429b1817d74ab925ef9eea523089a5dbcc59e3185e7c3d", size = 263733, upload-time = "2025-06-11T13:00:59.396Z" }
110
+ wheels = [
111
+ { url = "https://files.pythonhosted.org/packages/18/39/b9608ead33d92d4ec7f8148744160c736efe625d18ff5f8d72068e79e93d/ntstatus-2.0-py3-none-any.whl", hash = "sha256:df4cecb8f6a4052ea01871f36c5445fb2e945e6ceee72c6369c1669e06de47d1", size = 260611, upload-time = "2025-06-11T13:00:58.038Z" },
112
+ ]
113
+
114
+ [[package]]
115
+ name = "oslex"
116
+ version = "0.1.3"
117
+ source = { registry = "https://pypi.org/simple" }
118
+ dependencies = [
119
+ { name = "mslex" },
120
+ ]
121
+ sdist = { url = "https://files.pythonhosted.org/packages/5e/a9/ebd426ee0ca59fb5ba8f0039c53989f4ca475f2dd9583b5719e2fb01602c/oslex-0.1.3.tar.gz", hash = "sha256:1ed4cd82c75df2a8bcb0da34400984183753933155d0c7d999fa533137685f2d", size = 4415, upload-time = "2023-07-17T18:33:20.319Z" }
122
+ wheels = [
123
+ { url = "https://files.pythonhosted.org/packages/8a/21/f9d1c6196533d5e43625a71244796d6ec2565318f2d91f6c10ec461222f1/oslex-0.1.3-py3-none-any.whl", hash = "sha256:71acb8a1d42ed78ddd213a1d3a628bbf837f758bd2999c91df7ce57972466bdf", size = 3546, upload-time = "2023-07-17T18:33:18.725Z" },
124
+ ]
125
+
126
+ [[package]]
127
+ name = "pathext"
128
+ version = "1.5"
129
+ source = { registry = "https://pypi.org/simple" }
130
+ dependencies = [
131
+ { name = "more-itertools" },
132
+ ]
133
+ sdist = { url = "https://files.pythonhosted.org/packages/09/b3/cd7d7282c1640300630e963b84b6e202862206f5ac24959015d83372e16c/pathext-1.5.tar.gz", hash = "sha256:cbfd6f697a4874be7ea37109dbf60f108d451b44bb3df290f1560c75fa16bb87", size = 6424, upload-time = "2025-06-08T18:52:26.159Z" }
134
+ wheels = [
135
+ { url = "https://files.pythonhosted.org/packages/d0/d0/aab254e2ba1e2a390261ef746c4129ef81b2d13289850380c5273026915d/pathext-1.5-py3-none-any.whl", hash = "sha256:3aedc261736dc714881676847dcc20ce2246b09d331ec09276b478477a469530", size = 7325, upload-time = "2025-06-08T18:52:24.718Z" },
136
+ ]
137
+
138
+ [[package]]
139
+ name = "ruff"
140
+ version = "0.15.12"
141
+ source = { registry = "https://pypi.org/simple" }
142
+ sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" }
143
+ wheels = [
144
+ { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" },
145
+ { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" },
146
+ { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" },
147
+ { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" },
148
+ { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" },
149
+ { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" },
150
+ { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" },
151
+ { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" },
152
+ { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" },
153
+ { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" },
154
+ { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" },
155
+ { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" },
156
+ { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" },
157
+ { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" },
158
+ { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" },
159
+ { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" },
160
+ { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" },
161
+ ]
162
+
163
+ [[package]]
164
+ name = "tomli"
165
+ version = "2.4.1"
166
+ source = { registry = "https://pypi.org/simple" }
167
+ sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
168
+ wheels = [
169
+ { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
170
+ { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
171
+ { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
172
+ { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
173
+ { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
174
+ { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
175
+ { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
176
+ { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
177
+ { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
178
+ { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
179
+ { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
180
+ { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
181
+ { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
182
+ { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
183
+ { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
184
+ { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
185
+ { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
186
+ { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
187
+ { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
188
+ { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
189
+ { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
190
+ { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
191
+ { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
192
+ { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
193
+ { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
194
+ { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
195
+ { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
196
+ { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
197
+ { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
198
+ { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
199
+ { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
200
+ { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
201
+ { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
202
+ { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
203
+ { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
204
+ { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
205
+ { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
206
+ { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
207
+ { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
208
+ { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
209
+ { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
210
+ { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
211
+ { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
212
+ { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
213
+ { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
214
+ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
215
+ ]
216
+
217
+ [[package]]
218
+ name = "ty"
219
+ version = "0.0.35"
220
+ source = { registry = "https://pypi.org/simple" }
221
+ sdist = { url = "https://files.pythonhosted.org/packages/4e/53/440e7b1212c4b0abbd4adb7aed93f4971aa1f8dca386ac5515930afa9172/ty-0.0.35.tar.gz", hash = "sha256:8375c240ab38138a19db07996c9808fb7a92047c1492e1ce587c2ef5112ad3a9", size = 5629237, upload-time = "2026-05-10T18:25:17.105Z" }
222
+ wheels = [
223
+ { url = "https://files.pythonhosted.org/packages/d4/84/19662ee881675815b7fafff940a365be1985730465afd9b75cb2edd5f8b3/ty-0.0.35-py3-none-linux_armv6l.whl", hash = "sha256:85ae1e59b9fb0b40e9d84fe61b29653c5f2f5e78b487ece371a7a38c20c781cf", size = 11198741, upload-time = "2026-05-10T18:24:49.378Z" },
224
+ { url = "https://files.pythonhosted.org/packages/62/df/7e5b6f83d85b4d2e5b72b5dceb388f440acc10679417bd46f829b9200fab/ty-0.0.35-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:709dbb7af4fcadb1196863c00b8791bbbbcc9dacbe15a0ff17f0af82b35d415b", size = 10948304, upload-time = "2026-05-10T18:24:58.246Z" },
225
+ { url = "https://files.pythonhosted.org/packages/59/94/72d7263aca055cde427f0ebcf08d6a74e5a5fee1d1e7fdd553696089cecb/ty-0.0.35-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2cb0877419ab0c8708b6925cb0c2800b263842bd3c425113f200538772f3a0cc", size = 10407413, upload-time = "2026-05-10T18:24:37.422Z" },
226
+ { url = "https://files.pythonhosted.org/packages/b6/23/fda6fae8a81ce0cb5f24cdfe63260e110c7af8844e31fa07d1e6e8ef0232/ty-0.0.35-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7afbcfc61904b7e82e7fe1a1db832a40d8f01e69dee1775f6594e552980536c", size = 10932614, upload-time = "2026-05-10T18:24:47.401Z" },
227
+ { url = "https://files.pythonhosted.org/packages/72/3d/b98d8d4aa1a5ed6daaf15864e838f605ca7b1e8b93b7e17b96ed4bc4dfed/ty-0.0.35-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b61498cc3e4178031c079951257fbdb209a891b4feb10ad6c40f615a51846f41", size = 10962982, upload-time = "2026-05-10T18:24:44.88Z" },
228
+ { url = "https://files.pythonhosted.org/packages/18/c4/2881aad71bf6fb2f8df17fc8e4bc89e904e54490a3ee747b5ef73f98ac85/ty-0.0.35-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:573b1eacda349fc8dba0d767b41631c3a6f66412363127c5bf2b1b40a1d898d2", size = 11476274, upload-time = "2026-05-10T18:24:42.4Z" },
229
+ { url = "https://files.pythonhosted.org/packages/34/0f/7717650adaeaddd23eea70470e2c26d3f0b9b18fdc7f26ec9552d6001f17/ty-0.0.35-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7209746158d6393c1040aa64b3ca29622e212ea7d8bae22ba50dbcbb4f96f0a", size = 12012027, upload-time = "2026-05-10T18:25:00.752Z" },
230
+ { url = "https://files.pythonhosted.org/packages/22/c9/1a16cb4aab6f4707d8f550772e91abc26d1c8870f19b5e2453ad10bb8209/ty-0.0.35-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4466a1470aa4418d49a9aa45d9da7de42033addd0a2837c5b2b0eb71d3c2bcd3", size = 11648894, upload-time = "2026-05-10T18:25:12.44Z" },
231
+ { url = "https://files.pythonhosted.org/packages/18/a1/a977c0e07e9f88db9c67f90c6342a4dc4422c8091fa07bf26521870687c5/ty-0.0.35-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb44bb742d52c309dcaa6598bcf4d82eb4bf1241b9e4940461e522e30093fe8b", size = 11560482, upload-time = "2026-05-10T18:25:05.172Z" },
232
+ { url = "https://files.pythonhosted.org/packages/d6/c1/a5fb11227d5cc4ac3f29a115d8c8bc817578e8ef6907d1e4c914ddbf45ee/ty-0.0.35-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:34b219250736c989b2670a03782c61315f523f3a2be37f1f90b1207e2212c188", size = 11718495, upload-time = "2026-05-10T18:24:54.12Z" },
233
+ { url = "https://files.pythonhosted.org/packages/3c/cb/e92e4317388b6d1fd821a46941b448a8a1ff0bf13e22147c5167d8fa1b00/ty-0.0.35-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:88e2ac497decc0940ef1a07571dee8a746112a93a09cdc7f8bca0099752e2e05", size = 10900815, upload-time = "2026-05-10T18:25:02.941Z" },
234
+ { url = "https://files.pythonhosted.org/packages/e9/4f/03bd87388a92567f262f35ac64e10d2be047d258f2dfcf1405f500fa2b90/ty-0.0.35-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:02cae51b53e6ec17d5d827ff1a3a76fd119705b56a92156e04399eda6e911596", size = 10998051, upload-time = "2026-05-10T18:25:14.68Z" },
235
+ { url = "https://files.pythonhosted.org/packages/b4/60/6edbc375ee6073973200096168f644e1081e5e55a7d42596826465b275de/ty-0.0.35-py3-none-musllinux_1_2_i686.whl", hash = "sha256:11871d730c9400d899ac0b9f3d660ed2e7e433377c8725549f8250a36a7f2620", size = 11148910, upload-time = "2026-05-10T18:24:51.842Z" },
236
+ { url = "https://files.pythonhosted.org/packages/4d/b1/a845d2066ed521c477450f436d4bd353d107e7c02dd6536a485944aaf892/ty-0.0.35-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ad0a2f0530d0933dcc99ad36ac556c63e384ea72ab9a18d23ad2e2c9fd61c73", size = 11671005, upload-time = "2026-05-10T18:24:56.223Z" },
237
+ { url = "https://files.pythonhosted.org/packages/73/81/1d5912a54fb66b2f95ac828ae61d422ef5afeae1263e4d231e40796c229f/ty-0.0.35-py3-none-win32.whl", hash = "sha256:0e25d63ec4ab116e7f6757e44d16ca9216bca679d19ecc36d119cf80faada61a", size = 10481096, upload-time = "2026-05-10T18:24:39.976Z" },
238
+ { url = "https://files.pythonhosted.org/packages/3b/36/1c7f8632bfec1c321f01581d4c940a3617b24bd3e8b37c8a7363d33fbfc4/ty-0.0.35-py3-none-win_amd64.whl", hash = "sha256:6a0a6d259f6f2f8f2f954c6f013d4e0b5eba68af6b353bf19a47d59ec254a3d5", size = 11555691, upload-time = "2026-05-10T18:25:07.792Z" },
239
+ { url = "https://files.pythonhosted.org/packages/7a/fb/59325221bce52f6e833d6865ce8360ef7d5e1e21151b38df6dc77c4327a7/ty-0.0.35-py3-none-win_arm64.whl", hash = "sha256:619c52c0fb2aa21961a848a1995135ad3b6d0a9aa54da0194e60f679cc200e13", size = 10925457, upload-time = "2026-05-10T18:25:10.352Z" },
240
+ ]
241
+
242
+ [[package]]
243
+ name = "typeguard"
244
+ version = "4.5.1"
245
+ source = { registry = "https://pypi.org/simple" }
246
+ dependencies = [
247
+ { name = "typing-extensions" },
248
+ ]
249
+ sdist = { url = "https://files.pythonhosted.org/packages/2b/e8/66e25efcc18542d58706ce4e50415710593721aae26e794ab1dec34fb66f/typeguard-4.5.1.tar.gz", hash = "sha256:f6f8ecbbc819c9bc749983cc67c02391e16a9b43b8b27f15dc70ed7c4a007274", size = 80121, upload-time = "2026-02-19T16:09:03.392Z" }
250
+ wheels = [
251
+ { url = "https://files.pythonhosted.org/packages/91/88/b55b3117287a8540b76dbdd87733808d4d01c8067a3b339408c250bb3600/typeguard-4.5.1-py3-none-any.whl", hash = "sha256:44d2bf329d49a244110a090b55f5f91aa82d9a9834ebfd30bcc73651e4a8cc40", size = 36745, upload-time = "2026-02-19T16:09:01.6Z" },
252
+ ]
253
+
254
+ [[package]]
255
+ name = "typing-extensions"
256
+ version = "4.15.0"
257
+ source = { registry = "https://pypi.org/simple" }
258
+ sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
259
+ wheels = [
260
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
261
+ ]
@@ -1,52 +0,0 @@
1
- __all__ = [
2
- 'value_or',
3
- ]
4
-
5
- import sys
6
- from collections.abc import Sequence
7
- from pathlib import Path
8
- from typing import Optional, TypeVar
9
-
10
- import oslex
11
- if sys.platform=='win32':
12
- from ntstatus import NtStatus, NtStatusSeverity, ThirtyTwoBits
13
- else:
14
- import signal
15
-
16
- T = TypeVar('T')
17
- U = TypeVar('U')
18
-
19
- def value_or(value: T | None, default: U) -> T | U:
20
- if value is None:
21
- return default
22
- else:
23
- return value
24
-
25
- def oslex_join(cmd: Sequence[str | Path]) -> str:
26
- return oslex.join([str(arg) for arg in cmd])
27
-
28
- def stringify_exit_code(exit_code: int) -> Optional[str]:
29
- if sys.platform=='win32':
30
- # Windows
31
- try:
32
- bits = ThirtyTwoBits(exit_code)
33
- except ValueError:
34
- return None
35
-
36
- try:
37
- code = NtStatus(bits)
38
- if code.severity!=NtStatusSeverity.STATUS_SEVERITY_SUCCESS:
39
- return code.name
40
- except ValueError:
41
- pass
42
-
43
- return f'0x{bits.unsigned_value:08X}'
44
- else:
45
- # POSIX
46
- if exit_code<0:
47
- try:
48
- return signal.Signals(-exit_code).name
49
- except ValueError:
50
- return 'unknown signal'
51
-
52
- return None
@@ -1,26 +0,0 @@
1
- import fancy_subprocess
2
- from typing import Unpack
3
-
4
- def grab_output(cmd: list[str], **kwargs: Unpack[fancy_subprocess.RunParams]) -> str:
5
- # Raises ValueError if there are unknown parameters in kwargs or if a keyword argument's type is incorrect
6
- fancy_subprocess.check_run_params(**kwargs)
7
-
8
- # Make a copy of keyword arguments to be edited
9
- forwarded_args = kwargs.copy()
10
- # Make sure nothing's printed, raise ValueError if caller tries to specify "output_quiet" or "message_quiet"
11
- fancy_subprocess.force_run_params(forwarded_args, message_quiet=True, output_quiet=True)
12
- # Handle encoding/decoding errors by replacing them with placeholder character by default, but allow callers to still customize behaviour
13
- fancy_subprocess.change_default_run_params(forwarded_args, errors='replace')
14
-
15
- # Run command, raise fancy_subprocess.RunError on failure
16
- result = fancy_subprocess.run(cmd, **forwarded_args)
17
-
18
- # Return combined stdout and stderr
19
- return result.output
20
-
21
- comspec = grab_output(['cmd', '/c', 'echo', '%COMSPEC%'])
22
- print(f'COMSPEC={comspec}')
23
-
24
- files = grab_output(['cmd', '/c', 'dirr', '/b', '/o:n'], retry=2) # "dir" intentionally misspelled as "dirr"
25
- print('Files in current directory:')
26
- print(files)
@@ -1,96 +0,0 @@
1
- version = 1
2
- revision = 1
3
- requires-python = ">=3.10"
4
-
5
- [[package]]
6
- name = "fancy-subprocess"
7
- version = "2.2"
8
- source = { editable = "." }
9
- dependencies = [
10
- { name = "ntstatus" },
11
- { name = "oslex" },
12
- { name = "pathext" },
13
- { name = "typeguard" },
14
- { name = "typing-extensions" },
15
- ]
16
-
17
- [package.metadata]
18
- requires-dist = [
19
- { name = "ntstatus", specifier = ">=1.1,<2" },
20
- { name = "oslex", specifier = ">=0.1.3,<2" },
21
- { name = "pathext", specifier = ">=1.5,<2" },
22
- { name = "typeguard", specifier = ">=4.4.2,<5" },
23
- { name = "typing-extensions", specifier = ">=4.14,<5" },
24
- ]
25
-
26
- [[package]]
27
- name = "more-itertools"
28
- version = "10.7.0"
29
- source = { registry = "https://pypi.org/simple" }
30
- sdist = { url = "https://files.pythonhosted.org/packages/ce/a0/834b0cebabbfc7e311f30b46c8188790a37f89fc8d756660346fe5abfd09/more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3", size = 127671 }
31
- wheels = [
32
- { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278 },
33
- ]
34
-
35
- [[package]]
36
- name = "mslex"
37
- version = "1.3.0"
38
- source = { registry = "https://pypi.org/simple" }
39
- sdist = { url = "https://files.pythonhosted.org/packages/e0/97/7022667073c99a0fe028f2e34b9bf76b49a611afd21b02527fbfd92d4cd5/mslex-1.3.0.tar.gz", hash = "sha256:641c887d1d3db610eee2af37a8e5abda3f70b3006cdfd2d0d29dc0d1ae28a85d", size = 11583 }
40
- wheels = [
41
- { url = "https://files.pythonhosted.org/packages/64/f2/66bd65ca0139675a0d7b18f0bada6e12b51a984e41a76dbe44761bf1b3ee/mslex-1.3.0-py3-none-any.whl", hash = "sha256:c7074b347201b3466fc077c5692fbce9b5f62a63a51f537a53fbbd02eff2eea4", size = 7820 },
42
- ]
43
-
44
- [[package]]
45
- name = "ntstatus"
46
- version = "1.1"
47
- source = { registry = "https://pypi.org/simple" }
48
- sdist = { url = "https://files.pythonhosted.org/packages/b5/99/41f6934b2dc1b02c6366b9b5d5211f3dc8eb08c9f9d8813e58fe4f09f9b4/ntstatus-1.1.tar.gz", hash = "sha256:542069b3d0d654a37b9e068caaf4d29c8e3f9c1a2a4991ed0ec8c7a858022ee2", size = 37824 }
49
- wheels = [
50
- { url = "https://files.pythonhosted.org/packages/a5/cc/c9493a2a64dd9cfa8edf554028571f29398cb6fc693e6f4919f762994768/ntstatus-1.1-py3-none-any.whl", hash = "sha256:303239b3286fc0529fc8fd2daad6465ab7f5b2e0bc6abfdf785d48fbab1f2a54", size = 36674 },
51
- ]
52
-
53
- [[package]]
54
- name = "oslex"
55
- version = "0.1.3"
56
- source = { registry = "https://pypi.org/simple" }
57
- dependencies = [
58
- { name = "mslex" },
59
- ]
60
- sdist = { url = "https://files.pythonhosted.org/packages/5e/a9/ebd426ee0ca59fb5ba8f0039c53989f4ca475f2dd9583b5719e2fb01602c/oslex-0.1.3.tar.gz", hash = "sha256:1ed4cd82c75df2a8bcb0da34400984183753933155d0c7d999fa533137685f2d", size = 4415 }
61
- wheels = [
62
- { url = "https://files.pythonhosted.org/packages/8a/21/f9d1c6196533d5e43625a71244796d6ec2565318f2d91f6c10ec461222f1/oslex-0.1.3-py3-none-any.whl", hash = "sha256:71acb8a1d42ed78ddd213a1d3a628bbf837f758bd2999c91df7ce57972466bdf", size = 3546 },
63
- ]
64
-
65
- [[package]]
66
- name = "pathext"
67
- version = "1.5"
68
- source = { registry = "https://pypi.org/simple" }
69
- dependencies = [
70
- { name = "more-itertools" },
71
- ]
72
- sdist = { url = "https://files.pythonhosted.org/packages/09/b3/cd7d7282c1640300630e963b84b6e202862206f5ac24959015d83372e16c/pathext-1.5.tar.gz", hash = "sha256:cbfd6f697a4874be7ea37109dbf60f108d451b44bb3df290f1560c75fa16bb87", size = 6424 }
73
- wheels = [
74
- { url = "https://files.pythonhosted.org/packages/d0/d0/aab254e2ba1e2a390261ef746c4129ef81b2d13289850380c5273026915d/pathext-1.5-py3-none-any.whl", hash = "sha256:3aedc261736dc714881676847dcc20ce2246b09d331ec09276b478477a469530", size = 7325 },
75
- ]
76
-
77
- [[package]]
78
- name = "typeguard"
79
- version = "4.4.3"
80
- source = { registry = "https://pypi.org/simple" }
81
- dependencies = [
82
- { name = "typing-extensions" },
83
- ]
84
- sdist = { url = "https://files.pythonhosted.org/packages/34/53/f701077a29ddf65ed4556119961ef517d767c07f15f6cdf0717ad985426b/typeguard-4.4.3.tar.gz", hash = "sha256:be72b9c85f322c20459b29060c5c099cd733d5886c4ee14297795e62b0c0d59b", size = 75072 }
85
- wheels = [
86
- { url = "https://files.pythonhosted.org/packages/5c/18/662e2a14fcdbbc9e7842ad801a7f9292fcd6cf7df43af94e59ac9c0da9af/typeguard-4.4.3-py3-none-any.whl", hash = "sha256:7d8b4a3d280257fd1aa29023f22de64e29334bda0b172ff1040f05682223795e", size = 34855 },
87
- ]
88
-
89
- [[package]]
90
- name = "typing-extensions"
91
- version = "4.14.0"
92
- source = { registry = "https://pypi.org/simple" }
93
- sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423 }
94
- wheels = [
95
- { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839 },
96
- ]
File without changes