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/__main__.py +12 -6
- dependence/_utilities.py +203 -106
- dependence/freeze.py +52 -36
- dependence/update.py +67 -56
- dependence/upgrade.py +220 -0
- {dependence-1.0.4.dist-info → dependence-1.1.0.dist-info}/METADATA +81 -16
- dependence-1.1.0.dist-info/RECORD +11 -0
- {dependence-1.0.4.dist-info → dependence-1.1.0.dist-info}/WHEEL +1 -1
- dependence-1.0.4.dist-info/RECORD +0 -10
- {dependence-1.0.4.dist-info → dependence-1.1.0.dist-info}/entry_points.txt +0 -0
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:
|
|
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:
|
|
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:
|
|
92
|
-
cwd:
|
|
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 (
|
|
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) ->
|
|
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:
|
|
173
|
-
b:
|
|
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:
|
|
183
|
-
bs:
|
|
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:
|
|
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:
|
|
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
|
|
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(
|
|
303
|
-
|
|
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 =
|
|
360
|
+
basename: str = path.name.lower()
|
|
306
361
|
if basename == "setup.cfg":
|
|
307
362
|
return ConfigurationFileType.SETUP_CFG
|
|
308
|
-
|
|
363
|
+
if basename == "tox.ini":
|
|
309
364
|
return ConfigurationFileType.TOX_INI
|
|
310
|
-
|
|
365
|
+
if basename == "pyproject.toml":
|
|
311
366
|
return ConfigurationFileType.PYPROJECT_TOML
|
|
312
|
-
|
|
367
|
+
if basename.endswith(".txt"):
|
|
313
368
|
return ConfigurationFileType.REQUIREMENTS_TXT
|
|
314
|
-
|
|
369
|
+
if basename.endswith(".toml"):
|
|
315
370
|
return ConfigurationFileType.TOML
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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[
|
|
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() ->
|
|
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() ->
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
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:
|
|
529
|
-
include_pointers:
|
|
530
|
-
exclude_pointers:
|
|
531
|
-
) -> Iterable[
|
|
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:
|
|
585
|
-
exclude_pointers:
|
|
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:
|
|
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:
|
|
619
|
-
exclude_pointers:
|
|
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
|
-
|
|
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
|
-
|
|
742
|
+
if configuration_file_type == ConfigurationFileType.TOX_INI:
|
|
646
743
|
return _iter_tox_ini_requirement_strings(path=path)
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
857
|
+
def _setup(arguments: tuple[str, ...]) -> None:
|
|
764
858
|
try:
|
|
765
|
-
check_output((sys.executable, "setup.py"
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
1041
|
+
print(message) # noqa: T201
|
|
1042
|
+
raise
|
|
948
1043
|
try:
|
|
949
|
-
check_output(command
|
|
950
|
-
except CalledProcessError
|
|
951
|
-
print(message)
|
|
952
|
-
raise
|
|
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:
|
|
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
|
-
) ->
|
|
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
|
|
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:
|
|
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:
|
|
1134
|
+
depth: int | None = None,
|
|
1038
1135
|
) -> Iterable[str]:
|
|
1039
1136
|
name: str = normalize_name(requirement.name)
|
|
1040
|
-
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:
|
|
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:
|
|
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_:
|
|
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(
|