Skip to content

Commit b467954

Browse files
Add support for pip-inspect.deplock files
Add parser for pip-inspect.deplock files generated by deplock which has all the package metadata, i.e. the resolved versions and the dependency relationships. Reference: aboutcode-org/scancode.io#1262 Signed-off-by: Ayan Sinha Mahapatra <[email protected]>
1 parent c8046f1 commit b467954

File tree

6 files changed

+2313
-1
lines changed

6 files changed

+2313
-1
lines changed

src/packagedcode/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@
207207
windows.MicrosoftUpdateManifestHandler,
208208

209209
win_pe.WindowsExecutableHandler,
210+
211+
# These are handlers for deplock generated files
212+
pypi.PipInspectDeplockHandler,
210213
]
211214

212215
if on_linux:

src/packagedcode/pypi.py

+98-1
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,102 @@ def parse(cls, location, package_only=False):
563563
yield models.PackageData.from_data(package_data, package_only)
564564

565565

566+
class PipInspectDeplockHandler(models.DatafileHandler):
567+
datasource_id = 'pypi_inspect_deplock'
568+
path_patterns = ('*pip-inspect.deplock',)
569+
default_package_type = 'pypi'
570+
default_primary_language = 'Python'
571+
description = 'Python poetry pyproject.toml'
572+
# These are files generated by deplock, see https://github.com/nexB/dependency-inspector
573+
documentation_url = 'https://pip.pypa.io/en/stable/cli/pip_inspect/'
574+
575+
@classmethod
576+
def get_resolved_package_from_metadata(cls, metadata, package_only=False):
577+
578+
requires_dist = metadata.get('requires_dist')
579+
dependencies_for_resolved = get_requires_dependencies(
580+
requires=requires_dist,
581+
)
582+
package_data = dict(
583+
datasource_id=cls.datasource_id,
584+
type=cls.default_package_type,
585+
primary_language='Python',
586+
name=metadata.get('name'),
587+
version=metadata.get('version'),
588+
extracted_license_statement=metadata.get('license'),
589+
description=metadata.get('description'),
590+
keywords=metadata.get('keywords'),
591+
is_virtual=True,
592+
dependencies=[
593+
dep.to_dict()
594+
for dep in dependencies_for_resolved
595+
],
596+
)
597+
return models.PackageData.from_data(package_data, package_only)
598+
599+
@classmethod
600+
def parse(cls, location, package_only=False):
601+
602+
with open(location) as f:
603+
content = f.read()
604+
605+
data = json.loads(content)
606+
installed_packages = data.get('installed')
607+
if not installed_packages:
608+
return
609+
610+
main_package_metadata = {}
611+
dependencies = []
612+
613+
for package_metadata in installed_packages:
614+
package_metadata_dep = package_metadata.get('metadata')
615+
616+
# `direct_url` is only present for root package
617+
# `requested` is true for root package and direct dependencies only
618+
if package_metadata.get('requested') and 'direct_url' in package_metadata:
619+
main_package_metadata = package_metadata_dep
620+
main_package_requires = main_package_metadata.get('requires_dist')
621+
dependencies_for_main = get_requires_dependencies(
622+
requires=main_package_requires,
623+
)
624+
dependencies.extend([
625+
dep.to_dict()
626+
for dep in dependencies_for_main
627+
])
628+
continue
629+
630+
package_data_dep = cls.get_resolved_package_from_metadata(
631+
metadata=package_metadata_dep,
632+
package_only=package_only,
633+
)
634+
dep_purl = package_data_dep.purl
635+
636+
dependency = models.DependentPackage(
637+
purl=dep_purl,
638+
extracted_requirement=None,
639+
scope=None,
640+
is_runtime=True,
641+
is_optional=False,
642+
is_direct=False,
643+
is_resolved=True,
644+
resolved_package=package_data_dep.to_dict()
645+
)
646+
dependencies.append(dependency.to_dict())
647+
648+
pip_version = data.get('pip_version')
649+
inspect_version = data.get('version')
650+
extra_data = {
651+
"pip_version": pip_version,
652+
"inspect_version": inspect_version,
653+
}
654+
655+
package_data_main = cls.get_resolved_package_from_metadata(
656+
metadata=main_package_metadata,
657+
package_only=package_only,
658+
)
659+
package_data_main.dependencies = dependencies
660+
package_data_main.extra_data = extra_data
661+
yield package_data_main
566662

567663

568664
META_DIR_SUFFIXES = '.dist-info', '.egg-info', 'EGG-INFO',
@@ -1494,7 +1590,7 @@ def get_dist_dependencies(dist):
14941590
return get_requires_dependencies(requires=dist.requires)
14951591

14961592

1497-
def get_requires_dependencies(requires, default_scope='install'):
1593+
def get_requires_dependencies(requires, default_scope='install', is_direct=True):
14981594
"""
14991595
Return a list of DependentPackage found in a ``requires`` list of
15001596
requirement strings or an empty list.
@@ -1539,6 +1635,7 @@ def get_requires_dependencies(requires, default_scope='install'):
15391635
is_runtime=True,
15401636
is_optional=True if bool(extra) else False,
15411637
is_resolved=is_resolved,
1638+
is_direct=is_direct,
15421639
extracted_requirement=str(req),
15431640
))
15441641

tests/packagedcode/data/plugin/help.txt

+7
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,13 @@ Package type: pypi
713713
description: PyPI extracted egg PKG-INFO
714714
path_patterns: '*/EGG-INFO/PKG-INFO'
715715
--------------------------------------------
716+
Package type: pypi
717+
datasource_id: pypi_inspect_deplock
718+
documentation URL: https://pip.pypa.io/en/stable/cli/pip_inspect/
719+
primary language: Python
720+
description: Python poetry pyproject.toml
721+
path_patterns: '*pip-inspect.deplock'
722+
--------------------------------------------
716723
Package type: pypi
717724
datasource_id: pypi_poetry_pyproject_toml
718725
documentation URL: https://packaging.python.org/en/latest/specifications/pyproject-toml/

tests/packagedcode/data/pypi/deplock/univers/pip-inspect.deplock

+519
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)