diff --git a/src/packagedcode/pypi.py b/src/packagedcode/pypi.py index b5588ed7ca9..59823c2da6a 100644 --- a/src/packagedcode/pypi.py +++ b/src/packagedcode/pypi.py @@ -832,6 +832,238 @@ def parse(cls, location, package_only=False): yield models.PackageData.from_data(package_data, package_only) +class PylockTomlHandler(models.DatafileHandler): + datasource_id = 'pypi_pylock_toml' + path_patterns = ('*pylock.toml', '*pylock.*.toml',) + default_package_type = 'pypi' + default_primary_language = 'Python' + description = 'Python pylock.toml lockfile (PEP 751)' + documentation_url = 'https://peps.python.org/pep-0751/' + + @classmethod + def parse(cls, location, package_only=False): + """ + Parse a pylock.toml file according to PEP 751. + + The pylock.toml format records Python dependencies for reproducible installation. + Each package entry contains name, version, and optionally wheels, sdist, + or other source information. + """ + with open(location, "rb") as fp: + toml_data = tomllib.load(fp) + + lock_version = toml_data.get('lock-version') + created_by = toml_data.get('created-by') + requires_python = toml_data.get('requires-python') + environments = toml_data.get('environments', []) + extras = toml_data.get('extras', []) + dependency_groups = toml_data.get('dependency-groups', []) + default_groups = toml_data.get('default-groups', []) + + packages = toml_data.get('packages', []) + if not packages: + # Still yield a package data even if no packages + extra_data = { + 'lock_version': lock_version, + 'created_by': created_by, + } + if requires_python: + extra_data['requires_python'] = requires_python + if environments: + extra_data['environments'] = environments + if extras: + extra_data['extras'] = extras + if dependency_groups: + extra_data['dependency_groups'] = dependency_groups + if default_groups: + extra_data['default_groups'] = default_groups + + package_data = dict( + datasource_id=cls.datasource_id, + type=cls.default_package_type, + primary_language=cls.default_primary_language, + extra_data=extra_data, + dependencies=[], + ) + yield models.PackageData.from_data(package_data, package_only) + return + + dependencies = [] + for package in packages: + name = package.get('name') + version = package.get('version') + marker = package.get('marker') + pkg_requires_python = package.get('requires-python') + index = package.get('index') + + # Get source information (wheels, sdist, vcs, directory, archive) + wheels = package.get('wheels', []) + sdist = package.get('sdist') + vcs = package.get('vcs') + directory = package.get('directory') + archive = package.get('archive') + + # Get package dependencies + pkg_dependencies = package.get('dependencies', []) + dependencies_for_resolved = [] + for dep in pkg_dependencies: + dep_name = dep.get('name') + if dep_name: + dep_purl = PackageURL( + type=cls.default_package_type, + name=canonicalize_name(dep_name), + ) + dep_version = dep.get('version') + if dep_version: + dep_purl = dep_purl._replace(version=dep_version) + + dep_obj = models.DependentPackage( + purl=dep_purl.to_string(), + scope="dependencies", + is_runtime=True, + is_optional=False, + is_direct=True, + is_pinned=bool(dep_version), + ) + dependencies_for_resolved.append(dep_obj.to_dict()) + + # Build download URL from wheels or sdist + download_url = None + sha256 = None + file_references = [] + + if wheels: + for wheel in wheels: + wheel_url = wheel.get('url') + wheel_path = wheel.get('path') + wheel_name = wheel.get('name') + wheel_size = wheel.get('size') + wheel_hashes = wheel.get('hashes', {}) + + file_ref = {} + if wheel_name: + file_ref['name'] = wheel_name + if wheel_url: + file_ref['url'] = wheel_url + if not download_url: + download_url = wheel_url + if wheel_path: + file_ref['path'] = wheel_path + if wheel_size: + file_ref['size'] = wheel_size + if wheel_hashes: + file_ref['hashes'] = wheel_hashes + if 'sha256' in wheel_hashes and not sha256: + sha256 = wheel_hashes['sha256'] + if file_ref: + file_references.append({'wheel': file_ref}) + + if sdist: + sdist_url = sdist.get('url') + sdist_path = sdist.get('path') + sdist_name = sdist.get('name') + sdist_size = sdist.get('size') + sdist_hashes = sdist.get('hashes', {}) + + file_ref = {} + if sdist_name: + file_ref['name'] = sdist_name + if sdist_url: + file_ref['url'] = sdist_url + if not download_url: + download_url = sdist_url + if sdist_path: + file_ref['path'] = sdist_path + if sdist_size: + file_ref['size'] = sdist_size + if sdist_hashes: + file_ref['hashes'] = sdist_hashes + if 'sha256' in sdist_hashes and not sha256: + sha256 = sdist_hashes['sha256'] + if file_ref: + file_references.append({'sdist': file_ref}) + + # Build extra_data for the resolved package + resolved_extra_data = {} + if marker: + resolved_extra_data['marker'] = marker + if pkg_requires_python: + resolved_extra_data['requires_python'] = pkg_requires_python + if index: + resolved_extra_data['index'] = index + if file_references: + resolved_extra_data['file_references'] = file_references + if vcs: + resolved_extra_data['vcs'] = vcs + if directory: + resolved_extra_data['directory'] = directory + if archive: + resolved_extra_data['archive'] = archive + + # Get URLs for PyPI + urls = get_pypi_urls(name, version) if name else {} + + resolved_package_data = dict( + datasource_id=cls.datasource_id, + type=cls.default_package_type, + primary_language=cls.default_primary_language, + name=name, + version=version, + is_virtual=True, + download_url=download_url, + sha256=sha256, + dependencies=dependencies_for_resolved, + extra_data=resolved_extra_data if resolved_extra_data else None, + **urls, + ) + resolved_package = models.PackageData.from_data(resolved_package_data, package_only) + + # Create a dependency entry for the resolved package + if name: + purl = PackageURL( + type=cls.default_package_type, + name=canonicalize_name(name), + version=version, + ) + + dependency = models.DependentPackage( + purl=purl.to_string(), + scope=None, + is_runtime=True, + is_optional=False, + is_direct=False, + is_pinned=bool(version), + resolved_package=resolved_package.to_dict() + ) + dependencies.append(dependency.to_dict()) + + # Build extra_data for the lockfile itself + extra_data = { + 'lock_version': lock_version, + } + if created_by: + extra_data['created_by'] = created_by + if requires_python: + extra_data['requires_python'] = requires_python + if environments: + extra_data['environments'] = environments + if extras: + extra_data['extras'] = extras + if dependency_groups: + extra_data['dependency_groups'] = dependency_groups + if default_groups: + extra_data['default_groups'] = default_groups + + package_data = dict( + datasource_id=cls.datasource_id, + type=cls.default_package_type, + primary_language=cls.default_primary_language, + extra_data=extra_data, + dependencies=dependencies, + ) + yield models.PackageData.from_data(package_data, package_only) + + class PipInspectDeplockHandler(models.DatafileHandler): datasource_id = 'pypi_inspect_deplock' path_patterns = ('*pip-inspect.deplock',) diff --git a/tests/packagedcode/data/pypi/pylock-toml/pylock-toml-expected.json b/tests/packagedcode/data/pypi/pylock-toml/pylock-toml-expected.json new file mode 100644 index 00000000000..9b3f99ada5f --- /dev/null +++ b/tests/packagedcode/data/pypi/pylock-toml/pylock-toml-expected.json @@ -0,0 +1,284 @@ +[ + { + "type": "pypi", + "namespace": null, + "name": null, + "version": null, + "qualifiers": {}, + "subpath": null, + "primary_language": "Python", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "lock_version": "1.0", + "created_by": "mousebender", + "requires_python": ">=3.12", + "environments": [ + "sys_platform == 'win32'", + "sys_platform == 'linux'" + ] + }, + "dependencies": [ + { + "purl": "pkg:pypi/attrs@25.1.0", + "extracted_requirement": null, + "scope": null, + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": false, + "resolved_package": { + "type": "pypi", + "namespace": null, + "name": "attrs", + "version": "25.1.0", + "qualifiers": {}, + "subpath": null, + "primary_language": "Python", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", + "size": null, + "sha1": null, + "md5": null, + "sha256": "c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": true, + "extra_data": { + "requires_python": ">=3.8", + "index": "https://pypi.org/simple/", + "file_references": [ + { + "wheel": { + "name": "attrs-25.1.0-py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", + "size": 63152, + "hashes": { + "sha256": "c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a" + } + } + } + ] + }, + "dependencies": [], + "repository_homepage_url": "https://pypi.org/project/attrs", + "repository_download_url": "https://pypi.org/packages/source/a/attrs/attrs-25.1.0.tar.gz", + "api_data_url": "https://pypi.org/pypi/attrs/25.1.0/json", + "datasource_id": "pypi_pylock_toml", + "purl": "pkg:pypi/attrs@25.1.0" + }, + "extra_data": {} + }, + { + "purl": "pkg:pypi/cattrs@24.1.2", + "extracted_requirement": null, + "scope": null, + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": false, + "resolved_package": { + "type": "pypi", + "namespace": null, + "name": "cattrs", + "version": "24.1.2", + "qualifiers": {}, + "subpath": null, + "primary_language": "Python", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": "https://files.pythonhosted.org/packages/c8/d5/867e75361fc45f6de75fe277dd085627a9db5ebb511a87f27dc1396b5351/cattrs-24.1.2-py3-none-any.whl", + "size": null, + "sha1": null, + "md5": null, + "sha256": "67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0", + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": true, + "extra_data": { + "requires_python": ">=3.8", + "index": "https://pypi.org/simple/", + "file_references": [ + { + "wheel": { + "name": "cattrs-24.1.2-py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/c8/d5/867e75361fc45f6de75fe277dd085627a9db5ebb511a87f27dc1396b5351/cattrs-24.1.2-py3-none-any.whl", + "size": 66446, + "hashes": { + "sha256": "67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0" + } + } + } + ] + }, + "dependencies": [ + { + "purl": "pkg:pypi/attrs", + "extracted_requirement": null, + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_pinned": false, + "is_direct": true, + "resolved_package": {}, + "extra_data": {} + } + ], + "repository_homepage_url": "https://pypi.org/project/cattrs", + "repository_download_url": "https://pypi.org/packages/source/c/cattrs/cattrs-24.1.2.tar.gz", + "api_data_url": "https://pypi.org/pypi/cattrs/24.1.2/json", + "datasource_id": "pypi_pylock_toml", + "purl": "pkg:pypi/cattrs@24.1.2" + }, + "extra_data": {} + }, + { + "purl": "pkg:pypi/numpy@2.2.3", + "extracted_requirement": null, + "scope": null, + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": false, + "resolved_package": { + "type": "pypi", + "namespace": null, + "name": "numpy", + "version": "2.2.3", + "qualifiers": {}, + "subpath": null, + "primary_language": "Python", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": "https://files.pythonhosted.org/packages/42/6e/55580a538116d16ae7c9aa17d4edd56e83f42126cb1dfe7a684da7925d2c/numpy-2.2.3-cp312-cp312-win_amd64.whl", + "size": null, + "sha1": null, + "md5": null, + "sha256": "83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d", + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": true, + "extra_data": { + "requires_python": ">=3.10", + "index": "https://pypi.org/simple/", + "file_references": [ + { + "wheel": { + "name": "numpy-2.2.3-cp312-cp312-win_amd64.whl", + "url": "https://files.pythonhosted.org/packages/42/6e/55580a538116d16ae7c9aa17d4edd56e83f42126cb1dfe7a684da7925d2c/numpy-2.2.3-cp312-cp312-win_amd64.whl", + "size": 12626357, + "hashes": { + "sha256": "83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d" + } + } + }, + { + "wheel": { + "name": "numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "url": "https://files.pythonhosted.org/packages/39/04/78d2e7402fb479d893953fb78fa7045f7deb635ec095b6b4f0260223091a/numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", + "size": 16116679, + "hashes": { + "sha256": "3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe" + } + } + } + ] + }, + "dependencies": [], + "repository_homepage_url": "https://pypi.org/project/numpy", + "repository_download_url": "https://pypi.org/packages/source/n/numpy/numpy-2.2.3.tar.gz", + "api_data_url": "https://pypi.org/pypi/numpy/2.2.3/json", + "datasource_id": "pypi_pylock_toml", + "purl": "pkg:pypi/numpy@2.2.3" + }, + "extra_data": {} + } + ], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "pypi_pylock_toml", + "purl": null + } +] \ No newline at end of file diff --git a/tests/packagedcode/data/pypi/pylock-toml/pylock.dev-toml-expected.json b/tests/packagedcode/data/pypi/pylock-toml/pylock.dev-toml-expected.json new file mode 100644 index 00000000000..b93da431998 --- /dev/null +++ b/tests/packagedcode/data/pypi/pylock-toml/pylock.dev-toml-expected.json @@ -0,0 +1,602 @@ +[ + { + "type": "pypi", + "namespace": null, + "name": null, + "version": null, + "qualifiers": {}, + "subpath": null, + "primary_language": "Python", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "lock_version": "1.0", + "created_by": "uv", + "requires_python": ">=3.9" + }, + "dependencies": [ + { + "purl": "pkg:pypi/click@8.1.7", + "extracted_requirement": null, + "scope": null, + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": false, + "resolved_package": { + "type": "pypi", + "namespace": null, + "name": "click", + "version": "8.1.7", + "qualifiers": {}, + "subpath": null, + "primary_language": "Python", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", + "size": null, + "sha1": null, + "md5": null, + "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": true, + "extra_data": { + "requires_python": ">=3.7", + "index": "https://pypi.org/simple/", + "file_references": [ + { + "wheel": { + "name": "click-8.1.7-py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", + "size": 97941, + "hashes": { + "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28" + } + } + } + ] + }, + "dependencies": [ + { + "purl": "pkg:pypi/colorama", + "extracted_requirement": null, + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_pinned": false, + "is_direct": true, + "resolved_package": {}, + "extra_data": {} + } + ], + "repository_homepage_url": "https://pypi.org/project/click", + "repository_download_url": "https://pypi.org/packages/source/c/click/click-8.1.7.tar.gz", + "api_data_url": "https://pypi.org/pypi/click/8.1.7/json", + "datasource_id": "pypi_pylock_toml", + "purl": "pkg:pypi/click@8.1.7" + }, + "extra_data": {} + }, + { + "purl": "pkg:pypi/colorama@0.4.6", + "extracted_requirement": null, + "scope": null, + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": false, + "resolved_package": { + "type": "pypi", + "namespace": null, + "name": "colorama", + "version": "0.4.6", + "qualifiers": {}, + "subpath": null, + "primary_language": "Python", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "size": null, + "sha1": null, + "md5": null, + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": true, + "extra_data": { + "marker": "platform_system == 'Windows'", + "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7", + "index": "https://pypi.org/simple/", + "file_references": [ + { + "wheel": { + "name": "colorama-0.4.6-py2.py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "size": 25335, + "hashes": { + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + } + } + } + ] + }, + "dependencies": [], + "repository_homepage_url": "https://pypi.org/project/colorama", + "repository_download_url": "https://pypi.org/packages/source/c/colorama/colorama-0.4.6.tar.gz", + "api_data_url": "https://pypi.org/pypi/colorama/0.4.6/json", + "datasource_id": "pypi_pylock_toml", + "purl": "pkg:pypi/colorama@0.4.6" + }, + "extra_data": {} + }, + { + "purl": "pkg:pypi/requests@2.31.0", + "extracted_requirement": null, + "scope": null, + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": false, + "resolved_package": { + "type": "pypi", + "namespace": null, + "name": "requests", + "version": "2.31.0", + "qualifiers": {}, + "subpath": null, + "primary_language": "Python", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", + "size": null, + "sha1": null, + "md5": null, + "sha256": "58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": true, + "extra_data": { + "requires_python": ">=3.7", + "index": "https://pypi.org/simple/", + "file_references": [ + { + "wheel": { + "name": "requests-2.31.0-py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl", + "size": 62574, + "hashes": { + "sha256": "58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f" + } + } + }, + { + "sdist": { + "name": "requests-2.31.0.tar.gz", + "url": "https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz", + "size": 110794, + "hashes": { + "sha256": "942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + } + } + } + ] + }, + "dependencies": [ + { + "purl": "pkg:pypi/certifi", + "extracted_requirement": null, + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_pinned": false, + "is_direct": true, + "resolved_package": {}, + "extra_data": {} + }, + { + "purl": "pkg:pypi/charset-normalizer", + "extracted_requirement": null, + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_pinned": false, + "is_direct": true, + "resolved_package": {}, + "extra_data": {} + }, + { + "purl": "pkg:pypi/idna", + "extracted_requirement": null, + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_pinned": false, + "is_direct": true, + "resolved_package": {}, + "extra_data": {} + }, + { + "purl": "pkg:pypi/urllib3", + "extracted_requirement": null, + "scope": "dependencies", + "is_runtime": true, + "is_optional": false, + "is_pinned": false, + "is_direct": true, + "resolved_package": {}, + "extra_data": {} + } + ], + "repository_homepage_url": "https://pypi.org/project/requests", + "repository_download_url": "https://pypi.org/packages/source/r/requests/requests-2.31.0.tar.gz", + "api_data_url": "https://pypi.org/pypi/requests/2.31.0/json", + "datasource_id": "pypi_pylock_toml", + "purl": "pkg:pypi/requests@2.31.0" + }, + "extra_data": {} + }, + { + "purl": "pkg:pypi/certifi@2024.2.2", + "extracted_requirement": null, + "scope": null, + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": false, + "resolved_package": { + "type": "pypi", + "namespace": null, + "name": "certifi", + "version": "2024.2.2", + "qualifiers": {}, + "subpath": null, + "primary_language": "Python", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": "https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl", + "size": null, + "sha1": null, + "md5": null, + "sha256": "dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1", + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": true, + "extra_data": { + "requires_python": ">=3.6", + "index": "https://pypi.org/simple/", + "file_references": [ + { + "wheel": { + "name": "certifi-2024.2.2-py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl", + "size": 163774, + "hashes": { + "sha256": "dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" + } + } + } + ] + }, + "dependencies": [], + "repository_homepage_url": "https://pypi.org/project/certifi", + "repository_download_url": "https://pypi.org/packages/source/c/certifi/certifi-2024.2.2.tar.gz", + "api_data_url": "https://pypi.org/pypi/certifi/2024.2.2/json", + "datasource_id": "pypi_pylock_toml", + "purl": "pkg:pypi/certifi@2024.2.2" + }, + "extra_data": {} + }, + { + "purl": "pkg:pypi/charset-normalizer@3.3.2", + "extracted_requirement": null, + "scope": null, + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": false, + "resolved_package": { + "type": "pypi", + "namespace": null, + "name": "charset-normalizer", + "version": "3.3.2", + "qualifiers": {}, + "subpath": null, + "primary_language": "Python", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63571571604e9e0c7c5f9e3e6e4e9e4b7c6c7c/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", + "size": null, + "sha1": null, + "md5": null, + "sha256": "8f0c69e0c87a857dfc2c96acef6d3b856cd5b51c0e7a867fca0f90e5a7c3b94e", + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": true, + "extra_data": { + "requires_python": ">=3.7.0", + "index": "https://pypi.org/simple/", + "file_references": [ + { + "wheel": { + "name": "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", + "url": "https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63571571604e9e0c7c5f9e3e6e4e9e4b7c6c7c/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", + "size": 99458, + "hashes": { + "sha256": "8f0c69e0c87a857dfc2c96acef6d3b856cd5b51c0e7a867fca0f90e5a7c3b94e" + } + } + } + ] + }, + "dependencies": [], + "repository_homepage_url": "https://pypi.org/project/charset-normalizer", + "repository_download_url": "https://pypi.org/packages/source/c/charset-normalizer/charset-normalizer-3.3.2.tar.gz", + "api_data_url": "https://pypi.org/pypi/charset-normalizer/3.3.2/json", + "datasource_id": "pypi_pylock_toml", + "purl": "pkg:pypi/charset-normalizer@3.3.2" + }, + "extra_data": {} + }, + { + "purl": "pkg:pypi/idna@3.6", + "extracted_requirement": null, + "scope": null, + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": false, + "resolved_package": { + "type": "pypi", + "namespace": null, + "name": "idna", + "version": "3.6", + "qualifiers": {}, + "subpath": null, + "primary_language": "Python", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", + "size": null, + "sha1": null, + "md5": null, + "sha256": "9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": true, + "extra_data": { + "requires_python": ">=3.5", + "index": "https://pypi.org/simple/", + "file_references": [ + { + "wheel": { + "name": "idna-3.6-py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", + "size": 61567, + "hashes": { + "sha256": "9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca" + } + } + } + ] + }, + "dependencies": [], + "repository_homepage_url": "https://pypi.org/project/idna", + "repository_download_url": "https://pypi.org/packages/source/i/idna/idna-3.6.tar.gz", + "api_data_url": "https://pypi.org/pypi/idna/3.6/json", + "datasource_id": "pypi_pylock_toml", + "purl": "pkg:pypi/idna@3.6" + }, + "extra_data": {} + }, + { + "purl": "pkg:pypi/urllib3@2.2.1", + "extracted_requirement": null, + "scope": null, + "is_runtime": true, + "is_optional": false, + "is_pinned": true, + "is_direct": false, + "resolved_package": { + "type": "pypi", + "namespace": null, + "name": "urllib3", + "version": "2.2.1", + "qualifiers": {}, + "subpath": null, + "primary_language": "Python", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": "https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl", + "size": null, + "sha1": null, + "md5": null, + "sha256": "450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": true, + "extra_data": { + "requires_python": ">=3.8", + "index": "https://pypi.org/simple/", + "file_references": [ + { + "wheel": { + "name": "urllib3-2.2.1-py3-none-any.whl", + "url": "https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl", + "size": 121067, + "hashes": { + "sha256": "450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d" + } + } + } + ] + }, + "dependencies": [], + "repository_homepage_url": "https://pypi.org/project/urllib3", + "repository_download_url": "https://pypi.org/packages/source/u/urllib3/urllib3-2.2.1.tar.gz", + "api_data_url": "https://pypi.org/pypi/urllib3/2.2.1/json", + "datasource_id": "pypi_pylock_toml", + "purl": "pkg:pypi/urllib3@2.2.1" + }, + "extra_data": {} + } + ], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "pypi_pylock_toml", + "purl": null + } +] \ No newline at end of file diff --git a/tests/packagedcode/data/pypi/pylock-toml/pylock.dev.toml b/tests/packagedcode/data/pypi/pylock-toml/pylock.dev.toml new file mode 100644 index 00000000000..b4cf5a32a1d --- /dev/null +++ b/tests/packagedcode/data/pypi/pylock-toml/pylock.dev.toml @@ -0,0 +1,79 @@ +lock-version = '1.0' +requires-python = '>=3.9' +created-by = 'uv' +extras = [] +dependency-groups = [] + +[[packages]] +name = 'click' +version = '8.1.7' +requires-python = '>=3.7' +index = 'https://pypi.org/simple/' +dependencies = [ + {name = 'colorama', marker = "platform_system == 'Windows'"}, +] +wheels = [ + {name = 'click-8.1.7-py3-none-any.whl', url = 'https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl', size = 97941, hashes = {sha256 = 'ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28'}}, +] + +[[packages]] +name = 'colorama' +version = '0.4.6' +marker = "platform_system == 'Windows'" +requires-python = '!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7' +index = 'https://pypi.org/simple/' +wheels = [ + {name = 'colorama-0.4.6-py2.py3-none-any.whl', url = 'https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl', size = 25335, hashes = {sha256 = '4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6'}}, +] + +[[packages]] +name = 'requests' +version = '2.31.0' +requires-python = '>=3.7' +index = 'https://pypi.org/simple/' +dependencies = [ + {name = 'certifi'}, + {name = 'charset-normalizer'}, + {name = 'idna'}, + {name = 'urllib3'}, +] +wheels = [ + {name = 'requests-2.31.0-py3-none-any.whl', url = 'https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl', size = 62574, hashes = {sha256 = '58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f'}}, +] +sdist = {name = 'requests-2.31.0.tar.gz', url = 'https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz', size = 110794, hashes = {sha256 = '942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1'}} + +[[packages]] +name = 'certifi' +version = '2024.2.2' +requires-python = '>=3.6' +index = 'https://pypi.org/simple/' +wheels = [ + {name = 'certifi-2024.2.2-py3-none-any.whl', url = 'https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl', size = 163774, hashes = {sha256 = 'dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1'}}, +] + +[[packages]] +name = 'charset-normalizer' +version = '3.3.2' +requires-python = '>=3.7.0' +index = 'https://pypi.org/simple/' +wheels = [ + {name = 'charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl', url = 'https://files.pythonhosted.org/packages/b6/7c/8debebb4f90174074b827c63571571604e9e0c7c5f9e3e6e4e9e4b7c6c7c/charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl', size = 99458, hashes = {sha256 = '8f0c69e0c87a857dfc2c96acef6d3b856cd5b51c0e7a867fca0f90e5a7c3b94e'}}, +] + +[[packages]] +name = 'idna' +version = '3.6' +requires-python = '>=3.5' +index = 'https://pypi.org/simple/' +wheels = [ + {name = 'idna-3.6-py3-none-any.whl', url = 'https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl', size = 61567, hashes = {sha256 = '9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca'}}, +] + +[[packages]] +name = 'urllib3' +version = '2.2.1' +requires-python = '>=3.8' +index = 'https://pypi.org/simple/' +wheels = [ + {name = 'urllib3-2.2.1-py3-none-any.whl', url = 'https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl', size = 121067, hashes = {sha256 = '450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d'}}, +] diff --git a/tests/packagedcode/data/pypi/pylock-toml/pylock.toml b/tests/packagedcode/data/pypi/pylock-toml/pylock.toml new file mode 100644 index 00000000000..05b4a0d0a02 --- /dev/null +++ b/tests/packagedcode/data/pypi/pylock-toml/pylock.toml @@ -0,0 +1,43 @@ +lock-version = '1.0' +environments = ["sys_platform == 'win32'", "sys_platform == 'linux'"] +requires-python = '>=3.12' +created-by = 'mousebender' + +[[packages]] +name = 'attrs' +version = '25.1.0' +requires-python = '>=3.8' +index = 'https://pypi.org/simple/' +wheels = [ + {name = 'attrs-25.1.0-py3-none-any.whl', url = 'https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl', size = 63152, hashes = {sha256 = 'c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a'}}, +] +[[packages.attestation-identities]] +environment = 'release-pypi' +kind = 'GitHub' +repository = 'python-attrs/attrs' +workflow = 'pypi-package.yml' + +[[packages]] +name = 'cattrs' +version = '24.1.2' +requires-python = '>=3.8' +index = 'https://pypi.org/simple/' +dependencies = [ + {name = 'attrs'}, +] +wheels = [ + {name = 'cattrs-24.1.2-py3-none-any.whl', url = 'https://files.pythonhosted.org/packages/c8/d5/867e75361fc45f6de75fe277dd085627a9db5ebb511a87f27dc1396b5351/cattrs-24.1.2-py3-none-any.whl', size = 66446, hashes = {sha256 = '67c7495b760168d931a10233f979b28dc04daf853b30752246f4f8471c6d68d0'}}, +] + +[[packages]] +name = 'numpy' +version = '2.2.3' +requires-python = '>=3.10' +index = 'https://pypi.org/simple/' +wheels = [ + {name = 'numpy-2.2.3-cp312-cp312-win_amd64.whl', url = 'https://files.pythonhosted.org/packages/42/6e/55580a538116d16ae7c9aa17d4edd56e83f42126cb1dfe7a684da7925d2c/numpy-2.2.3-cp312-cp312-win_amd64.whl', size = 12626357, hashes = {sha256 = '83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d'}}, + {name = 'numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl', url = 'https://files.pythonhosted.org/packages/39/04/78d2e7402fb479d893953fb78fa7045f7deb635ec095b6b4f0260223091a/numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl', size = 16116679, hashes = {sha256 = '3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe'}}, +] + +[tool.mousebender] +command = ['.', 'lock', '--platform', 'cpython3.12-windows-x64', '--platform', 'cpython3.12-manylinux2014-x64', 'cattrs', 'numpy'] diff --git a/tests/packagedcode/test_pypi.py b/tests/packagedcode/test_pypi.py index 3dcfa7d4268..c5d65853c11 100644 --- a/tests/packagedcode/test_pypi.py +++ b/tests/packagedcode/test_pypi.py @@ -405,6 +405,30 @@ def test_parse_pyproject_toml_poetry_univers(self): self.check_packages_data(package, expected_loc, regen=REGEN_TEST_FIXTURES) +class TestPylockTomlHandler(PackageTester): + test_data_dir = os.path.join(os.path.dirname(__file__), 'data') + + def test_is_pylock_toml(self): + test_file = self.get_test_loc('pypi/pylock-toml/pylock.toml') + assert pypi.PylockTomlHandler.is_datafile(test_file) + + def test_is_pylock_named_toml(self): + test_file = self.get_test_loc('pypi/pylock-toml/pylock.dev.toml') + assert pypi.PylockTomlHandler.is_datafile(test_file) + + def test_parse_pylock_toml(self): + test_file = self.get_test_loc('pypi/pylock-toml/pylock.toml') + package = pypi.PylockTomlHandler.parse(test_file) + expected_loc = self.get_test_loc('pypi/pylock-toml/pylock-toml-expected.json') + self.check_packages_data(package, expected_loc, regen=REGEN_TEST_FIXTURES) + + def test_parse_pylock_dev_toml(self): + test_file = self.get_test_loc('pypi/pylock-toml/pylock.dev.toml') + package = pypi.PylockTomlHandler.parse(test_file) + expected_loc = self.get_test_loc('pypi/pylock-toml/pylock.dev-toml-expected.json') + self.check_packages_data(package, expected_loc, regen=REGEN_TEST_FIXTURES) + + class TestPipInspectDeplockHandler(PackageTester): test_data_dir = os.path.join(os.path.dirname(__file__), 'data')