dependence 1.0.4__py3-none-any.whl → 1.1.0__py3-none-any.whl

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.
dependence/_utilities.py CHANGED
@@ -1,9 +1,13 @@
1
+ from __future__ import annotations
2
+
1
3
  import functools
2
4
  import json
3
5
  import os
4
6
  import re
5
7
  import sys
6
8
  from collections import deque
9
+ from collections.abc import Container, Hashable, Iterable, MutableSet
10
+ from collections.abc import Set as AbstractSet
7
11
  from configparser import ConfigParser, SectionProxy
8
12
  from enum import Enum, auto
9
13
  from glob import iglob
@@ -17,21 +21,11 @@ from subprocess import DEVNULL, PIPE, CalledProcessError, list2cmdline, run
17
21
  from traceback import format_exception
18
22
  from typing import (
19
23
  IO,
20
- AbstractSet,
21
24
  Any,
22
25
  Callable,
23
- Container,
24
- Dict,
25
- Hashable,
26
- Iterable,
27
- List,
28
- MutableSet,
29
- Optional,
30
- Set,
31
- Tuple,
32
26
  TypedDict,
33
- Union,
34
27
  cast,
28
+ overload,
35
29
  )
36
30
  from warnings import warn
37
31
 
@@ -40,15 +34,72 @@ from jsonpointer import resolve_pointer # type: ignore
40
34
  from packaging.requirements import InvalidRequirement, Requirement
41
35
  from packaging.utils import canonicalize_name
42
36
 
43
- _BUILTIN_DISTRIBUTION_NAMES: Tuple[str] = ("distribute",)
37
+ _BUILTIN_DISTRIBUTION_NAMES: tuple[str] = ("distribute",)
44
38
  _UNSAFE_CHARACTERS_PATTERN: re.Pattern = re.compile("[^A-Za-z0-9.]+")
45
39
 
46
40
 
41
+ class DefinitionExistsError(Exception):
42
+ """
43
+ This error is raised when an attempt is made to redefine
44
+ a singleton class instance.
45
+ """
46
+
47
+
48
+ _module_locals: dict[str, Any] = locals()
49
+
50
+
51
+ class Undefined:
52
+ """
53
+ This class is intended to indicate that a parameter has not been passed
54
+ to a keyword argument in situations where `None` is to be used as a
55
+ meaningful value.
56
+
57
+ The `Undefined` class is a singleton, so only one instance of this class
58
+ is permitted: `sob.UNDEFINED`.
59
+ """
60
+
61
+ __module__ = "sob"
62
+
63
+ def __init__(self) -> None:
64
+ # Only one instance of `Undefined` is permitted, so initialization
65
+ # checks to make sure this is the first use.
66
+ if "UNDEFINED" in _module_locals:
67
+ message: str = f"{self!r} may only be instantiated once."
68
+ raise DefinitionExistsError(message)
69
+
70
+ def __repr__(self) -> str:
71
+ # Represent instances of this class using the qualified name for the
72
+ # constant `UNDEFINED`.
73
+ return "sob.UNDEFINED"
74
+
75
+ def __bool__(self) -> bool:
76
+ # `UNDEFINED` cast as a boolean is `False` (as with `None`)
77
+ return False
78
+
79
+ def __hash__(self) -> int:
80
+ return 0
81
+
82
+ def __eq__(self, other: object) -> bool:
83
+ # Another object is only equal to this if it shares the same id, since
84
+ # there should only be one instance of this class defined
85
+ return other is self
86
+
87
+ def __reduce__(self) -> tuple[Callable[[], Undefined], tuple]:
88
+ return _undefined, ()
89
+
90
+
91
+ UNDEFINED: Undefined = Undefined()
92
+
93
+
94
+ def _undefined() -> Undefined:
95
+ return UNDEFINED
96
+
97
+
47
98
  def iter_distinct(items: Iterable[Hashable]) -> Iterable:
48
99
  """
49
100
  Yield distinct elements, preserving order
50
101
  """
51
- visited: Set[Hashable] = set()
102
+ visited: set[Hashable] = set()
52
103
  item: Hashable
53
104
  for item in items:
54
105
  if item not in visited:
@@ -88,8 +139,9 @@ def iter_parse_delimited_values(
88
139
 
89
140
 
90
141
  def check_output(
91
- args: Tuple[str, ...],
92
- cwd: Union[str, Path] = "",
142
+ args: tuple[str, ...],
143
+ cwd: str | Path = "",
144
+ *,
93
145
  echo: bool = False,
94
146
  ) -> str:
95
147
  """
@@ -98,13 +150,13 @@ def check_output(
98
150
 
99
151
  Parameters:
100
152
 
101
- - command (Tuple[str, ...]): The command to run
153
+ - command (tuple[str, ...]): The command to run
102
154
  """
103
155
  if echo:
104
156
  if cwd:
105
- print("$", "cd", cwd, "&&", list2cmdline(args))
157
+ print("$", "cd", cwd, "&&", list2cmdline(args)) # noqa: T201
106
158
  else:
107
- print("$", list2cmdline(args))
159
+ print("$", list2cmdline(args)) # noqa: T201
108
160
  output: str = run(
109
161
  args,
110
162
  stdout=PIPE,
@@ -113,7 +165,7 @@ def check_output(
113
165
  cwd=cwd or None,
114
166
  ).stdout.decode("utf-8", errors="ignore")
115
167
  if echo:
116
- print(output)
168
+ print(output) # noqa: T201
117
169
  return output
118
170
 
119
171
 
@@ -164,13 +216,13 @@ def deprecated(message: str = "") -> Callable[..., Callable[..., Any]]:
164
216
  return decorating_function
165
217
 
166
218
 
167
- def split_dot(path: str) -> Tuple[str, ...]:
219
+ def split_dot(path: str) -> tuple[str, ...]:
168
220
  return tuple(path.split("."))
169
221
 
170
222
 
171
223
  def tuple_starts_with(
172
- a: Tuple[str, ...],
173
- b: Tuple[str, ...],
224
+ a: tuple[str, ...],
225
+ b: tuple[str, ...],
174
226
  ) -> bool:
175
227
  """
176
228
  Determine if tuple `a` starts with tuple `b`
@@ -179,18 +231,18 @@ def tuple_starts_with(
179
231
 
180
232
 
181
233
  def tuple_starts_with_any(
182
- a: Tuple[str, ...],
183
- bs: Tuple[Tuple[str, ...], ...],
234
+ a: tuple[str, ...],
235
+ bs: tuple[tuple[str, ...], ...],
184
236
  ) -> bool:
185
237
  """
186
238
  Determine if tuple `a` starts with any tuple in `bs`
187
239
  """
188
- b: Tuple[str, ...]
240
+ b: tuple[str, ...]
189
241
  return any(tuple_starts_with(a, b) for b in bs)
190
242
 
191
243
 
192
244
  def iter_find_qualified_lists(
193
- data: Union[Dict[str, Any], list],
245
+ data: dict[str, Any] | list,
194
246
  item_condition: Callable[[Any], bool],
195
247
  exclude_object_ids: AbstractSet[int] = frozenset(),
196
248
  ) -> Iterable[list]:
@@ -262,9 +314,8 @@ def iter_find_qualified_lists(
262
314
  if id(data) in exclude_object_ids:
263
315
  return
264
316
  if isinstance(data, dict):
265
- _key: str
266
317
  value: Any
267
- for _key, value in data.items():
318
+ for value in data.values():
268
319
  if isinstance(value, (list, dict)):
269
320
  yield from iter_find_qualified_lists(
270
321
  value, item_condition, exclude_object_ids
@@ -299,32 +350,72 @@ class ConfigurationFileType(Enum):
299
350
 
300
351
 
301
352
  @functools.lru_cache
302
- def get_configuration_file_type(path: str) -> ConfigurationFileType:
303
- if not os.path.isfile(path):
353
+ def get_configuration_file_type(
354
+ path: str | Path, default: Any = UNDEFINED
355
+ ) -> ConfigurationFileType:
356
+ if isinstance(path, str):
357
+ path = Path(path)
358
+ if not path.is_file():
304
359
  raise FileNotFoundError(path)
305
- basename: str = os.path.basename(path).lower()
360
+ basename: str = path.name.lower()
306
361
  if basename == "setup.cfg":
307
362
  return ConfigurationFileType.SETUP_CFG
308
- elif basename == "tox.ini":
363
+ if basename == "tox.ini":
309
364
  return ConfigurationFileType.TOX_INI
310
- elif basename == "pyproject.toml":
365
+ if basename == "pyproject.toml":
311
366
  return ConfigurationFileType.PYPROJECT_TOML
312
- elif basename.endswith(".txt"):
367
+ if basename.endswith(".txt"):
313
368
  return ConfigurationFileType.REQUIREMENTS_TXT
314
- elif basename.endswith(".toml"):
369
+ if basename.endswith(".toml"):
315
370
  return ConfigurationFileType.TOML
316
- else:
317
- raise ValueError(
318
- f"{path} is not a recognized type of configuration file."
371
+ if default is UNDEFINED:
372
+ message: str = (
373
+ f"{path!s} is not a recognized type of configuration file."
319
374
  )
375
+ raise ValueError(message)
376
+ return default
320
377
 
321
378
 
322
379
  def is_configuration_file(path: str) -> bool:
323
- try:
324
- get_configuration_file_type(path)
325
- except (FileNotFoundError, ValueError):
326
- return False
327
- return True
380
+ return get_configuration_file_type(path, default=None) is not None
381
+
382
+
383
+ @overload
384
+ def iter_configuration_files(path: str) -> Iterable[str]: ...
385
+
386
+
387
+ @overload
388
+ def iter_configuration_files(path: Path) -> Iterable[Path]: ...
389
+
390
+
391
+ def iter_configuration_files(path: str | Path) -> Iterable[Path | str]:
392
+ """
393
+ Iterate over the project configuration files for the given path.
394
+ If the path is a file path—yields only that path. If the path is a
395
+ directory, yields all configuration files in that directory.
396
+ """
397
+ if os.path.exists(path):
398
+ if os.path.isdir(path):
399
+ child: Path
400
+ for child in filter(
401
+ Path.is_file,
402
+ (
403
+ path.iterdir()
404
+ if isinstance(path, Path)
405
+ else Path(path).iterdir()
406
+ ),
407
+ ):
408
+ if (
409
+ get_configuration_file_type(child, default=None)
410
+ is not None
411
+ ):
412
+ yield (
413
+ child
414
+ if isinstance(path, Path)
415
+ else str(child.absolute())
416
+ )
417
+ elif get_configuration_file_type(path, default=None) is not None:
418
+ yield path
328
419
 
329
420
 
330
421
  class _EditablePackageMetadata(TypedDict):
@@ -333,7 +424,7 @@ class _EditablePackageMetadata(TypedDict):
333
424
  editable_project_location: str
334
425
 
335
426
 
336
- def _iter_editable_distribution_locations() -> Iterable[Tuple[str, str]]:
427
+ def _iter_editable_distribution_locations() -> Iterable[tuple[str, str]]:
337
428
  metadata: _EditablePackageMetadata
338
429
  for metadata in json.loads(
339
430
  check_output(
@@ -354,7 +445,7 @@ def _iter_editable_distribution_locations() -> Iterable[Tuple[str, str]]:
354
445
 
355
446
 
356
447
  @functools.lru_cache
357
- def get_editable_distributions_locations() -> Dict[str, str]:
448
+ def get_editable_distributions_locations() -> dict[str, str]:
358
449
  """
359
450
  Get a mapping of (normalized) editable distribution names to their
360
451
  locations.
@@ -384,12 +475,12 @@ def refresh_editable_distributions() -> None:
384
475
 
385
476
 
386
477
  @functools.lru_cache
387
- def get_installed_distributions() -> Dict[str, Distribution]:
478
+ def get_installed_distributions() -> dict[str, Distribution]:
388
479
  """
389
480
  Return a dictionary of installed distributions.
390
481
  """
391
482
  refresh_editable_distributions()
392
- installed: Dict[str, Distribution] = {}
483
+ installed: dict[str, Distribution] = {}
393
484
  for distribution in _get_distributions():
394
485
  installed[normalize_name(distribution.metadata["Name"])] = distribution
395
486
  return installed
@@ -425,7 +516,7 @@ def is_requirement_string(requirement_string: str) -> bool:
425
516
 
426
517
 
427
518
  def _iter_file_requirement_strings(path: str) -> Iterable[str]:
428
- lines: List[str]
519
+ lines: list[str]
429
520
  requirement_file_io: IO[str]
430
521
  with open(path) as requirement_file_io:
431
522
  lines = requirement_file_io.readlines()
@@ -459,7 +550,7 @@ def _iter_setup_cfg_requirement_strings(path: str) -> Iterable[str]:
459
550
 
460
551
 
461
552
  def _iter_tox_ini_requirement_strings(
462
- path: Union[str, Path, ConfigParser] = "",
553
+ path: str | Path | ConfigParser = "",
463
554
  string: str = "",
464
555
  ) -> Iterable[str]:
465
556
  """
@@ -472,13 +563,19 @@ def _iter_tox_ini_requirement_strings(
472
563
  - string (str) = "": The contents of a tox.ini file
473
564
  """
474
565
  parser: ConfigParser = ConfigParser()
566
+ message: str
475
567
  if path:
476
- assert (
477
- not string
478
- ), "Either `path` or `string` arguments may be provided, but not both"
568
+ if string:
569
+ message = (
570
+ "Either `path` or `string` arguments may be provided, but not "
571
+ "both"
572
+ )
573
+ raise ValueError(message)
479
574
  parser.read(path)
480
575
  else:
481
- assert string, "Either a `path` or `string` argument must be provided"
576
+ if not string:
577
+ message = "Either a `path` or `string` argument must be provided"
578
+ raise ValueError(message)
482
579
  parser.read_string(string)
483
580
 
484
581
  def get_section_option_requirements(
@@ -525,10 +622,10 @@ def _is_installed_requirement_string(item: Any) -> bool:
525
622
 
526
623
 
527
624
  def iter_find_requirements_lists(
528
- document: Union[Dict[str, Any], list],
529
- include_pointers: Tuple[str, ...] = (),
530
- exclude_pointers: Tuple[str, ...] = (),
531
- ) -> Iterable[List[str]]:
625
+ document: dict[str, Any] | list,
626
+ include_pointers: tuple[str, ...] = (),
627
+ exclude_pointers: tuple[str, ...] = (),
628
+ ) -> Iterable[list[str]]:
532
629
  """
533
630
  Recursively yield all lists of valid requirement strings for installed
534
631
  packages. Exclusions are resolved before inclusions.
@@ -581,8 +678,8 @@ def iter_find_requirements_lists(
581
678
 
582
679
  def _iter_toml_requirement_strings(
583
680
  path: str,
584
- include_pointers: Tuple[str, ...] = (),
585
- exclude_pointers: Tuple[str, ...] = (),
681
+ include_pointers: tuple[str, ...] = (),
682
+ exclude_pointers: tuple[str, ...] = (),
586
683
  ) -> Iterable[str]:
587
684
  """
588
685
  Read a TOML file and yield the requirements found.
@@ -597,7 +694,7 @@ def _iter_toml_requirement_strings(
597
694
  # Parse pyproject.toml
598
695
  try:
599
696
  with open(path, "rb") as pyproject_io:
600
- document: Dict[str, Any] = tomli.load(pyproject_io)
697
+ document: dict[str, Any] = tomli.load(pyproject_io)
601
698
  except FileNotFoundError:
602
699
  return
603
700
  # Find requirements
@@ -615,8 +712,8 @@ def _iter_toml_requirement_strings(
615
712
  def iter_configuration_file_requirement_strings(
616
713
  path: str,
617
714
  *,
618
- include_pointers: Tuple[str, ...] = (),
619
- exclude_pointers: Tuple[str, ...] = (),
715
+ include_pointers: tuple[str, ...] = (),
716
+ exclude_pointers: tuple[str, ...] = (),
620
717
  ) -> Iterable[str]:
621
718
  """
622
719
  Read a configuration file and yield the parsed requirements.
@@ -633,7 +730,7 @@ def iter_configuration_file_requirement_strings(
633
730
  )
634
731
  if configuration_file_type == ConfigurationFileType.SETUP_CFG:
635
732
  return _iter_setup_cfg_requirement_strings(path)
636
- elif configuration_file_type in (
733
+ if configuration_file_type in (
637
734
  ConfigurationFileType.PYPROJECT_TOML,
638
735
  ConfigurationFileType.TOML,
639
736
  ):
@@ -642,13 +739,11 @@ def iter_configuration_file_requirement_strings(
642
739
  include_pointers=include_pointers,
643
740
  exclude_pointers=exclude_pointers,
644
741
  )
645
- elif configuration_file_type == ConfigurationFileType.TOX_INI:
742
+ if configuration_file_type == ConfigurationFileType.TOX_INI:
646
743
  return _iter_tox_ini_requirement_strings(path=path)
647
- else:
648
- assert (
649
- configuration_file_type == ConfigurationFileType.REQUIREMENTS_TXT
650
- )
651
- return _iter_file_requirement_strings(path)
744
+ if configuration_file_type != ConfigurationFileType.REQUIREMENTS_TXT:
745
+ raise ValueError(configuration_file_type)
746
+ return _iter_file_requirement_strings(path)
652
747
 
653
748
 
654
749
  @functools.lru_cache
@@ -669,15 +764,14 @@ def _get_setup_cfg_metadata(path: str, key: str) -> str:
669
764
  parser.read(path)
670
765
  if "metadata" in parser:
671
766
  return parser.get("metadata", key, fallback="")
672
- else:
673
- warn(
674
- f"No `metadata` section found in: {path}",
675
- stacklevel=2,
676
- )
767
+ warn(
768
+ f"No `metadata` section found in: {path}",
769
+ stacklevel=2,
770
+ )
677
771
  return ""
678
772
 
679
773
 
680
- def _get_setup_py_metadata(path: str, args: Tuple[str, ...]) -> str:
774
+ def _get_setup_py_metadata(path: str, args: tuple[str, ...]) -> str:
681
775
  """
682
776
  Execute a setup.py script with `args` and return the response.
683
777
 
@@ -699,7 +793,7 @@ def _get_setup_py_metadata(path: str, args: Tuple[str, ...]) -> str:
699
793
  os.chdir(directory)
700
794
  path = os.path.join(directory, "setup.py")
701
795
  if os.path.isfile(path):
702
- command: Tuple[str, ...] = (sys.executable, path) + args
796
+ command: tuple[str, ...] = (sys.executable, path, *args)
703
797
  try:
704
798
  value = check_output(command).strip().split("\n")[-1]
705
799
  except CalledProcessError:
@@ -713,7 +807,7 @@ def _get_setup_py_metadata(path: str, args: Tuple[str, ...]) -> str:
713
807
  setup_egg_info(directory)
714
808
  try:
715
809
  value = check_output(command).strip().split("\n")[-1]
716
- except Exception:
810
+ except Exception: # noqa: BLE001
717
811
  warn(
718
812
  f"A package name could not be found in {path}"
719
813
  f"\nError ignored: {get_exception_text()}",
@@ -732,7 +826,7 @@ def _get_pyproject_toml_project_metadata(path: str, key: str) -> str:
732
826
  if os.path.isfile(path):
733
827
  pyproject_io: IO[str]
734
828
  with open(path) as pyproject_io:
735
- pyproject: Dict[str, Any] = tomli.loads(pyproject_io.read())
829
+ pyproject: dict[str, Any] = tomli.loads(pyproject_io.read())
736
830
  if "project" in pyproject:
737
831
  return pyproject["project"].get(key, "")
738
832
  return ""
@@ -760,15 +854,15 @@ def get_setup_distribution_version(path: str) -> str:
760
854
  )
761
855
 
762
856
 
763
- def _setup(arguments: Tuple[str, ...]) -> None:
857
+ def _setup(arguments: tuple[str, ...]) -> None:
764
858
  try:
765
- check_output((sys.executable, "setup.py") + arguments)
859
+ check_output((sys.executable, "setup.py", *arguments))
766
860
  except CalledProcessError:
767
861
  warn(f"Ignoring error: {get_exception_text()}", stacklevel=2)
768
862
 
769
863
 
770
864
  def _setup_location(
771
- location: Union[str, Path], arguments: Iterable[Tuple[str, ...]]
865
+ location: str | Path, arguments: Iterable[tuple[str, ...]]
772
866
  ) -> None:
773
867
  if isinstance(location, str):
774
868
  location = Path(location)
@@ -789,7 +883,7 @@ def get_editable_distribution_location(name: str) -> str:
789
883
  return get_editable_distributions_locations().get(normalize_name(name), "")
790
884
 
791
885
 
792
- def setup_egg_info(directory: Union[str, Path], egg_base: str = "") -> None:
886
+ def setup_egg_info(directory: str | Path, egg_base: str = "") -> None:
793
887
  """
794
888
  Refresh egg-info for the editable package installed in
795
889
  `directory` (only applicable for packages using a `setup.py` script)
@@ -819,27 +913,30 @@ def get_requirement(
819
913
  ) -> Requirement:
820
914
  try:
821
915
  return Requirement(requirement_string)
822
- except InvalidRequirement:
916
+ except InvalidRequirement as error:
823
917
  # Try to parse the requirement as an installation target location,
824
918
  # such as can be used with `pip install`
825
919
  location: str = requirement_string
826
920
  extras: str = ""
827
921
  if "[" in requirement_string and requirement_string.endswith("]"):
828
- parts: List[str] = requirement_string.split("[")
922
+ parts: list[str] = requirement_string.split("[")
829
923
  location = "[".join(parts[:-1])
830
924
  extras = f"[{parts[-1]}"
831
925
  location = os.path.abspath(location)
832
926
  name: str = get_setup_distribution_name(location)
833
- assert name, f"No distribution found in {location}"
927
+ if not name:
928
+ message: str = f"No distribution found in {location}"
929
+ raise FileNotFoundError(message) from error
834
930
  return Requirement(f"{name}{extras}")
835
931
 
836
932
 
837
933
  def get_required_distribution_names(
838
934
  requirement_string: str,
935
+ *,
839
936
  exclude: Iterable[str] = (),
840
937
  recursive: bool = True,
841
938
  echo: bool = False,
842
- depth: Optional[int] = None,
939
+ depth: int | None = None,
843
940
  ) -> MutableSet[str]:
844
941
  """
845
942
  Return a `tuple` of all distribution names which are required by the
@@ -878,10 +975,7 @@ def _get_requirement_name(requirement: Requirement) -> str:
878
975
  return normalize_name(requirement.name)
879
976
 
880
977
 
881
- def install_requirement(
882
- requirement: Union[str, Requirement],
883
- echo: bool = True,
884
- ) -> None:
978
+ def install_requirement(requirement: str | Requirement) -> None:
885
979
  """
886
980
  Install a requirement
887
981
 
@@ -899,13 +993,14 @@ def install_requirement(
899
993
  def _install_requirement_string(
900
994
  requirement_string: str,
901
995
  name: str = "",
996
+ *,
902
997
  editable: bool = False,
903
998
  ) -> None:
904
999
  """
905
1000
  Install a requirement string with no dependencies, compilation, build
906
1001
  isolation, etc.
907
1002
  """
908
- command: Tuple[str, ...] = (
1003
+ command: tuple[str, ...] = (
909
1004
  sys.executable,
910
1005
  "-m",
911
1006
  "pip",
@@ -943,13 +1038,13 @@ def _install_requirement_string(
943
1038
  )
944
1039
  )
945
1040
  if not editable:
946
- print(message)
947
- raise error
1041
+ print(message) # noqa: T201
1042
+ raise
948
1043
  try:
949
- check_output(command + ("--force-reinstall",))
950
- except CalledProcessError as retry_error:
951
- print(message)
952
- raise retry_error
1044
+ check_output((*command, "--force-reinstall"))
1045
+ except CalledProcessError:
1046
+ print(message) # noqa: T201
1047
+ raise
953
1048
 
954
1049
 
955
1050
  def _install_requirement(
@@ -957,7 +1052,7 @@ def _install_requirement(
957
1052
  ) -> None:
958
1053
  requirement_string: str = str(requirement)
959
1054
  # Get the distribution name
960
- distribution: Optional[Distribution] = None
1055
+ distribution: Distribution | None = None
961
1056
  editable_location: str = ""
962
1057
  try:
963
1058
  distribution = _get_distribution(requirement.name)
@@ -987,9 +1082,10 @@ def _install_requirement(
987
1082
  def _get_requirement_distribution(
988
1083
  requirement: Requirement,
989
1084
  name: str,
1085
+ *,
990
1086
  reinstall: bool = True,
991
1087
  echo: bool = False,
992
- ) -> Optional[Distribution]:
1088
+ ) -> Distribution | None:
993
1089
  if name in _BUILTIN_DISTRIBUTION_NAMES:
994
1090
  return None
995
1091
  try:
@@ -1004,7 +1100,7 @@ def _get_requirement_distribution(
1004
1100
  stacklevel=2,
1005
1101
  )
1006
1102
  # Attempt to install the requirement...
1007
- install_requirement(requirement, echo=echo)
1103
+ install_requirement(requirement)
1008
1104
  return _get_requirement_distribution(
1009
1105
  requirement, name, reinstall=False, echo=echo
1010
1106
  )
@@ -1012,7 +1108,7 @@ def _get_requirement_distribution(
1012
1108
 
1013
1109
  def _iter_distribution_requirements(
1014
1110
  distribution: Distribution,
1015
- extras: Tuple[str, ...] = (),
1111
+ extras: tuple[str, ...] = (),
1016
1112
  exclude: Container[str] = (),
1017
1113
  ) -> Iterable[Requirement]:
1018
1114
  if not distribution.requires:
@@ -1031,24 +1127,25 @@ def _iter_distribution_requirements(
1031
1127
 
1032
1128
  def _iter_requirement_names(
1033
1129
  requirement: Requirement,
1130
+ *,
1034
1131
  exclude: MutableSet[str],
1035
1132
  recursive: bool = True,
1036
1133
  echo: bool = False,
1037
- depth: Optional[int] = None,
1134
+ depth: int | None = None,
1038
1135
  ) -> Iterable[str]:
1039
1136
  name: str = normalize_name(requirement.name)
1040
- extras: Tuple[str, ...] = tuple(requirement.extras)
1137
+ extras: tuple[str, ...] = tuple(requirement.extras)
1041
1138
  if name in exclude:
1042
1139
  return ()
1043
1140
  # Ensure we don't follow the same requirement again, causing cyclic
1044
1141
  # recursion
1045
1142
  exclude.add(name)
1046
- distribution: Optional[Distribution] = _get_requirement_distribution(
1143
+ distribution: Distribution | None = _get_requirement_distribution(
1047
1144
  requirement, name, echo=echo
1048
1145
  )
1049
1146
  if distribution is None:
1050
1147
  return ()
1051
- requirements: Tuple[Requirement, ...] = tuple(
1148
+ requirements: tuple[Requirement, ...] = tuple(
1052
1149
  iter_distinct(
1053
1150
  _iter_distribution_requirements(
1054
1151
  distribution,
@@ -1061,7 +1158,7 @@ def _iter_requirement_names(
1061
1158
 
1062
1159
  def iter_requirement_names_(
1063
1160
  requirement_: Requirement,
1064
- depth_: Optional[int] = None,
1161
+ depth_: int | None = None,
1065
1162
  ) -> Iterable[str]:
1066
1163
  if (depth_ is None) or depth_ >= 0:
1067
1164
  yield from _iter_requirement_names(