Skip to content

Commit 157540b

Browse files
committed
Add support for pylock.toml (fixes #4638)
1 parent 10afc59 commit 157540b

9 files changed

Lines changed: 144 additions & 0 deletions

File tree

src/packagedcode/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@
3434
from packagedcode import phpcomposer
3535
from packagedcode import pubspec
3636
from packagedcode import pypi
37+
from packagedcode import pypi
3738
from packagedcode import readme
3839
from packagedcode import rpm
3940
from packagedcode import rubygems
4041
from packagedcode import swift
4142
from packagedcode import win_pe
4243
from packagedcode import windows
44+
from packagedcode.pylock import parse_pylock
45+
4346

4447
if on_linux:
4548
from packagedcode import msi
@@ -212,6 +215,7 @@
212215

213216
# These are handlers for deplock generated files
214217
pypi.PipInspectDeplockHandler,
218+
pypi.PylockTomlHandler,
215219
]
216220

217221
if on_linux:

src/packagedcode/pypi.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,44 @@ def parse(cls, location, package_only=False):
832832
yield models.PackageData.from_data(package_data, package_only)
833833

834834

835+
class PylockTomlHandler(models.DatafileHandler):
836+
datasource_id = 'pypi_pylock_toml'
837+
path_patterns = ('*pylock.toml',)
838+
default_package_type = 'pypi'
839+
default_primary_language = 'Python'
840+
description = 'Python pylock.toml'
841+
documentation_url = 'https://github.com/nexB/scancode-toolkit'
842+
843+
@classmethod
844+
def parse(cls, location, package_only=False):
845+
pylock_data = parse_pylock(location)
846+
if not pylock_data:
847+
return
848+
849+
dependencies = []
850+
for package_name, package_info in pylock_data.get('package', {}).items():
851+
version = package_info.get('version')
852+
purl = PackageURL(type='pypi', name=package_name, version=version)
853+
dependency = models.DependentPackage(
854+
purl=purl.to_string(),
855+
extracted_requirement=f'{package_name}=={version}' if version else package_name,
856+
scope='install',
857+
is_runtime=True,
858+
is_optional=False,
859+
is_direct=True,
860+
is_pinned=bool(version),
861+
)
862+
dependencies.append(dependency.to_dict())
863+
864+
package_data = dict(
865+
datasource_id=cls.datasource_id,
866+
type=cls.default_package_type,
867+
primary_language='Python',
868+
dependencies=dependencies,
869+
extra_data=pylock_data,
870+
)
871+
yield models.PackageData.from_data(package_data, package_only)
872+
835873
class PipInspectDeplockHandler(models.DatafileHandler):
836874
datasource_id = 'pypi_inspect_deplock'
837875
path_patterns = ('*pip-inspect.deplock',)

src/packagedcode/python.pyx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
requirements
2+
3+
4+
5+
6+

src/scancode/api.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from commoncode.hash import multi_checksums
1717
from scancode import ScancodeError
1818
from typecode.contenttype import get_type
19+
from scancode.pylock import parse_pylock
1920

2021
TRACE = os.environ.get('SCANCODE_DEBUG_API', False)
2122

@@ -333,9 +334,25 @@ def get_package_data(
333334
**kwargs,
334335
) or []
335336

337+
# get pylock data from the `pylock.toml` file
338+
pylock_data = get_pylock_data(location)
339+
340+
if pylock_data:
341+
package_datas.append(pylock_data)
342+
336343
return dict(package_data=[pd.to_dict() for pd in package_datas])
337344

338345

346+
def get_pylock_data(location):
347+
"""
348+
Return a mapping of pylock data from the `pylock.toml` file at `location`.
349+
"""
350+
pylock_location = os.path.join(location, "pylock.toml")
351+
if os.path.exists(pylock_location):
352+
return parse_pylock(pylock_location)
353+
return {}
354+
355+
339356
def get_file_info(location, **kwargs):
340357
"""
341358
Return a mapping of file information collected for the file at `location`.

src/scancode/pylock.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# ScanCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/nexB/scancode-toolkit for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
import tomli
11+
12+
def parse_pylock(location):
13+
"""
14+
Parse a pylock.toml file and return its content.
15+
"""
16+
with open(location, "rb") as fp:
17+
data = tomli.load(fp)
18+
return data

src/scancode/pylock.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[package.requests]
2+
version = "2.31.0"
3+
4+
[package.numpy]
5+
version = "1.26.0"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[package.requests]
2+
version = "2.31.0"
3+
4+
[package.numpy]
5+
version = "1.26.0"
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[
2+
{
3+
"datasource_id": "pypi_pylock_toml",
4+
"type": "pypi",
5+
"primary_language": "Python",
6+
"dependencies": [
7+
{
8+
"purl": "pkg:pypi/requests@2.31.0",
9+
"extracted_requirement": "requests==2.31.0",
10+
"scope": "install",
11+
"is_runtime": true,
12+
"is_optional": false,
13+
"is_direct": true,
14+
"is_pinned": true
15+
},
16+
{
17+
"purl": "pkg:pypi/numpy@1.26.0",
18+
"extracted_requirement": "numpy==1.26.0",
19+
"scope": "install",
20+
"is_runtime": true,
21+
"is_optional": false,
22+
"is_direct": true,
23+
"is_pinned": true
24+
}
25+
],
26+
"extra_data": {
27+
"package": {
28+
"requests": {
29+
"version": "2.31.0"
30+
},
31+
"numpy": {
32+
"version": "1.26.0"
33+
}
34+
}
35+
}
36+
}
37+
]

tests/packagedcode/test_pypi.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from commoncode.system import on_windows
1616

1717
from packagedcode import pypi
18+
from scancode.pylock import parse_pylock
1819
from packages_test_utils import check_result_equals_expected_json
1920
from packages_test_utils import PackageTester
2021
from scancode_config import REGEN_TEST_FIXTURES
@@ -428,6 +429,19 @@ def test_parse_pip_inspect_deplock_univers(self):
428429
class TestPipRequirementsFileHandler(PackageTester):
429430
test_data_dir = os.path.join(os.path.dirname(__file__), 'data')
430431

432+
class TestPylockTomlHandler(PackageTester):
433+
test_data_dir = os.path.join(os.path.dirname(__file__), 'data')
434+
435+
def test_is_pylock_toml(self):
436+
test_file = self.get_test_loc('pypi/pylock/pylock.toml')
437+
assert pypi.PylockTomlHandler.is_datafile(test_file)
438+
439+
def test_parse_pylock_toml(self):
440+
test_file = self.get_test_loc('pypi/pylock/pylock.toml')
441+
package = pypi.PylockTomlHandler.parse(test_file)
442+
expected_loc = self.get_test_loc('pypi/pylock/pylock.toml-expected.json')
443+
self.check_packages_data(package, expected_loc, regen=REGEN_TEST_FIXTURES)
444+
431445
def test_python_requirements_is_package_data_file(self):
432446
test_file = self.get_test_loc('pypi/requirements_txt/basic/requirements.txt')
433447
assert pypi.PipRequirementsFileHandler.is_datafile(test_file)

0 commit comments

Comments
 (0)