Skip to content

Commit 5e48cf2

Browse files
committed
Merge latest main branch
Signed-off-by: Philippe Ombredanne <[email protected]>
2 parents 6581e2e + a0311e0 commit 5e48cf2

20 files changed

+780
-373
lines changed

README.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
python-inspector - inspect Python packages dependencies and metadata
1+
python-inspector - inspect Python package dependencies and metadata
22
=====================================================================
33

44

@@ -14,7 +14,7 @@ Homepage: https://github.com/aboutcode-org/python-inspector and https://www.abou
1414
- parse various requirements.txt files and setup.py files as input
1515
for resolving dependencies.
1616

17-
- parse additionally various manifests and packages files such as
17+
- parse various manifests and packages files such as
1818
Pipfile, pyproject.toml, poetry.lock and setup.cfg and legacy and
1919
current metadata file formats for eggs, wheels and sdist. These
2020
have not been wired with the command line yet.
@@ -35,7 +35,7 @@ Testing
3535

3636
pytest -vvs
3737

38-
- These are live tests to regenrate the tests with updated data run::
38+
- There are live tests to regenerate the tests with updated data run::
3939

4040
PYINSP_REGEN_TEST_FIXTURES=yes pytest -vvs
4141

src/python_inspector/api.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,7 @@ def resolve_dependencies(
153153
for req_file in requirement_files:
154154
deps = dependencies.get_dependencies_from_requirements(requirements_file=req_file)
155155
for extra_data in dependencies.get_extra_data_from_requirements(requirements_file=req_file):
156-
index_urls = (*index_urls, *tuple(extra_data.get("extra_index_urls") or []))
157-
index_urls = (*index_urls, *tuple(extra_data.get("index_url") or []))
156+
index_urls = get_index_urls(index_urls, extra_data)
158157
direct_dependencies.extend(deps)
159158
package_data = [
160159
pkg_data.to_dict() for pkg_data in PipRequirementsFileHandler.parse(location=req_file)
@@ -321,6 +320,25 @@ async def get_pypi_data(package):
321320
)
322321

323322

323+
def get_index_urls(index_urls: Tuple, extra_data: Dict) -> Tuple:
324+
"""
325+
Return a list of index URLs from the extra_data.
326+
The extra_data is a dictionary that may contain
327+
"extra_index_urls" and "index_url" keys.
328+
"""
329+
if isinstance(index_urls, str):
330+
index_urls = [index_urls]
331+
if isinstance(index_urls, tuple):
332+
index_urls = list(index_urls)
333+
extra_index_urls = extra_data.get("extra_index_urls") or []
334+
index_url = extra_data.get("index_url") or []
335+
if isinstance(extra_index_urls, list):
336+
index_urls = (*index_urls, *tuple(extra_index_urls))
337+
if isinstance(index_url, str):
338+
index_urls = (*index_urls, *tuple([index_url]))
339+
return tuple(set(index_urls))
340+
341+
324342
resolver_api = resolve_dependencies
325343

326344

src/python_inspector/resolution.py

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,6 @@ def get_requirements_from_python_manifest(
320320
and elem.value.func.id == "setup"
321321
)
322322
]
323-
if len(setup_fct) == 0:
324-
raise Exception(
325-
f"Unable to collect setup.py dependencies securely: {setup_py_location}"
326-
)
327323
if len(setup_fct) > 1:
328324
print(
329325
f"Warning: identified multiple definitions of 'setup()' in {setup_py_location}, "
@@ -333,20 +329,17 @@ def get_requirements_from_python_manifest(
333329
install_requires = [
334330
k.value for k in setup_fct.value.keywords if k.arg == "install_requires"
335331
]
336-
if len(install_requires) == 0:
337-
raise Exception(
338-
f"Unable to collect setup.py dependencies securely: {setup_py_location}"
339-
)
340-
if len(install_requires) > 1:
341-
print(
342-
f"Warning: identified multiple definitions of 'install_requires' in "
343-
"{setup_py_location}, defaulting to the first occurrence"
344-
)
345-
install_requires = install_requires[0].elts
346-
if len(install_requires) != 0:
347-
raise Exception(
348-
f"Unable to collect setup.py dependencies securely: {setup_py_location}"
349-
)
332+
if install_requires:
333+
if len(install_requires) > 1:
334+
print(
335+
f"Warning: identified multiple definitions of 'install_requires' in "
336+
"{setup_py_location}, defaulting to the first occurrence"
337+
)
338+
install_requires = install_requires[0].elts
339+
if len(install_requires) != 0:
340+
raise Exception(
341+
f"Unable to collect setup.py dependencies securely: {setup_py_location}"
342+
)
350343

351344

352345
DEFAULT_ENVIRONMENT = utils_pypi.Environment.from_pyver_and_os(

src/python_inspector/utils_pypi.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#
1111
import asyncio
1212
import email
13+
import hashlib
1314
import itertools
1415
import os
1516
import pathlib
@@ -1661,6 +1662,9 @@ class Cache:
16611662
def __attrs_post_init__(self):
16621663
os.makedirs(self.directory, exist_ok=True)
16631664

1665+
def sha256_hash(self, text: str) -> str:
1666+
return hashlib.sha256(text.encode()).hexdigest()
1667+
16641668
async def get(
16651669
self,
16661670
credentials,
@@ -1676,7 +1680,7 @@ async def get(
16761680
True otherwise as treat as binary. `path_or_url` can be a path or a URL
16771681
to a file.
16781682
"""
1679-
cache_key = quote_plus(path_or_url.strip("/"))
1683+
cache_key = self.sha256_hash(quote_plus(path_or_url.strip("/")))
16801684
cached = os.path.join(self.directory, cache_key)
16811685

16821686
if force or not os.path.exists(cached):

tests/data/azure-devops.req-310-expected.json

Lines changed: 57 additions & 62 deletions
Large diffs are not rendered by default.

tests/data/azure-devops.req-312-expected.json

Lines changed: 57 additions & 62 deletions
Large diffs are not rendered by default.

tests/data/azure-devops.req-313-expected.json

Lines changed: 57 additions & 62 deletions
Large diffs are not rendered by default.

tests/data/azure-devops.req-38-expected.json

Lines changed: 33 additions & 33 deletions
Large diffs are not rendered by default.

tests/data/example-requirements-ignore-errors-expected.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,12 @@
225225
"type": "pypi",
226226
"namespace": null,
227227
"name": "packaging",
228-
"version": "24.2",
228+
"version": "25.0",
229229
"qualifiers": {},
230230
"subpath": null,
231231
"primary_language": "Python",
232232
"description": "Core utilities for Python packages\npackaging\n=========\n\n.. start-intro\n\nReusable core utilities for various Python Packaging\n`interoperability specifications <https://packaging.python.org/specifications/>`_.\n\nThis library provides utilities that implement the interoperability\nspecifications which have clearly one correct behaviour (eg: :pep:`440`)\nor benefit greatly from having a single shared implementation (eg: :pep:`425`).\n\n.. end-intro\n\nThe ``packaging`` project includes the following: version handling, specifiers,\nmarkers, requirements, tags, utilities.\n\nDocumentation\n-------------\n\nThe `documentation`_ provides information and the API for the following:\n\n- Version Handling\n- Specifiers\n- Markers\n- Requirements\n- Tags\n- Utilities\n\nInstallation\n------------\n\nUse ``pip`` to install these utilities::\n\n pip install packaging\n\nThe ``packaging`` library uses calendar-based versioning (``YY.N``).\n\nDiscussion\n----------\n\nIf you run into bugs, you can file them in our `issue tracker`_.\n\nYou can also join ``#pypa`` on Freenode to ask questions or get involved.\n\n\n.. _`documentation`: https://packaging.pypa.io/\n.. _`issue tracker`: https://github.com/pypa/packaging/issues\n\n\nCode of Conduct\n---------------\n\nEveryone interacting in the packaging project's codebases, issue trackers, chat\nrooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.\n\n.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md\n\nContributing\n------------\n\nThe ``CONTRIBUTING.rst`` file outlines how to contribute to this project as\nwell as how to report a potential security issue. The documentation for this\nproject also covers information about `project development`_ and `security`_.\n\n.. _`project development`: https://packaging.pypa.io/en/latest/development/\n.. _`security`: https://packaging.pypa.io/en/latest/security/\n\nProject History\n---------------\n\nPlease review the ``CHANGELOG.rst`` file or the `Changelog documentation`_ for\nrecent changes and project history.\n\n.. _`Changelog documentation`: https://packaging.pypa.io/en/latest/changelog/",
233-
"release_date": "2024-11-08T09:47:44",
233+
"release_date": "2025-04-19T11:48:57",
234234
"parties": [
235235
{
236236
"type": "person",
@@ -257,11 +257,11 @@
257257
"Typing :: Typed"
258258
],
259259
"homepage_url": null,
260-
"download_url": "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl",
261-
"size": 65451,
260+
"download_url": "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl",
261+
"size": 66469,
262262
"sha1": null,
263-
"md5": "137b07612433f1ad2cd27dd8ab38ce49",
264-
"sha256": "09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759",
263+
"md5": "5fa4842e2eb0d7883b4b0e7c42d6229e",
264+
"sha256": "29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484",
265265
"sha512": null,
266266
"bug_tracking_url": null,
267267
"code_view_url": "https://github.com/pypa/packaging",
@@ -281,9 +281,9 @@
281281
"dependencies": [],
282282
"repository_homepage_url": null,
283283
"repository_download_url": null,
284-
"api_data_url": "https://pypi.org/pypi/packaging/24.2/json",
284+
"api_data_url": "https://pypi.org/pypi/packaging/25.0/json",
285285
"datasource_id": null,
286-
"purl": "pkg:pypi/packaging@24.2"
286+
"purl": "pkg:pypi/packaging@25.0"
287287
},
288288
{
289289
"type": "pypi",
@@ -492,7 +492,7 @@
492492
"dependencies": []
493493
},
494494
{
495-
"package": "pkg:pypi/packaging@24.2",
495+
"package": "pkg:pypi/packaging@25.0",
496496
"dependencies": []
497497
},
498498
{
@@ -504,7 +504,7 @@
504504
"dependencies": [
505505
"pkg:pypi/[email protected]",
506506
"pkg:pypi/[email protected]",
507-
"pkg:pypi/packaging@24.2",
507+
"pkg:pypi/packaging@25.0",
508508
"pkg:pypi/[email protected]",
509509
"pkg:pypi/[email protected]"
510510
]
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
{
2+
"headers": {
3+
"tool_name": "python-inspector",
4+
"tool_homepageurl": "https://github.com/aboutcode-org/python-inspector",
5+
"tool_version": "0.13.0",
6+
"options": [
7+
"--index-url https://pypi.org/simple",
8+
"--json <file>",
9+
"--operating-system linux",
10+
"--python-version 38",
11+
"--specifier crontab==1.0.4"
12+
],
13+
"notice": "Dependency tree generated with python-inspector.\npython-inspector is a free software tool from nexB Inc. and others.\nVisit https://github.com/aboutcode-org/python-inspector/ for support and download.",
14+
"warnings": [],
15+
"errors": []
16+
},
17+
"files": [],
18+
"packages": [
19+
{
20+
"type": "pypi",
21+
"namespace": null,
22+
"name": "crontab",
23+
"version": "1.0.4",
24+
"qualifiers": {},
25+
"subpath": null,
26+
"primary_language": "Python",
27+
"description": "Parse and use crontab schedules in Python\nCopyright 2011-2021 Josiah Carlson\n\nReleased under the LGPL license version 2.1 and version 3 (you can choose\nwhich you'd like to be bound under).\n\nDescription\n===========\n\nThis package intends to offer a method of parsing crontab schedule entries and\ndetermining when an item should next be run. More specifically, it calculates\na delay in seconds from when the .next() method is called to when the item\nshould next be executed.\n\nComparing the below chart to http://en.wikipedia.org/wiki/Cron#CRON_expression\nyou will note that W and # symbols are not supported.\n\n============= =========== ================= ============== ===========================\nField Name Mandatory Allowed Values Default Value Allowed Special Characters\n============= =========== ================= ============== ===========================\nSeconds No 0-59 0 \\* / , -\nMinutes Yes 0-59 N/A \\* / , -\nHours Yes 0-23 N/A \\* / , -\nDay of month Yes 1-31 N/A \\* / , - ? L\nMonth Yes 1-12 or JAN-DEC N/A \\* / , -\nDay of week Yes 0-6 or SUN-SAT N/A \\* / , - ? L\nYear No 1970-2099 * \\* / , -\n============= =========== ================= ============== ===========================\n\nIf your cron entry has 5 values, minutes-day of week are used, default seconds\nis and default year is appended. If your cron entry has 6 values, minutes-year\nare used, and default seconds are prepended.\n\nAs such, only 5-7 value crontab entries are accepted (and mangled to 7 values,\nas necessary).\n\n\nSample individual crontab fields\n================================\n\nExamples of supported entries are as follows::\n\n *\n */5\n 7/8\n 3-25/7\n 3,7,9\n 0-10,30-40/5\n\nFor month or day of week entries, 3 letter abbreviations of the month or day\ncan be used to the left of any optional / where a number could be used.\n\nFor days of the week::\n\n mon-fri\n sun-thu/2\n\nFor month::\n\n apr-jul\n mar-sep/3\n\nInstallation\n============\n\n::\n\n pip install crontab\n\n\nExample uses\n============\n\n::\n\n >>> from crontab import CronTab\n >>> from datetime import datetime\n >>> # define the crontab for 25 minutes past the hour every hour\n ... entry = CronTab('25 * * * *')\n >>> # find the delay from when this was run (around 11:13AM)\n ... entry.next()\n 720.81637899999998\n >>> # find the delay from when it was last scheduled\n ... entry.next(datetime(2011, 7, 17, 11, 25))\n 3600.0\n\n\n\n\nNotes\n=====\n\nAt most one of 'day of week' or 'day of month' can be a value other than '?'\nor '*'. We violate spec here and allow '*' to be an alias for '?', in the case\nwhere one of those values is specified (seeing as some platforms don't support\n'?').\n\nThis module also supports the convenient aliases::\n\n @yearly\n @annually\n @monthly\n @weekly\n @daily\n @hourly\n\nExample full crontab entries and their meanings::\n\n 30 */2 * * * -> 30 minutes past the hour every 2 hours\n 15,45 23 * * * -> 11:15PM and 11:45PM every day\n 0 1 ? * SUN -> 1AM every Sunday\n 0 1 * * SUN -> 1AM every Sunday (same as above)\n 0 0 1 jan/2 * 2011-2013 ->\n midnight on January 1, 2011 and the first of every odd month until\n the end of 2013\n 24 7 L * * -> 7:24 AM on the last day of every month\n 24 7 * * L5 -> 7:24 AM on the last friday of every month\n 24 7 * * Lwed-fri ->\n 7:24 AM on the last wednesday, thursday, and friday of every month",
28+
"release_date": "2025-04-09T18:23:59",
29+
"parties": [
30+
{
31+
"type": "person",
32+
"role": "author",
33+
"name": "Josiah Carlson",
34+
"email": "[email protected]",
35+
"url": null
36+
}
37+
],
38+
"keywords": [
39+
"Development Status :: 5 - Production/Stable",
40+
"Programming Language :: Python",
41+
"Programming Language :: Python :: 2.7",
42+
"Programming Language :: Python :: 3.10",
43+
"Programming Language :: Python :: 3.11",
44+
"Programming Language :: Python :: 3.12",
45+
"Programming Language :: Python :: 3.13",
46+
"Programming Language :: Python :: 3.2",
47+
"Programming Language :: Python :: 3.4",
48+
"Programming Language :: Python :: 3.5",
49+
"Programming Language :: Python :: 3.6",
50+
"Programming Language :: Python :: 3.7",
51+
"Programming Language :: Python :: 3.8",
52+
"Programming Language :: Python :: 3.9"
53+
],
54+
"homepage_url": "https://github.com/josiahcarlson/parse-crontab",
55+
"download_url": "https://files.pythonhosted.org/packages/1e/8b/3ea72ac8e26090b63779b4e0074af79b02bbbab7ddd01b36109bc0892d31/crontab-1.0.4.tar.gz",
56+
"size": 21677,
57+
"sha1": null,
58+
"md5": "ad190b69ff4199c44a5170daf896e73f",
59+
"sha256": "715b0e5e105bc62c9683cbb93c1cc5821e07a3e28d17404576d22dba7a896c92",
60+
"sha512": null,
61+
"bug_tracking_url": null,
62+
"code_view_url": null,
63+
"vcs_url": null,
64+
"copyright": null,
65+
"license_expression": null,
66+
"declared_license": {
67+
"license": "GNU LGPL v2.1",
68+
"classifiers": [
69+
"License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)",
70+
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
71+
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)"
72+
]
73+
},
74+
"notice_text": null,
75+
"source_packages": [],
76+
"file_references": [],
77+
"extra_data": {},
78+
"dependencies": [],
79+
"repository_homepage_url": null,
80+
"repository_download_url": null,
81+
"api_data_url": "https://pypi.org/pypi/crontab/1.0.4/json",
82+
"datasource_id": null,
83+
"purl": "pkg:pypi/[email protected]"
84+
}
85+
],
86+
"resolved_dependencies_graph": [
87+
{
88+
"package": "pkg:pypi/[email protected]",
89+
"dependencies": []
90+
}
91+
]
92+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
[
2+
[
3+
{
4+
"package": "pkg:pypi/[email protected]",
5+
"dependencies": [
6+
"pkg:pypi/[email protected]",
7+
"pkg:pypi/[email protected]",
8+
"pkg:pypi/[email protected]",
9+
"pkg:pypi/[email protected]"
10+
]
11+
},
12+
{
13+
"package": "pkg:pypi/[email protected]",
14+
"dependencies": [
15+
"pkg:pypi/[email protected]"
16+
]
17+
},
18+
{
19+
"package": "pkg:pypi/[email protected]",
20+
"dependencies": [
21+
"pkg:pypi/[email protected]"
22+
]
23+
},
24+
{
25+
"package": "pkg:pypi/[email protected]",
26+
"dependencies": [
27+
"pkg:pypi/[email protected]"
28+
]
29+
},
30+
{
31+
"package": "pkg:pypi/[email protected]",
32+
"dependencies": []
33+
},
34+
{
35+
"package": "pkg:pypi/[email protected]",
36+
"dependencies": []
37+
},
38+
{
39+
"package": "pkg:pypi/[email protected]",
40+
"dependencies": []
41+
},
42+
{
43+
"package": "pkg:pypi/[email protected]",
44+
"dependencies": []
45+
}
46+
],
47+
[
48+
"pkg:pypi/[email protected]",
49+
"pkg:pypi/[email protected]",
50+
"pkg:pypi/[email protected]",
51+
"pkg:pypi/[email protected]",
52+
"pkg:pypi/[email protected]",
53+
"pkg:pypi/[email protected]",
54+
"pkg:pypi/[email protected]",
55+
"pkg:pypi/[email protected]"
56+
]
57+
]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
[
2+
[
3+
{
4+
"package": "pkg:pypi/[email protected]",
5+
"dependencies": []
6+
},
7+
{
8+
"package": "pkg:pypi/[email protected]",
9+
"dependencies": [
10+
"pkg:pypi/[email protected]",
11+
"pkg:pypi/[email protected]",
12+
"pkg:pypi/[email protected]",
13+
"pkg:pypi/[email protected]"
14+
]
15+
},
16+
{
17+
"package": "pkg:pypi/[email protected]",
18+
"dependencies": []
19+
},
20+
{
21+
"package": "pkg:pypi/[email protected]",
22+
"dependencies": [
23+
"pkg:pypi/[email protected]"
24+
]
25+
},
26+
{
27+
"package": "pkg:pypi/[email protected]",
28+
"dependencies": []
29+
},
30+
{
31+
"package": "pkg:pypi/[email protected]",
32+
"dependencies": [
33+
"pkg:pypi/[email protected]"
34+
]
35+
}
36+
],
37+
[
38+
"pkg:pypi/[email protected]",
39+
"pkg:pypi/[email protected]",
40+
"pkg:pypi/[email protected]",
41+
"pkg:pypi/[email protected]",
42+
"pkg:pypi/[email protected]",
43+
"pkg:pypi/[email protected]"
44+
]
45+
]

0 commit comments

Comments
 (0)