Skip to content

Commit c9165be

Browse files
committed
Refine settings handling
* Do not hide settings module imports with settings variable * Update requirements for pydantic and pydantic_settings * Restore tests on Python 3.8 * Regenerate tests expectations * Remove code formatting changes and minor typing changes * Improve support for defaults and index_urls Signed-off-by: Philippe Ombredanne <pombredanne@nexb.com>
1 parent 1074689 commit c9165be

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+4988
-5297
lines changed

CHANGELOG.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,38 @@
11
Changelog
22
=========
33

4+
v0.13.0
5+
-----------
6+
7+
- Speed up downloads with asyncio
8+
9+
- New settings featuring environment variables and .env file to store settings and defaults.
10+
11+
- This also changes the CACHE_THIRDPARTY_DIR environment variable: it used to default first
12+
to ".cache/python_inspector" and if not writable, it would fallback to home
13+
"~/.cache/python_inspector". The new behavior is to only use the "~/.cache/python_inspector"
14+
in the home directory. You can configure this directory to any other value.
15+
16+
- Another change is that pypi.org is no longer systematically added as an index URL for
17+
resolution. Instead the list of configured index URLs is used, and only defaults to pypi.org
18+
if not provided.
19+
20+
- Another change is that it is possible to only use the provided or configured index URLs
21+
and skip other URLs from requirements that are not in these configured URLs.
22+
23+
- Calling utils_pypi.download_sdist or utils_pypi.download_wheel requires a non-empty list
24+
of PypiSimpleRepository.
25+
26+
- python_inspector.utils_pypi.Distribution.download_url is now a method, not a property
27+
28+
- Drop support for running on Python 3.8. You can still resolve dependencies for Python 3.8.
29+
The default command line tool Python version used for resolution is now 3.9.
30+
31+
- Add support for the latest Python and OS versions.
32+
33+
- Merge latest skeleton and adopt ruff for code formatting.
34+
35+
436
v0.12.1
537
-----------
638

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ packaging==21.3
1414
packvers==21.5
1515
pip-requirements-parser==32.0.1
1616
pkginfo2==30.0.0
17-
pydantic_settings >= 2.6.1
17+
pydantic_settings == 2.8.1
18+
pydantic == 2.11.2
1819
pyparsing==3.0.9
1920
PyYAML==6.0
2021
requests==2.28.1

setup.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ install_requires =
6969
toml >= 0.10.0
7070
mock >= 3.0.5
7171
packvers >= 21.5
72+
pydantic >= 2.10.0
73+
pydantic_settings >= 2.8.0
74+
7275
[options.packages.find]
7376
where = src
7477

src/python_inspector/__init__.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,9 @@
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
99

10-
from pydantic import ValidationError
11-
12-
from python_inspector.settings import Settings
10+
from python_inspector import settings
1311

1412
# Initialize global settings
15-
try:
16-
settings = Settings()
17-
except ValidationError as e:
18-
print(e)
13+
pyinspector_settings = settings.Settings()
14+
15+
settings.create_cache_directory(pyinspector_settings.CACHE_THIRDPARTY_DIR)

src/python_inspector/api.py

Lines changed: 36 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from _packagedcode.pypi import PythonSetupPyHandler
2828
from _packagedcode.pypi import can_process_dependent_package
2929
from python_inspector import dependencies
30-
from python_inspector import settings
30+
from python_inspector import pyinspector_settings as settings
3131
from python_inspector import utils
3232
from python_inspector import utils_pypi
3333
from python_inspector.package_data import get_pypi_data_from_purl
@@ -64,8 +64,7 @@ def to_dict(self, generic_paths=False):
6464
# clean file paths
6565
for file in files:
6666
path = file["path"]
67-
file["path"] = utils.remove_test_data_dir_variable_prefix(
68-
path=path)
67+
file["path"] = utils.remove_test_data_dir_variable_prefix(path=path)
6968
return {
7069
"files": files,
7170
"packages": [package for package in self.packages],
@@ -102,19 +101,19 @@ def resolve_dependencies(
102101
linux OS.
103102
104103
Download from the provided PyPI simple index_urls INDEX(s) URLs defaulting
105-
to PyPI.org
104+
to PyPI.org or a configured setting.
106105
"""
107106

108107
if not operating_system:
109-
raise Exception("No operating system provided.")
108+
raise Exception(f"No operating system provided.")
110109
if operating_system not in PLATFORMS_BY_OS:
111110
raise ValueError(
112111
f"Invalid operating system: {operating_system}. "
113112
f"Must be one of: {', '.join(PLATFORMS_BY_OS.keys())}"
114113
)
115114

116115
if not python_version:
117-
raise Exception("No python version provided.")
116+
raise Exception(f"No python version provided.")
118117
if python_version not in valid_python_versions:
119118
raise ValueError(
120119
f"Invalid python version: {python_version}. "
@@ -149,22 +148,16 @@ def resolve_dependencies(
149148

150149
# requirements
151150
for req_file in requirement_files:
152-
deps = dependencies.get_dependencies_from_requirements(
153-
requirements_file=req_file)
154-
for extra_data in dependencies.get_extra_data_from_requirements(
155-
requirements_file=req_file
156-
):
157-
index_urls = (
158-
*index_urls, *tuple(extra_data.get("extra_index_urls") or []))
159-
index_urls = (
160-
*index_urls, *tuple(extra_data.get("index_url") or []))
151+
deps = dependencies.get_dependencies_from_requirements(requirements_file=req_file)
152+
for extra_data in dependencies.get_extra_data_from_requirements(requirements_file=req_file):
153+
index_urls = (*index_urls, *tuple(extra_data.get("extra_index_urls") or []))
154+
index_urls = (*index_urls, *tuple(extra_data.get("index_url") or []))
161155
direct_dependencies.extend(deps)
162156
package_data = [
163157
pkg_data.to_dict() for pkg_data in PipRequirementsFileHandler.parse(location=req_file)
164158
]
165159
if generic_paths:
166-
req_file = utils.remove_test_data_dir_variable_prefix(
167-
path=req_file)
160+
req_file = utils.remove_test_data_dir_variable_prefix(path=req_file)
168161

169162
files.append(
170163
dict(
@@ -217,15 +210,13 @@ def resolve_dependencies(
217210
files=[setup_py_file],
218211
analyze_setup_py_insecurely=analyze_setup_py_insecurely,
219212
)
220-
setup_py_file_deps = list(
221-
get_dependent_packages_from_reqs(reqs))
213+
setup_py_file_deps = list(get_dependent_packages_from_reqs(reqs))
222214
direct_dependencies.extend(setup_py_file_deps)
223215

224216
package_data.dependencies = setup_py_file_deps
225217
file_package_data = [package_data.to_dict()]
226218
if generic_paths:
227-
setup_py_file = utils.remove_test_data_dir_variable_prefix(
228-
path=setup_py_file)
219+
setup_py_file = utils.remove_test_data_dir_variable_prefix(path=setup_py_file)
229220
files.append(
230221
dict(
231222
type="file",
@@ -254,29 +245,32 @@ def resolve_dependencies(
254245
if verbose:
255246
printer(f"environment: {environment}")
256247

257-
repos = []
248+
repos_by_url = {}
258249
if not use_pypi_json_api:
259250
# Collect PyPI repos
251+
use_only_confed = settings.USE_ONLY_CONFIGURED_INDEX_URLS
260252
for index_url in index_urls:
261253
index_url = index_url.strip("/")
262-
if index_url in settings.INDEX_URL:
263-
repos.append(PypiSimpleRepository(index_url))
264-
else:
265-
credentials = None
266-
if parsed_netrc:
267-
login, password = utils.get_netrc_auth(
268-
index_url, parsed_netrc)
269-
credentials = (
270-
dict(login=login,
271-
password=password) if login and password else None
272-
)
273-
repo = PypiSimpleRepository(
274-
index_url=index_url,
275-
use_cached_index=use_cached_index,
276-
credentials=credentials,
277-
)
278-
repos.append(repo)
254+
if use_only_confed and index_url not in settings.INDEX_URL:
255+
if verbose:
256+
printer(f"Skipping index URL unknown in settings: {index_url!r}")
257+
continue
258+
if index_url in repos_by_url:
259+
continue
260+
261+
credentials = None
262+
if parsed_netrc:
263+
login, password = utils.get_netrc_auth(index_url, parsed_netrc)
264+
if login and password:
265+
credentials = dict(login=login, password=password)
266+
repo = utils_pypi.PypiSimpleRepository(
267+
index_url=index_url,
268+
use_cached_index=use_cached_index,
269+
credentials=credentials,
270+
)
271+
repos_by_url[index_url] = repo
279272

273+
repos = repos_by_url.values()
280274
if verbose:
281275
printer("repos:")
282276
for repo in repos:
@@ -363,8 +357,8 @@ def resolve(
363357

364358
def get_resolved_dependencies(
365359
requirements: List[Requirement],
366-
environment: Environment,
367-
repos: Sequence[PypiSimpleRepository] = tuple(),
360+
environment: Environment = None,
361+
repos: Sequence[utils_pypi.PypiSimpleRepository] = tuple(),
368362
as_tree: bool = False,
369363
max_rounds: int = 200000,
370364
pdt_output: bool = False,
@@ -379,7 +373,6 @@ def get_resolved_dependencies(
379373
Used the provided ``repos`` list of PypiSimpleRepository.
380374
If empty, use instead the PyPI.org JSON API exclusively instead
381375
"""
382-
383376
resolver = Resolver(
384377
provider=PythonInputProvider(
385378
environment=environment,
@@ -389,12 +382,8 @@ def get_resolved_dependencies(
389382
),
390383
reporter=BaseReporter(),
391384
)
392-
393-
resolver_results = resolver.resolve(
394-
requirements=requirements, max_rounds=max_rounds)
395-
385+
resolver_results = resolver.resolve(requirements=requirements, max_rounds=max_rounds)
396386
package_list = get_package_list(results=resolver_results)
397-
398387
if pdt_output:
399388
return (format_pdt_tree(resolver_results), package_list)
400389
return (

src/python_inspector/cli_utils.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ class FileOptionType(click.File):
2121

2222
def convert(self, value, param, ctx):
2323
known_opts = set(
24-
chain.from_iterable(
25-
p.opts for p in ctx.command.params if isinstance(p, click.Option))
24+
chain.from_iterable(p.opts for p in ctx.command.params if isinstance(p, click.Option))
2625
)
2726
if value in known_opts:
2827
self.fail(

src/python_inspector/package_data.py

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
# See https://aboutcode.org for more information about nexB OSS projects.
1010
#
1111

12-
from collections.abc import Generator
1312
from typing import List
1413

1514
from packageurl import PackageURL
@@ -26,10 +25,7 @@
2625

2726

2827
def get_pypi_data_from_purl(
29-
purl: str,
30-
environment: Environment,
31-
repos: list[PypiSimpleRepository],
32-
prefer_source: bool,
28+
purl: str, environment: Environment, repos: List[PypiSimpleRepository], prefer_source: bool
3329
) -> PackageData:
3430
"""
3531
Generate `Package` object from the `purl` string of pypi type
@@ -40,9 +36,9 @@ def get_pypi_data_from_purl(
4036
``prefer_source`` is a boolean value to prefer source distribution over wheel,
4137
if no source distribution is available then wheel is used
4238
"""
43-
packageurl: PackageURL = PackageURL.from_string(purl)
44-
name = packageurl.name
45-
version = packageurl.version
39+
purl = PackageURL.from_string(purl)
40+
name = purl.name
41+
version = purl.version
4642
if not version:
4743
raise Exception("Version is not specified in the purl")
4844
base_path = "https://pypi.org/pypi"
@@ -57,14 +53,12 @@ def get_pypi_data_from_purl(
5753
project_urls = info.get("project_urls") or {}
5854
code_view_url = get_pypi_codeview_url(project_urls)
5955
bug_tracking_url = get_pypi_bugtracker_url(project_urls)
60-
python_version = get_python_version_from_env_tag(
61-
python_version=environment.python_version
62-
)
56+
python_version = get_python_version_from_env_tag(python_version=environment.python_version)
6357
valid_distribution_urls = []
6458

6559
valid_distribution_urls.append(
6660
get_sdist_download_url(
67-
purl=packageurl,
61+
purl=purl,
6862
repos=repos,
6963
python_version=python_version,
7064
)
@@ -75,7 +69,7 @@ def get_pypi_data_from_purl(
7569
if not valid_distribution_urls or not prefer_source:
7670
wheel_urls = list(
7771
get_wheel_download_urls(
78-
purl=packageurl,
72+
purl=purl,
7973
repos=repos,
8074
environment=environment,
8175
python_version=python_version,
@@ -113,7 +107,7 @@ def get_pypi_data_from_purl(
113107
maintainer_key="maintainer",
114108
maintainer_email_key="maintainer_email",
115109
),
116-
**packageurl.to_dict(),
110+
**purl.to_dict(),
117111
)
118112

119113

@@ -149,7 +143,7 @@ def get_wheel_download_urls(
149143
repos: List[PypiSimpleRepository],
150144
environment: Environment,
151145
python_version: str,
152-
) -> Generator[str, None, None]:
146+
) -> List[str]:
153147
"""
154148
Return a list of download urls for the given purl.
155149
"""
@@ -161,7 +155,7 @@ def get_wheel_download_urls(
161155
environment=environment,
162156
python_version=python_version,
163157
):
164-
yield wheel.download_url
158+
yield wheel.download_url(repo)
165159

166160

167161
def get_sdist_download_url(
@@ -178,4 +172,4 @@ def get_sdist_download_url(
178172
python_version=python_version,
179173
)
180174
if sdist:
181-
return sdist.download_url
175+
return sdist.download_url(repo)

0 commit comments

Comments
 (0)