dependence 0.3.6__py3-none-any.whl → 1.0.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 +966 -39
- dependence/freeze.py +86 -45
- dependence/update.py +194 -86
- dependence-1.0.0.dist-info/METADATA +179 -0
- dependence-1.0.0.dist-info/RECORD +10 -0
- {dependence-0.3.6.dist-info → dependence-1.0.0.dist-info}/WHEEL +1 -2
- dependence/utilities.py +0 -1034
- dependence-0.3.6.dist-info/METADATA +0 -136
- dependence-0.3.6.dist-info/RECORD +0 -12
- dependence-0.3.6.dist-info/top_level.txt +0 -1
- {dependence-0.3.6.dist-info → dependence-1.0.0.dist-info}/entry_points.txt +0 -0
dependence/utilities.py
DELETED
|
@@ -1,1034 +0,0 @@
|
|
|
1
|
-
import functools
|
|
2
|
-
import os
|
|
3
|
-
import re
|
|
4
|
-
import sys
|
|
5
|
-
from collections import deque
|
|
6
|
-
from configparser import ConfigParser, SectionProxy
|
|
7
|
-
from enum import Enum, auto
|
|
8
|
-
from glob import iglob
|
|
9
|
-
from importlib.metadata import Distribution, PackageNotFoundError
|
|
10
|
-
from importlib.metadata import distribution as _get_distribution
|
|
11
|
-
from importlib.metadata import distributions as _get_distributions
|
|
12
|
-
from itertools import chain
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
from runpy import run_path
|
|
15
|
-
from shutil import rmtree
|
|
16
|
-
from subprocess import CalledProcessError, list2cmdline
|
|
17
|
-
from types import ModuleType
|
|
18
|
-
from typing import (
|
|
19
|
-
IO,
|
|
20
|
-
AbstractSet,
|
|
21
|
-
Any,
|
|
22
|
-
Container,
|
|
23
|
-
Dict,
|
|
24
|
-
Iterable,
|
|
25
|
-
List,
|
|
26
|
-
MutableSet,
|
|
27
|
-
Optional,
|
|
28
|
-
Tuple,
|
|
29
|
-
Union,
|
|
30
|
-
cast,
|
|
31
|
-
)
|
|
32
|
-
from warnings import warn
|
|
33
|
-
|
|
34
|
-
import tomli
|
|
35
|
-
from packaging.requirements import InvalidRequirement, Requirement
|
|
36
|
-
from packaging.utils import canonicalize_name
|
|
37
|
-
|
|
38
|
-
from ._utilities import (
|
|
39
|
-
append_exception_text,
|
|
40
|
-
check_output,
|
|
41
|
-
deprecated,
|
|
42
|
-
get_exception_text,
|
|
43
|
-
iter_distinct,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
_BUILTIN_DISTRIBUTION_NAMES: Tuple[str] = ("distribute",)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
_UNSAFE_CHARACTERS_PATTERN: re.Pattern = re.compile("[^A-Za-z0-9.]+")
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def normalize_name(name: str) -> str:
|
|
53
|
-
"""
|
|
54
|
-
Normalize a project/distribution name
|
|
55
|
-
"""
|
|
56
|
-
return _UNSAFE_CHARACTERS_PATTERN.sub("-", canonicalize_name(name)).lower()
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class ConfigurationFileType(Enum):
|
|
60
|
-
REQUIREMENTS_TXT = auto()
|
|
61
|
-
SETUP_CFG = auto()
|
|
62
|
-
TOX_INI = auto()
|
|
63
|
-
PYPROJECT_TOML = auto()
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@functools.lru_cache()
|
|
67
|
-
def get_configuration_file_type(path: str) -> ConfigurationFileType:
|
|
68
|
-
if not os.path.isfile(path):
|
|
69
|
-
raise FileNotFoundError(path)
|
|
70
|
-
basename: str = os.path.basename(path).lower()
|
|
71
|
-
if basename == "setup.cfg":
|
|
72
|
-
return ConfigurationFileType.SETUP_CFG
|
|
73
|
-
elif basename == "tox.ini":
|
|
74
|
-
return ConfigurationFileType.TOX_INI
|
|
75
|
-
elif basename == "pyproject.toml":
|
|
76
|
-
return ConfigurationFileType.PYPROJECT_TOML
|
|
77
|
-
elif basename.endswith(".txt"):
|
|
78
|
-
return ConfigurationFileType.REQUIREMENTS_TXT
|
|
79
|
-
else:
|
|
80
|
-
raise ValueError(
|
|
81
|
-
f"{path} is not a recognized type of configuration file."
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def is_configuration_file(path: str) -> bool:
|
|
86
|
-
try:
|
|
87
|
-
get_configuration_file_type(path)
|
|
88
|
-
except (FileNotFoundError, ValueError):
|
|
89
|
-
return False
|
|
90
|
-
return True
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _get_editable_finder_location(path_name: str) -> str:
|
|
94
|
-
key: str
|
|
95
|
-
value: Any
|
|
96
|
-
init_globals: Dict[str, Any]
|
|
97
|
-
try:
|
|
98
|
-
init_globals = run_path(path_name)
|
|
99
|
-
except Exception:
|
|
100
|
-
return ""
|
|
101
|
-
for key, value in init_globals.items():
|
|
102
|
-
if key.startswith("__editable__"):
|
|
103
|
-
finder: ModuleType = value
|
|
104
|
-
module_name: str
|
|
105
|
-
module_location: str
|
|
106
|
-
for module_name, module_location in getattr(
|
|
107
|
-
finder, "MAPPING", {}
|
|
108
|
-
).items():
|
|
109
|
-
path: Path = Path(module_location)
|
|
110
|
-
index: int
|
|
111
|
-
for index in range(len(module_name.split("."))):
|
|
112
|
-
path = path.parent
|
|
113
|
-
while path != path.parent:
|
|
114
|
-
if (
|
|
115
|
-
path.joinpath("setup.py").is_file()
|
|
116
|
-
or path.joinpath("setup.cfg").is_file()
|
|
117
|
-
or path.joinpath("pyproject.toml").is_file()
|
|
118
|
-
):
|
|
119
|
-
return str(path)
|
|
120
|
-
path = path.parent
|
|
121
|
-
return ""
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def _iter_path_editable_distribution_locations(
|
|
125
|
-
directory: str,
|
|
126
|
-
) -> Iterable[Tuple[str, str]]:
|
|
127
|
-
directory_path: Path = Path(directory)
|
|
128
|
-
file_path: Path
|
|
129
|
-
for file_path in chain(
|
|
130
|
-
directory_path.glob("*.egg-link"),
|
|
131
|
-
directory_path.glob("__editable__.*.pth"),
|
|
132
|
-
):
|
|
133
|
-
name: str
|
|
134
|
-
if file_path.name.endswith(".egg-link"):
|
|
135
|
-
name = file_path.name[:-9]
|
|
136
|
-
else:
|
|
137
|
-
name = file_path.name[13:-4].partition("-")[0]
|
|
138
|
-
name = normalize_name(name)
|
|
139
|
-
with open(file_path) as file_io:
|
|
140
|
-
location: str = file_io.read().strip().partition("\n")[0]
|
|
141
|
-
if os.path.exists(location):
|
|
142
|
-
yield name, location
|
|
143
|
-
else:
|
|
144
|
-
location = _get_editable_finder_location(str(file_path))
|
|
145
|
-
if location:
|
|
146
|
-
yield name, location
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def _iter_editable_distribution_locations() -> Iterable[Tuple[str, str]]:
|
|
150
|
-
yield from chain(
|
|
151
|
-
*map(_iter_path_editable_distribution_locations, sys.path)
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
@functools.lru_cache()
|
|
156
|
-
def get_editable_distributions_locations() -> Dict[str, str]:
|
|
157
|
-
"""
|
|
158
|
-
Get a mapping of (normalized) editable distribution names to their
|
|
159
|
-
locations.
|
|
160
|
-
"""
|
|
161
|
-
return dict(_iter_editable_distribution_locations())
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def cache_clear() -> None:
|
|
165
|
-
"""
|
|
166
|
-
Clear distribution metadata caches
|
|
167
|
-
"""
|
|
168
|
-
get_installed_distributions.cache_clear()
|
|
169
|
-
get_editable_distributions_locations.cache_clear()
|
|
170
|
-
is_editable.cache_clear()
|
|
171
|
-
is_installed.cache_clear()
|
|
172
|
-
get_requirement_string_distribution_name.cache_clear()
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def refresh_editable_distributions() -> None:
|
|
176
|
-
"""
|
|
177
|
-
Update distribution information for editable installs
|
|
178
|
-
"""
|
|
179
|
-
name: str
|
|
180
|
-
location: str
|
|
181
|
-
for name, location in get_editable_distributions_locations().items():
|
|
182
|
-
_install_requirement_string(location, name=name, editable=True)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
@functools.lru_cache()
|
|
186
|
-
def get_installed_distributions() -> Dict[str, Distribution]:
|
|
187
|
-
"""
|
|
188
|
-
Return a dictionary of installed distributions.
|
|
189
|
-
"""
|
|
190
|
-
refresh_editable_distributions()
|
|
191
|
-
installed: Dict[str, Distribution] = {}
|
|
192
|
-
for distribution in _get_distributions():
|
|
193
|
-
installed[normalize_name(distribution.metadata["Name"])] = distribution
|
|
194
|
-
return installed
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def get_distribution(name: str) -> Distribution:
|
|
198
|
-
return get_installed_distributions()[normalize_name(name)]
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
@functools.lru_cache()
|
|
202
|
-
def is_installed(distribution_name: str) -> bool:
|
|
203
|
-
return normalize_name(distribution_name) in get_installed_distributions()
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def get_requirement_distribution_name(requirement: Requirement) -> str:
|
|
207
|
-
return normalize_name(requirement.name)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
@functools.lru_cache()
|
|
211
|
-
def get_requirement_string_distribution_name(requirement_string: str) -> str:
|
|
212
|
-
return get_requirement_distribution_name(
|
|
213
|
-
get_requirement(requirement_string)
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
@functools.lru_cache()
|
|
218
|
-
def is_requirement_string(requirement_string: str) -> bool:
|
|
219
|
-
try:
|
|
220
|
-
Requirement(requirement_string)
|
|
221
|
-
except InvalidRequirement:
|
|
222
|
-
return False
|
|
223
|
-
return True
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def _iter_file_requirement_strings(path: str) -> Iterable[str]:
|
|
227
|
-
lines: List[str]
|
|
228
|
-
requirement_file_io: IO[str]
|
|
229
|
-
with open(path) as requirement_file_io:
|
|
230
|
-
lines = requirement_file_io.readlines()
|
|
231
|
-
return filter(is_requirement_string, lines)
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
def _iter_setup_cfg_requirement_strings(path: str) -> Iterable[str]:
|
|
235
|
-
parser: ConfigParser = ConfigParser()
|
|
236
|
-
parser.read(path)
|
|
237
|
-
requirement_strings: Iterable[str] = ()
|
|
238
|
-
if ("options" in parser) and ("install_requires" in parser["options"]):
|
|
239
|
-
requirement_strings = chain(
|
|
240
|
-
requirement_strings,
|
|
241
|
-
filter(
|
|
242
|
-
is_requirement_string,
|
|
243
|
-
parser["options"]["install_requires"].split("\n"),
|
|
244
|
-
),
|
|
245
|
-
)
|
|
246
|
-
if "options.extras_require" in parser:
|
|
247
|
-
extras_require: SectionProxy = parser["options.extras_require"]
|
|
248
|
-
extra_requirements_string: str
|
|
249
|
-
for extra_requirements_string in extras_require.values():
|
|
250
|
-
requirement_strings = chain(
|
|
251
|
-
requirement_strings,
|
|
252
|
-
filter(
|
|
253
|
-
is_requirement_string,
|
|
254
|
-
extra_requirements_string.split("\n"),
|
|
255
|
-
),
|
|
256
|
-
)
|
|
257
|
-
return iter_distinct(requirement_strings)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def _iter_tox_ini_requirement_strings(
|
|
261
|
-
path: Union[str, Path, ConfigParser] = "",
|
|
262
|
-
string: str = "",
|
|
263
|
-
) -> Iterable[str]:
|
|
264
|
-
"""
|
|
265
|
-
Parse a tox.ini file and yield the requirements found in the `deps`
|
|
266
|
-
options of each section.
|
|
267
|
-
|
|
268
|
-
Parameters:
|
|
269
|
-
|
|
270
|
-
- path (str|Path) = "": The path to a tox.ini file
|
|
271
|
-
- string (str) = "": The contents of a tox.ini file
|
|
272
|
-
"""
|
|
273
|
-
parser: ConfigParser = ConfigParser()
|
|
274
|
-
if path:
|
|
275
|
-
assert (
|
|
276
|
-
not string
|
|
277
|
-
), "Either `path` or `string` arguments may be provided, but not both"
|
|
278
|
-
parser.read(path)
|
|
279
|
-
else:
|
|
280
|
-
assert string, "Either a `path` or `string` argument must be provided"
|
|
281
|
-
parser.read_string(string)
|
|
282
|
-
|
|
283
|
-
def get_section_option_requirements(
|
|
284
|
-
section_name: str, option_name: str
|
|
285
|
-
) -> Iterable[str]:
|
|
286
|
-
if parser.has_option(section_name, option_name):
|
|
287
|
-
return filter(
|
|
288
|
-
is_requirement_string,
|
|
289
|
-
parser.get(section_name, option_name).split("\n"),
|
|
290
|
-
)
|
|
291
|
-
return ()
|
|
292
|
-
|
|
293
|
-
def get_section_requirements(section_name: str) -> Iterable[str]:
|
|
294
|
-
requirements: Iterable[str] = get_section_option_requirements(
|
|
295
|
-
section_name, "deps"
|
|
296
|
-
)
|
|
297
|
-
if section_name == "tox":
|
|
298
|
-
requirements = chain(
|
|
299
|
-
requirements,
|
|
300
|
-
get_section_option_requirements(section_name, "requires"),
|
|
301
|
-
)
|
|
302
|
-
return requirements
|
|
303
|
-
|
|
304
|
-
return iter_distinct(
|
|
305
|
-
chain(("tox",), *map(get_section_requirements, parser.sections()))
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
def _iter_pyproject_toml_requirement_strings(
|
|
310
|
-
path: str,
|
|
311
|
-
exclude_build_system: bool = False,
|
|
312
|
-
exclude_project: bool = False,
|
|
313
|
-
exclude_project_dependencies: bool = False,
|
|
314
|
-
exclude_project_optional_dependencies: bool = False,
|
|
315
|
-
include_project_optional_dependencies: Iterable[str] = frozenset(),
|
|
316
|
-
exclude_tools: bool = False,
|
|
317
|
-
exclude_tox: bool = False,
|
|
318
|
-
) -> Iterable[str]:
|
|
319
|
-
"""
|
|
320
|
-
Read a pyproject.toml file and yield the requirements found.
|
|
321
|
-
|
|
322
|
-
- exclude_build_system (bool) = False: If `True`, build-system
|
|
323
|
-
requirements will not be included
|
|
324
|
-
- exclude_project (bool) = False: If `True`, build-system
|
|
325
|
-
requirements will not be included
|
|
326
|
-
- exclude_project_dependencies (bool) = False: If `True`, project
|
|
327
|
-
dependencies will not be included
|
|
328
|
-
- exclude_project_optional_dependencies (bool) = False: If `True`, project
|
|
329
|
-
optional dependencies will not be included
|
|
330
|
-
- include_project_optional_dependencies ({str}) = frozenset(): If a
|
|
331
|
-
non-empty set is provided, *only* dependencies for the specified extras
|
|
332
|
-
(options) will be included
|
|
333
|
-
- exclude_tools (bool) = False: If `True`, tool requirements will not be
|
|
334
|
-
included
|
|
335
|
-
- exclude_tox (bool) = False: If `True`, tool.tox dependencies will not be
|
|
336
|
-
included
|
|
337
|
-
"""
|
|
338
|
-
include_project_optional_dependencies = (
|
|
339
|
-
include_project_optional_dependencies
|
|
340
|
-
if isinstance(include_project_optional_dependencies, set)
|
|
341
|
-
else frozenset(include_project_optional_dependencies)
|
|
342
|
-
)
|
|
343
|
-
pyproject_io: IO[str]
|
|
344
|
-
with open(path) as pyproject_io:
|
|
345
|
-
pyproject: Dict[str, Any] = tomli.loads(pyproject_io.read())
|
|
346
|
-
# Build system requirements
|
|
347
|
-
if (
|
|
348
|
-
("build-system" in pyproject)
|
|
349
|
-
and ("requires" in pyproject["build-system"])
|
|
350
|
-
and not exclude_build_system
|
|
351
|
-
):
|
|
352
|
-
yield from pyproject["build-system"]["requires"]
|
|
353
|
-
# Project requirements
|
|
354
|
-
if ("project" in pyproject) and not exclude_project:
|
|
355
|
-
if (
|
|
356
|
-
"dependencies" in pyproject["project"]
|
|
357
|
-
) and not exclude_project_dependencies:
|
|
358
|
-
yield from pyproject["project"]["dependencies"]
|
|
359
|
-
if (
|
|
360
|
-
"optional-dependencies" in pyproject["project"]
|
|
361
|
-
) and not exclude_project_optional_dependencies:
|
|
362
|
-
key: str
|
|
363
|
-
values: Iterable[str]
|
|
364
|
-
for key, values in pyproject["project"][
|
|
365
|
-
"optional-dependencies"
|
|
366
|
-
].items():
|
|
367
|
-
if (not include_project_optional_dependencies) or (
|
|
368
|
-
key in include_project_optional_dependencies
|
|
369
|
-
):
|
|
370
|
-
yield from values
|
|
371
|
-
# Tool Requirements
|
|
372
|
-
if ("tool" in pyproject) and not exclude_tools:
|
|
373
|
-
# Tox
|
|
374
|
-
if ("tox" in pyproject["tool"]) and not exclude_tox:
|
|
375
|
-
if "legacy_tox_ini" in pyproject["tool"]["tox"]:
|
|
376
|
-
yield from _iter_tox_ini_requirement_strings(
|
|
377
|
-
string=pyproject["tool"]["tox"]["legacy_tox_ini"]
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
def iter_configuration_file_requirement_strings(
|
|
382
|
-
path: str,
|
|
383
|
-
exclude_build_system: bool = False,
|
|
384
|
-
exclude_project: bool = False,
|
|
385
|
-
exclude_project_dependencies: bool = False,
|
|
386
|
-
exclude_project_optional_dependencies: bool = False,
|
|
387
|
-
include_project_optional_dependencies: AbstractSet[str] = frozenset(),
|
|
388
|
-
exclude_tools: bool = False,
|
|
389
|
-
exclude_tox: bool = False,
|
|
390
|
-
) -> Iterable[str]:
|
|
391
|
-
"""
|
|
392
|
-
Read a configuration file and yield the parsed requirements.
|
|
393
|
-
|
|
394
|
-
Parameters:
|
|
395
|
-
|
|
396
|
-
- path (str): The path to a configuration file
|
|
397
|
-
|
|
398
|
-
Parameters only applicable to `pyproject.toml` files:
|
|
399
|
-
|
|
400
|
-
- exclude_build_system (bool) = False: If `True`, build-system
|
|
401
|
-
requirements will not be included
|
|
402
|
-
- exclude_project (bool) = False: If `True`, build-system
|
|
403
|
-
requirements will not be included
|
|
404
|
-
- exclude_project_dependencies (bool) = False: If `True`, project
|
|
405
|
-
dependencies will not be included
|
|
406
|
-
- exclude_project_optional_dependencies (bool) = False: If `True`, project
|
|
407
|
-
optional dependencies will not be included
|
|
408
|
-
- include_project_optional_dependencies ({str}) = frozenset(): If a
|
|
409
|
-
non-empty set is provided, *only* dependencies for the specified extras
|
|
410
|
-
(options) will be included
|
|
411
|
-
- exclude_tools (bool) = False: If `True`, tool requirements will not be
|
|
412
|
-
included
|
|
413
|
-
- exclude_tox (bool) = False: If `True`, tool.tox dependencies will not be
|
|
414
|
-
included
|
|
415
|
-
"""
|
|
416
|
-
configuration_file_type: ConfigurationFileType = (
|
|
417
|
-
get_configuration_file_type(path)
|
|
418
|
-
)
|
|
419
|
-
if configuration_file_type == ConfigurationFileType.SETUP_CFG:
|
|
420
|
-
return _iter_setup_cfg_requirement_strings(path)
|
|
421
|
-
elif configuration_file_type == ConfigurationFileType.PYPROJECT_TOML:
|
|
422
|
-
return _iter_pyproject_toml_requirement_strings(
|
|
423
|
-
path,
|
|
424
|
-
exclude_build_system=exclude_build_system,
|
|
425
|
-
exclude_project=exclude_project,
|
|
426
|
-
exclude_project_dependencies=exclude_project_dependencies,
|
|
427
|
-
exclude_project_optional_dependencies=(
|
|
428
|
-
exclude_project_optional_dependencies
|
|
429
|
-
),
|
|
430
|
-
include_project_optional_dependencies=(
|
|
431
|
-
include_project_optional_dependencies
|
|
432
|
-
),
|
|
433
|
-
exclude_tools=exclude_tools,
|
|
434
|
-
exclude_tox=exclude_tox,
|
|
435
|
-
)
|
|
436
|
-
elif configuration_file_type == ConfigurationFileType.TOX_INI:
|
|
437
|
-
return _iter_tox_ini_requirement_strings(path=path)
|
|
438
|
-
else:
|
|
439
|
-
assert (
|
|
440
|
-
configuration_file_type == ConfigurationFileType.REQUIREMENTS_TXT
|
|
441
|
-
)
|
|
442
|
-
return _iter_file_requirement_strings(path)
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
@functools.lru_cache()
|
|
446
|
-
def is_editable(name: str) -> bool:
|
|
447
|
-
"""
|
|
448
|
-
Return `True` if the indicated distribution is an editable installation.
|
|
449
|
-
"""
|
|
450
|
-
return bool(normalize_name(name) in get_editable_distributions_locations())
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
def _get_setup_cfg_metadata(path: str, key: str) -> str:
|
|
454
|
-
if os.path.basename(path).lower() != "setup.cfg":
|
|
455
|
-
if not os.path.isdir(path):
|
|
456
|
-
path = os.path.dirname(path)
|
|
457
|
-
path = os.path.join(path, "setup.cfg")
|
|
458
|
-
if os.path.isfile(path):
|
|
459
|
-
parser: ConfigParser = ConfigParser()
|
|
460
|
-
parser.read(path)
|
|
461
|
-
if "metadata" in parser:
|
|
462
|
-
return parser.get("metadata", key, fallback="")
|
|
463
|
-
else:
|
|
464
|
-
warn(f"No `metadata` section found in: {path}")
|
|
465
|
-
return ""
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
def _get_setup_py_metadata(path: str, args: Tuple[str, ...]) -> str:
|
|
469
|
-
"""
|
|
470
|
-
Execute a setup.py script with `args` and return the response.
|
|
471
|
-
|
|
472
|
-
Parameters:
|
|
473
|
-
|
|
474
|
-
- path (str)
|
|
475
|
-
- args ([str])
|
|
476
|
-
"""
|
|
477
|
-
value: str = ""
|
|
478
|
-
current_directory: str = os.path.abspath(os.curdir)
|
|
479
|
-
directory: str = path
|
|
480
|
-
try:
|
|
481
|
-
if os.path.basename(path).lower() == "setup.py":
|
|
482
|
-
directory = os.path.dirname(path)
|
|
483
|
-
os.chdir(directory)
|
|
484
|
-
else:
|
|
485
|
-
if not os.path.isdir(path):
|
|
486
|
-
directory = os.path.dirname(path)
|
|
487
|
-
os.chdir(directory)
|
|
488
|
-
path = os.path.join(directory, "setup.py")
|
|
489
|
-
if os.path.isfile(path):
|
|
490
|
-
command: Tuple[str, ...] = (sys.executable, path) + args
|
|
491
|
-
try:
|
|
492
|
-
value = check_output(command).strip().split("\n")[-1]
|
|
493
|
-
except CalledProcessError:
|
|
494
|
-
warn(
|
|
495
|
-
f"A package name could not be found in {path}, "
|
|
496
|
-
"attempting to refresh egg info"
|
|
497
|
-
f"\nError ignored: {get_exception_text()}"
|
|
498
|
-
)
|
|
499
|
-
# re-write egg info and attempt to get the name again
|
|
500
|
-
setup_egg_info(directory)
|
|
501
|
-
try:
|
|
502
|
-
value = check_output(command).strip().split("\n")[-1]
|
|
503
|
-
except Exception:
|
|
504
|
-
warn(
|
|
505
|
-
f"A package name could not be found in {path}"
|
|
506
|
-
f"\nError ignored: {get_exception_text()}"
|
|
507
|
-
)
|
|
508
|
-
finally:
|
|
509
|
-
os.chdir(current_directory)
|
|
510
|
-
return value
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
def _get_pyproject_toml_project_metadata(path: str, key: str) -> str:
|
|
514
|
-
if os.path.basename(path).lower() != "pyproject.toml":
|
|
515
|
-
if not os.path.isdir(path):
|
|
516
|
-
path = os.path.dirname(path)
|
|
517
|
-
path = os.path.join(path, "pyproject.toml")
|
|
518
|
-
if os.path.isfile(path):
|
|
519
|
-
pyproject_io: IO[str]
|
|
520
|
-
with open(path) as pyproject_io:
|
|
521
|
-
pyproject: Dict[str, Any] = tomli.loads(pyproject_io.read())
|
|
522
|
-
if "project" in pyproject:
|
|
523
|
-
return pyproject["project"].get(key, "")
|
|
524
|
-
return ""
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
def get_setup_distribution_name(path: str) -> str:
|
|
528
|
-
"""
|
|
529
|
-
Get a distribution's name from setup.py, setup.cfg or pyproject.toml
|
|
530
|
-
"""
|
|
531
|
-
return normalize_name(
|
|
532
|
-
_get_setup_cfg_metadata(path, "name")
|
|
533
|
-
or _get_pyproject_toml_project_metadata(path, "name")
|
|
534
|
-
or _get_setup_py_metadata(path, ("--name",))
|
|
535
|
-
)
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
def get_setup_distribution_version(path: str) -> str:
|
|
539
|
-
"""
|
|
540
|
-
Get a distribution's version from setup.py, setup.cfg or pyproject.toml
|
|
541
|
-
"""
|
|
542
|
-
return (
|
|
543
|
-
_get_setup_cfg_metadata(path, "version")
|
|
544
|
-
or _get_pyproject_toml_project_metadata(path, "version")
|
|
545
|
-
or _get_setup_py_metadata(path, ("--version",))
|
|
546
|
-
)
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
def _setup(arguments: Tuple[str, ...]) -> None:
|
|
550
|
-
try:
|
|
551
|
-
check_output((sys.executable, "setup.py") + arguments)
|
|
552
|
-
except CalledProcessError:
|
|
553
|
-
warn(f"Ignoring error: {get_exception_text()}")
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
def _setup_location(
|
|
557
|
-
location: Union[str, Path], arguments: Iterable[Tuple[str, ...]]
|
|
558
|
-
) -> None:
|
|
559
|
-
if isinstance(location, str):
|
|
560
|
-
location = Path(location)
|
|
561
|
-
# If there is no setup.py file, we can't update egg info
|
|
562
|
-
if not location.joinpath("setup.py").is_file():
|
|
563
|
-
return
|
|
564
|
-
if isinstance(arguments, str):
|
|
565
|
-
arguments = (arguments,)
|
|
566
|
-
current_directory: Path = Path(os.curdir).absolute()
|
|
567
|
-
os.chdir(location)
|
|
568
|
-
try:
|
|
569
|
-
deque(map(_setup, arguments), maxlen=0)
|
|
570
|
-
finally:
|
|
571
|
-
os.chdir(current_directory)
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
@deprecated()
|
|
575
|
-
def setup_dist_egg_info(directory: str) -> None:
|
|
576
|
-
"""
|
|
577
|
-
Refresh dist-info and egg-info for the editable package installed in
|
|
578
|
-
`directory`
|
|
579
|
-
"""
|
|
580
|
-
directory = os.path.abspath(directory)
|
|
581
|
-
if not os.path.isdir(directory):
|
|
582
|
-
directory = os.path.dirname(directory)
|
|
583
|
-
_setup_location(
|
|
584
|
-
directory,
|
|
585
|
-
(
|
|
586
|
-
("-q", "dist_info"),
|
|
587
|
-
("-q", "egg_info"),
|
|
588
|
-
),
|
|
589
|
-
)
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
def get_editable_distribution_location(name: str) -> str:
|
|
593
|
-
return get_editable_distributions_locations().get(normalize_name(name), "")
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
@deprecated()
|
|
597
|
-
def setup_dist_info(
|
|
598
|
-
directory: Union[str, Path], output_dir: Union[str, Path] = ""
|
|
599
|
-
) -> None:
|
|
600
|
-
"""
|
|
601
|
-
Refresh dist-info for the editable package installed in
|
|
602
|
-
`directory`
|
|
603
|
-
"""
|
|
604
|
-
if isinstance(directory, str):
|
|
605
|
-
directory = Path(directory)
|
|
606
|
-
directory = directory.absolute()
|
|
607
|
-
if not directory.is_dir():
|
|
608
|
-
directory = directory.parent
|
|
609
|
-
if isinstance(output_dir, str) and output_dir:
|
|
610
|
-
output_dir = Path(output_dir)
|
|
611
|
-
_setup_location(
|
|
612
|
-
directory,
|
|
613
|
-
(
|
|
614
|
-
("-q", "dist_info")
|
|
615
|
-
+ (("--output-dir", str(output_dir)) if output_dir else ()),
|
|
616
|
-
),
|
|
617
|
-
)
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
def setup_egg_info(directory: Union[str, Path], egg_base: str = "") -> None:
|
|
621
|
-
"""
|
|
622
|
-
Refresh egg-info for the editable package installed in
|
|
623
|
-
`directory` (only applicable for packages using a `setup.py` script)
|
|
624
|
-
"""
|
|
625
|
-
if isinstance(directory, str):
|
|
626
|
-
directory = Path(directory)
|
|
627
|
-
directory = directory.absolute()
|
|
628
|
-
if not directory.is_dir():
|
|
629
|
-
directory = directory.parent
|
|
630
|
-
# If there is a setup.py, and a *.dist-info directory, but that
|
|
631
|
-
# *.dist-info directory has no RECORD, we need to remove the *.dist-info
|
|
632
|
-
# directory
|
|
633
|
-
if directory.joinpath("setup.py").is_file():
|
|
634
|
-
dist_info: str
|
|
635
|
-
for dist_info in iglob(str(directory.joinpath("*.dist-info"))):
|
|
636
|
-
dist_info_path: Path = Path(dist_info)
|
|
637
|
-
if not dist_info_path.joinpath("RECORD").is_file():
|
|
638
|
-
rmtree(dist_info_path)
|
|
639
|
-
_setup_location(
|
|
640
|
-
directory,
|
|
641
|
-
(("-q", "egg_info") + (("--egg-base", egg_base) if egg_base else ()),),
|
|
642
|
-
)
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
def get_requirement(
|
|
646
|
-
requirement_string: str,
|
|
647
|
-
) -> Requirement:
|
|
648
|
-
try:
|
|
649
|
-
return Requirement(requirement_string)
|
|
650
|
-
except InvalidRequirement:
|
|
651
|
-
# Try to parse the requirement as an installation target location,
|
|
652
|
-
# such as can be used with `pip install`
|
|
653
|
-
location: str = requirement_string
|
|
654
|
-
extras: str = ""
|
|
655
|
-
if "[" in requirement_string and requirement_string.endswith("]"):
|
|
656
|
-
parts: List[str] = requirement_string.split("[")
|
|
657
|
-
location = "[".join(parts[:-1])
|
|
658
|
-
extras = f"[{parts[-1]}"
|
|
659
|
-
location = os.path.abspath(location)
|
|
660
|
-
name: str = get_setup_distribution_name(location)
|
|
661
|
-
assert name, f"No distribution found in {location}"
|
|
662
|
-
return Requirement(f"{name}{extras}")
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
def get_required_distribution_names(
|
|
666
|
-
requirement_string: str,
|
|
667
|
-
exclude: Iterable[str] = (),
|
|
668
|
-
recursive: bool = True,
|
|
669
|
-
echo: bool = False,
|
|
670
|
-
depth: Optional[int] = None,
|
|
671
|
-
) -> MutableSet[str]:
|
|
672
|
-
"""
|
|
673
|
-
Return a `tuple` of all distribution names which are required by the
|
|
674
|
-
distribution specified in `requirement_string`.
|
|
675
|
-
|
|
676
|
-
Parameters:
|
|
677
|
-
|
|
678
|
-
- requirement_string (str): A distribution name, or a requirement string
|
|
679
|
-
indicating both a distribution name and extras.
|
|
680
|
-
- exclude ([str]): The name of one or more distributions to *exclude*
|
|
681
|
-
from requirements lookup. Please note that excluding a distribution will
|
|
682
|
-
also halt recursive lookup of requirements for that distribution.
|
|
683
|
-
- recursive (bool): If `True` (the default), required distributions will
|
|
684
|
-
be obtained recursively.
|
|
685
|
-
- echo (bool) = False: If `True`, commands and responses executed in
|
|
686
|
-
subprocesses will be printed to `sys.stdout`
|
|
687
|
-
- depth (int|None) = None: The maximum depth of recursion to follow
|
|
688
|
-
requirements. If `None` (the default), recursion is not restricted.
|
|
689
|
-
"""
|
|
690
|
-
if isinstance(exclude, str):
|
|
691
|
-
exclude = set((normalize_name(exclude),))
|
|
692
|
-
else:
|
|
693
|
-
exclude = set(map(normalize_name, exclude))
|
|
694
|
-
return set(
|
|
695
|
-
_iter_requirement_names(
|
|
696
|
-
get_requirement(requirement_string),
|
|
697
|
-
exclude=exclude,
|
|
698
|
-
recursive=recursive,
|
|
699
|
-
echo=echo,
|
|
700
|
-
depth=depth,
|
|
701
|
-
)
|
|
702
|
-
)
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
def _get_requirement_name(requirement: Requirement) -> str:
|
|
706
|
-
return normalize_name(requirement.name)
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
def install_requirement(
|
|
710
|
-
requirement: Union[str, Requirement],
|
|
711
|
-
echo: bool = True,
|
|
712
|
-
) -> None:
|
|
713
|
-
"""
|
|
714
|
-
Install a requirement
|
|
715
|
-
|
|
716
|
-
Parameters:
|
|
717
|
-
|
|
718
|
-
- requirement (str)
|
|
719
|
-
- echo (bool) = True: If `True` (default), the `pip install`
|
|
720
|
-
commands will be echoed to `sys.stdout`
|
|
721
|
-
"""
|
|
722
|
-
if isinstance(requirement, str):
|
|
723
|
-
requirement = Requirement(requirement)
|
|
724
|
-
return _install_requirement(requirement)
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
def _install_requirement_string(
|
|
728
|
-
requirement_string: str,
|
|
729
|
-
name: str = "",
|
|
730
|
-
editable: bool = False,
|
|
731
|
-
) -> None:
|
|
732
|
-
"""
|
|
733
|
-
Install a requirement string with no dependencies, compilation, build
|
|
734
|
-
isolation, etc.
|
|
735
|
-
"""
|
|
736
|
-
command: Tuple[str, ...] = (
|
|
737
|
-
sys.executable,
|
|
738
|
-
"-m",
|
|
739
|
-
"pip",
|
|
740
|
-
"install",
|
|
741
|
-
"--no-deps",
|
|
742
|
-
"--no-compile",
|
|
743
|
-
"--no-build-isolation",
|
|
744
|
-
)
|
|
745
|
-
if editable:
|
|
746
|
-
command += (
|
|
747
|
-
"-e",
|
|
748
|
-
requirement_string,
|
|
749
|
-
)
|
|
750
|
-
if sys.version_info < (3, 9):
|
|
751
|
-
command += (
|
|
752
|
-
"--config-settings",
|
|
753
|
-
"editable_mode=compat",
|
|
754
|
-
)
|
|
755
|
-
else:
|
|
756
|
-
command += (requirement_string,)
|
|
757
|
-
try:
|
|
758
|
-
check_output(command)
|
|
759
|
-
except CalledProcessError as error:
|
|
760
|
-
message: str = (
|
|
761
|
-
(
|
|
762
|
-
f"\n{list2cmdline(command)}" f"\nCould not install {name}"
|
|
763
|
-
if name == requirement_string
|
|
764
|
-
else (
|
|
765
|
-
f"\n{list2cmdline(command)}"
|
|
766
|
-
f"\nCould not install {name} from "
|
|
767
|
-
f"{requirement_string}"
|
|
768
|
-
)
|
|
769
|
-
)
|
|
770
|
-
if name
|
|
771
|
-
else (
|
|
772
|
-
f"\n{list2cmdline(command)}"
|
|
773
|
-
f"\nCould not install {requirement_string}"
|
|
774
|
-
)
|
|
775
|
-
)
|
|
776
|
-
if not editable:
|
|
777
|
-
append_exception_text(
|
|
778
|
-
error,
|
|
779
|
-
message,
|
|
780
|
-
)
|
|
781
|
-
raise error
|
|
782
|
-
try:
|
|
783
|
-
check_output(command + ("--force-reinstall",))
|
|
784
|
-
except CalledProcessError as retry_error:
|
|
785
|
-
append_exception_text(
|
|
786
|
-
retry_error,
|
|
787
|
-
message,
|
|
788
|
-
)
|
|
789
|
-
raise retry_error
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
def _install_requirement(
|
|
793
|
-
requirement: Requirement,
|
|
794
|
-
) -> None:
|
|
795
|
-
requirement_string: str = str(requirement)
|
|
796
|
-
# Get the distribution name
|
|
797
|
-
distribution: Optional[Distribution] = None
|
|
798
|
-
editable_location: str = ""
|
|
799
|
-
try:
|
|
800
|
-
distribution = _get_distribution(requirement.name)
|
|
801
|
-
editable_location = get_editable_distribution_location(
|
|
802
|
-
distribution.metadata["Name"]
|
|
803
|
-
)
|
|
804
|
-
except (PackageNotFoundError, KeyError):
|
|
805
|
-
pass
|
|
806
|
-
# If the requirement is installed and editable, re-install from
|
|
807
|
-
# the editable location
|
|
808
|
-
if distribution and editable_location:
|
|
809
|
-
# Assemble a requirement specifier for the editable install
|
|
810
|
-
requirement_string = editable_location
|
|
811
|
-
if requirement.extras:
|
|
812
|
-
requirement_string = (
|
|
813
|
-
f"{requirement_string}[{','.join(requirement.extras)}]"
|
|
814
|
-
)
|
|
815
|
-
_install_requirement_string(
|
|
816
|
-
requirement_string=requirement_string,
|
|
817
|
-
name=normalize_name(requirement.name),
|
|
818
|
-
editable=bool(editable_location),
|
|
819
|
-
)
|
|
820
|
-
# Refresh the metadata
|
|
821
|
-
cache_clear()
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
def _get_requirement_distribution(
|
|
825
|
-
requirement: Requirement,
|
|
826
|
-
name: str,
|
|
827
|
-
reinstall: bool = True,
|
|
828
|
-
echo: bool = False,
|
|
829
|
-
) -> Optional[Distribution]:
|
|
830
|
-
if name in _BUILTIN_DISTRIBUTION_NAMES:
|
|
831
|
-
return None
|
|
832
|
-
try:
|
|
833
|
-
return get_installed_distributions()[name]
|
|
834
|
-
except KeyError:
|
|
835
|
-
if not reinstall:
|
|
836
|
-
raise
|
|
837
|
-
if echo:
|
|
838
|
-
warn(
|
|
839
|
-
f'The required distribution "{name}" was not installed, '
|
|
840
|
-
"attempting to install it now..."
|
|
841
|
-
)
|
|
842
|
-
# Attempt to install the requirement...
|
|
843
|
-
install_requirement(requirement, echo=echo)
|
|
844
|
-
return _get_requirement_distribution(
|
|
845
|
-
requirement, name, reinstall=False, echo=echo
|
|
846
|
-
)
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
def _iter_distribution_requirements(
|
|
850
|
-
distribution: Distribution,
|
|
851
|
-
extras: Tuple[str, ...] = (),
|
|
852
|
-
exclude: Container[str] = (),
|
|
853
|
-
) -> Iterable[Requirement]:
|
|
854
|
-
if not distribution.requires:
|
|
855
|
-
return
|
|
856
|
-
requirement: Requirement
|
|
857
|
-
for requirement in map(Requirement, distribution.requires):
|
|
858
|
-
if (
|
|
859
|
-
(requirement.marker is None)
|
|
860
|
-
or any(
|
|
861
|
-
requirement.marker.evaluate({"extra": extra})
|
|
862
|
-
for extra in extras
|
|
863
|
-
)
|
|
864
|
-
) and (normalize_name(requirement.name) not in exclude):
|
|
865
|
-
yield requirement
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
def _iter_requirement_names(
|
|
869
|
-
requirement: Requirement,
|
|
870
|
-
exclude: MutableSet[str],
|
|
871
|
-
recursive: bool = True,
|
|
872
|
-
echo: bool = False,
|
|
873
|
-
depth: Optional[int] = None,
|
|
874
|
-
) -> Iterable[str]:
|
|
875
|
-
name: str = normalize_name(requirement.name)
|
|
876
|
-
extras: Tuple[str, ...] = tuple(requirement.extras)
|
|
877
|
-
if name in exclude:
|
|
878
|
-
return ()
|
|
879
|
-
# Ensure we don't follow the same requirement again, causing cyclic
|
|
880
|
-
# recursion
|
|
881
|
-
exclude.add(name)
|
|
882
|
-
distribution: Optional[Distribution] = _get_requirement_distribution(
|
|
883
|
-
requirement, name, echo=echo
|
|
884
|
-
)
|
|
885
|
-
if distribution is None:
|
|
886
|
-
return ()
|
|
887
|
-
requirements: Tuple[Requirement, ...] = tuple(
|
|
888
|
-
iter_distinct(
|
|
889
|
-
_iter_distribution_requirements(
|
|
890
|
-
distribution,
|
|
891
|
-
extras=extras,
|
|
892
|
-
exclude=exclude,
|
|
893
|
-
),
|
|
894
|
-
)
|
|
895
|
-
)
|
|
896
|
-
lateral_exclude: MutableSet[str] = set()
|
|
897
|
-
|
|
898
|
-
def iter_requirement_names_(
|
|
899
|
-
requirement_: Requirement,
|
|
900
|
-
depth_: Optional[int] = None,
|
|
901
|
-
) -> Iterable[str]:
|
|
902
|
-
if (depth_ is None) or depth_ >= 0:
|
|
903
|
-
yield from _iter_requirement_names(
|
|
904
|
-
requirement_,
|
|
905
|
-
exclude=cast(
|
|
906
|
-
MutableSet[str],
|
|
907
|
-
exclude
|
|
908
|
-
| (
|
|
909
|
-
lateral_exclude
|
|
910
|
-
- set((_get_requirement_name(requirement_),))
|
|
911
|
-
),
|
|
912
|
-
),
|
|
913
|
-
recursive=recursive,
|
|
914
|
-
echo=echo,
|
|
915
|
-
depth=None if (depth_ is None) else depth_ - 1,
|
|
916
|
-
)
|
|
917
|
-
|
|
918
|
-
def not_excluded(name: str) -> bool:
|
|
919
|
-
if name not in exclude:
|
|
920
|
-
# Add this to the exclusions
|
|
921
|
-
lateral_exclude.add(name)
|
|
922
|
-
return True
|
|
923
|
-
return False
|
|
924
|
-
|
|
925
|
-
requirement_names: Iterable[str] = filter(
|
|
926
|
-
not_excluded, map(_get_requirement_name, requirements)
|
|
927
|
-
)
|
|
928
|
-
if recursive:
|
|
929
|
-
requirement_: Requirement
|
|
930
|
-
requirement_names = chain(
|
|
931
|
-
requirement_names,
|
|
932
|
-
*map(
|
|
933
|
-
lambda requirement_: iter_requirement_names_(
|
|
934
|
-
requirement_, None if (depth is None) else depth - 1
|
|
935
|
-
),
|
|
936
|
-
requirements,
|
|
937
|
-
),
|
|
938
|
-
)
|
|
939
|
-
return requirement_names
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
@deprecated()
|
|
943
|
-
def _iter_requirement_strings_required_distribution_names(
|
|
944
|
-
requirement_strings: Iterable[str],
|
|
945
|
-
echo: bool = False,
|
|
946
|
-
) -> Iterable[str]:
|
|
947
|
-
visited_requirement_strings: MutableSet[str] = set()
|
|
948
|
-
if isinstance(requirement_strings, str):
|
|
949
|
-
requirement_strings = (requirement_strings,)
|
|
950
|
-
|
|
951
|
-
def get_required_distribution_names_(
|
|
952
|
-
requirement_string: str,
|
|
953
|
-
) -> MutableSet[str]:
|
|
954
|
-
if requirement_string not in visited_requirement_strings:
|
|
955
|
-
try:
|
|
956
|
-
name: str = get_requirement_string_distribution_name(
|
|
957
|
-
requirement_string
|
|
958
|
-
)
|
|
959
|
-
visited_requirement_strings.add(requirement_string)
|
|
960
|
-
return cast(
|
|
961
|
-
MutableSet[str],
|
|
962
|
-
set((name,))
|
|
963
|
-
| get_required_distribution_names(
|
|
964
|
-
requirement_string, echo=echo
|
|
965
|
-
),
|
|
966
|
-
)
|
|
967
|
-
except KeyError:
|
|
968
|
-
pass
|
|
969
|
-
return set()
|
|
970
|
-
|
|
971
|
-
return iter_distinct(
|
|
972
|
-
chain(*map(get_required_distribution_names_, requirement_strings)),
|
|
973
|
-
)
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
@deprecated()
|
|
977
|
-
def get_requirements_required_distribution_names(
|
|
978
|
-
requirements: Iterable[str] = (),
|
|
979
|
-
echo: bool = False,
|
|
980
|
-
) -> MutableSet[str]:
|
|
981
|
-
"""
|
|
982
|
-
Get the distributions required by one or more specified distributions or
|
|
983
|
-
configuration files.
|
|
984
|
-
|
|
985
|
-
Parameters:
|
|
986
|
-
|
|
987
|
-
- requirements ([str]): One or more requirement specifiers (for example:
|
|
988
|
-
"requirement-name[extra-a,extra-b]" or ".[extra-a, extra-b]) and/or paths
|
|
989
|
-
to a setup.cfg, pyproject.toml, tox.ini or requirements.txt file
|
|
990
|
-
"""
|
|
991
|
-
# Separate requirement strings from requirement files
|
|
992
|
-
if isinstance(requirements, str):
|
|
993
|
-
requirements = set((requirements,))
|
|
994
|
-
else:
|
|
995
|
-
requirements = set(requirements)
|
|
996
|
-
requirement_files: MutableSet[str] = set(
|
|
997
|
-
filter(is_configuration_file, requirements)
|
|
998
|
-
)
|
|
999
|
-
requirement_strings: MutableSet[str] = cast(
|
|
1000
|
-
MutableSet[str], requirements - requirement_files
|
|
1001
|
-
)
|
|
1002
|
-
name: str
|
|
1003
|
-
return set(
|
|
1004
|
-
_iter_requirement_strings_required_distribution_names(
|
|
1005
|
-
iter_distinct(
|
|
1006
|
-
chain(
|
|
1007
|
-
requirement_strings,
|
|
1008
|
-
*map(
|
|
1009
|
-
iter_configuration_file_requirement_strings,
|
|
1010
|
-
requirement_files,
|
|
1011
|
-
),
|
|
1012
|
-
)
|
|
1013
|
-
),
|
|
1014
|
-
echo=echo,
|
|
1015
|
-
)
|
|
1016
|
-
)
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
@deprecated()
|
|
1020
|
-
def iter_distribution_location_file_paths(location: str) -> Iterable[str]:
|
|
1021
|
-
location = os.path.abspath(location)
|
|
1022
|
-
name: str = get_setup_distribution_name(location)
|
|
1023
|
-
setup_egg_info(location)
|
|
1024
|
-
metadata_path: str = os.path.join(
|
|
1025
|
-
location, f"{name.replace('-', '_')}.egg-info"
|
|
1026
|
-
)
|
|
1027
|
-
distribution: Distribution = Distribution.at(metadata_path)
|
|
1028
|
-
if not distribution.files:
|
|
1029
|
-
raise RuntimeError(f"No metadata found at {metadata_path}")
|
|
1030
|
-
path: str
|
|
1031
|
-
return map(
|
|
1032
|
-
lambda path: os.path.abspath(os.path.join(location, path)),
|
|
1033
|
-
distribution.files,
|
|
1034
|
-
)
|