Skip to content

Commit 448fc36

Browse files
Add is_direct attribute to dependency model #3780
Adds is_direct attribute to differentiate between direct dependecy relationships and dependencies listed in lockfiles which have both direct and transitive dependencies together, which will have is_direct as False. Reference: #3780 Signed-off-by: Ayan Sinha Mahapatra <ayansmahapatra@gmail.com>
1 parent 69ea6b7 commit 448fc36

File tree

9 files changed

+1309
-3
lines changed

9 files changed

+1309
-3
lines changed

src/packagedcode/models.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,14 @@ class DependentPackage(ModelMixin):
371371
'been resolved and this dependency url points to an '
372372
'exact version.')
373373

374+
is_direct = Boolean(
375+
default=True,
376+
label='is direct flag',
377+
help='True if this dependency version requirement is '
378+
'a direct dependency relation between two packages '
379+
'as opposed to a transitive dependency relation, '
380+
'which are present in lockfiles/dependency list.')
381+
374382
resolved_package = Mapping(
375383
label='resolved package data',
376384
help='A mapping of resolved package data for this dependent package, '
@@ -1072,6 +1080,17 @@ def is_datafile(cls, location, filetypes=tuple(), _bare_filename=False):
10721080
actual_type = T.filetype_file.lower()
10731081
return any(ft in actual_type for ft in filetypes)
10741082

1083+
@classmethod
1084+
def is_lockfile(cls):
1085+
"""
1086+
Return True if this is a lockfile, False otherwise.
1087+
1088+
This has to be implemented by datafile handlers classes
1089+
of lockfiles, to return True, in contrast to the default
1090+
value False.
1091+
"""
1092+
return False
1093+
10751094
@classmethod
10761095
def parse(cls, location, package_only=False):
10771096
"""

src/packagedcode/npm.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ def assemble(cls, package_data, resource, codebase, package_adder):
107107
yield from yield_dependencies_from_package_resource(resource)
108108
return
109109

110+
if codebase.has_single_resource:
111+
yield from models.DatafileHandler.assemble(package_data, resource, codebase, package_adder)
112+
return
113+
110114
assert len(package_resource.package_data) == 1, f'Invalid package.json for {package_resource.path}'
111115
pkg_data = package_resource.package_data[0]
112116
pkg_data = models.PackageData.from_dict(pkg_data)
@@ -204,6 +208,7 @@ def update_dependencies_by_purl(
204208
is_runtime=False,
205209
is_optional=False,
206210
is_resolved=False,
211+
is_direct=True,
207212
):
208213

209214
metadata_deps = ['peerDependenciesMeta', 'dependenciesMeta']
@@ -221,6 +226,7 @@ def update_dependencies_by_purl(
221226
is_runtime=is_runtime,
222227
is_optional=is_optional,
223228
is_resolved=is_resolved,
229+
is_direct=is_direct,
224230
)
225231
dependecies_by_purl[dep_purl] = dep_package
226232

@@ -244,6 +250,7 @@ def update_dependencies_by_purl(
244250
is_runtime=is_runtime,
245251
is_optional=metadata.get("optional"),
246252
is_resolved=is_resolved,
253+
is_direct=is_direct,
247254
)
248255
dependecies_by_purl[dep_purl] = dep_package
249256
continue
@@ -264,6 +271,7 @@ def update_dependencies_by_purl(
264271
is_runtime=is_runtime,
265272
is_optional=is_optional,
266273
is_resolved=is_resolved,
274+
is_direct=is_direct,
267275
)
268276
dependecies_by_purl[dep_purl] = dep_package
269277

@@ -476,6 +484,10 @@ def parse(cls, location, package_only=False):
476484

477485
class BaseNpmLockHandler(BaseNpmHandler):
478486

487+
@classmethod
488+
def is_lockfile(cls):
489+
return True
490+
479491
@classmethod
480492
def parse(cls, location, package_only=False):
481493

@@ -590,6 +602,7 @@ def parse(cls, location, package_only=False):
590602
is_runtime=is_runtime,
591603
is_optional=is_optional,
592604
is_resolved=True,
605+
is_direct=False,
593606
)
594607

595608
# URLs and checksums
@@ -638,6 +651,7 @@ def parse(cls, location, package_only=False):
638651
is_runtime=is_runtime,
639652
is_optional=is_optional,
640653
is_resolved=False,
654+
is_direct=True,
641655
)
642656

643657
resolved_package.dependencies = [
@@ -723,6 +737,10 @@ class YarnLockV2Handler(BaseNpmHandler):
723737
def is_datafile(cls, location, filetypes=tuple()):
724738
return super().is_datafile(location, filetypes=filetypes) and is_yarn_v2(location)
725739

740+
@classmethod
741+
def is_lockfile(cls):
742+
return True
743+
726744
@classmethod
727745
def parse(cls, location, package_only=False):
728746
"""
@@ -833,6 +851,10 @@ class YarnLockV1Handler(BaseNpmHandler):
833851
description = 'yarn.lock lockfile v1 format'
834852
documentation_url = 'https://classic.yarnpkg.com/lang/en/docs/yarn-lock/'
835853

854+
@classmethod
855+
def is_lockfile(cls):
856+
return True
857+
836858
@classmethod
837859
def is_datafile(cls, location, filetypes=tuple()):
838860
return super().is_datafile(location, filetypes=filetypes) and not is_yarn_v2(location)
@@ -953,6 +975,7 @@ def parse(cls, location, package_only=False):
953975
scope='dependencies',
954976
is_optional=False,
955977
is_runtime=True,
978+
is_direct=True,
956979
)
957980
resolved_package_data.dependencies.append(subdep)
958981

@@ -972,6 +995,7 @@ def parse(cls, location, package_only=False):
972995
scope='dependencies',
973996
is_optional=False,
974997
is_runtime=True,
998+
is_direct=False,
975999
resolved_package=resolved_package_data.to_dict(),
9761000
)
9771001
dependencies.append(dep.to_dict())
@@ -988,6 +1012,10 @@ def parse(cls, location, package_only=False):
9881012

9891013
class BasePnpmLockHandler(BaseNpmHandler):
9901014

1015+
@classmethod
1016+
def is_lockfile(cls):
1017+
return True
1018+
9911019
@classmethod
9921020
def parse(cls, location, package_only=False):
9931021
"""
@@ -1063,19 +1091,22 @@ def parse(cls, location, package_only=False):
10631091
scope='dependencies',
10641092
dependecies_by_purl=deps_for_resolved_by_purl,
10651093
is_resolved=True,
1094+
is_direct=False,
10661095
)
10671096
cls.update_dependencies_by_purl(
10681097
dependencies=peer_dependencies,
10691098
scope='peerDependencies',
10701099
dependecies_by_purl=deps_for_resolved_by_purl,
10711100
is_optional=True,
1101+
is_direct=False,
10721102
)
10731103
cls.update_dependencies_by_purl(
10741104
dependencies=optional_dependencies,
10751105
scope='optionalDependencies',
10761106
dependecies_by_purl=deps_for_resolved_by_purl,
10771107
is_resolved=True,
10781108
is_optional=True,
1109+
is_direct=False,
10791110
)
10801111
cls.update_dependencies_by_purl(
10811112
dependencies=peer_dependencies_meta,
@@ -1122,6 +1153,7 @@ def parse(cls, location, package_only=False):
11221153
is_optional=is_optional,
11231154
is_runtime=is_runtime,
11241155
is_resolved=True,
1156+
is_direct=True,
11251157
resolved_package=resolved_package.to_dict(),
11261158
extra_data=extra_data_deps,
11271159
)
@@ -1577,7 +1609,7 @@ def bundle_deps_mapper(bundle_deps, package):
15771609
return package
15781610

15791611

1580-
def deps_mapper(deps, package, field_name):
1612+
def deps_mapper(deps, package, field_name, is_direct=True):
15811613
"""
15821614
Handle deps such as dependencies, devDependencies, peerDependencies, optionalDependencies
15831615
return a tuple of (dep type, list of deps)
@@ -1630,6 +1662,7 @@ def deps_mapper(deps, package, field_name):
16301662
purl=purl,
16311663
scope=field_name,
16321664
extracted_requirement=requirement,
1665+
is_direct=is_direct,
16331666
**dependency_attributes
16341667
)
16351668
dependencies.append(dep)

tests/licensedcode/data/plugin_license/package/package.expected.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@
7070
"notice_text": null,
7171
"source_packages": [],
7272
"is_private": false,
73-
"extra_data": {},
73+
"extra_data": {
74+
"engines": {
75+
"node": ">=0.8.0"
76+
}
77+
},
7478
"repository_homepage_url": "https://www.npmjs.com/package/busboy",
7579
"repository_download_url": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
7680
"api_data_url": "https://registry.npmjs.org/busboy/0.2.14",
@@ -92,6 +96,7 @@
9296
"is_runtime": true,
9397
"is_optional": false,
9498
"is_resolved": false,
99+
"is_direct": true,
95100
"resolved_package": {},
96101
"extra_data": {},
97102
"dependency_uid": "pkg:npm/dicer?uuid=fixed-uid-done-for-testing-5642512d1758",
@@ -106,6 +111,7 @@
106111
"is_runtime": true,
107112
"is_optional": false,
108113
"is_resolved": false,
114+
"is_direct": true,
109115
"resolved_package": {},
110116
"extra_data": {},
111117
"dependency_uid": "pkg:npm/readable-stream?uuid=fixed-uid-done-for-testing-5642512d1758",
@@ -236,7 +242,11 @@
236242
"source_packages": [],
237243
"file_references": [],
238244
"is_private": false,
239-
"extra_data": {},
245+
"extra_data": {
246+
"engines": {
247+
"node": ">=0.8.0"
248+
}
249+
},
240250
"dependencies": [
241251
{
242252
"purl": "pkg:npm/dicer",
@@ -245,6 +255,7 @@
245255
"is_runtime": true,
246256
"is_optional": false,
247257
"is_resolved": false,
258+
"is_direct": true,
248259
"resolved_package": {},
249260
"extra_data": {}
250261
},
@@ -255,6 +266,7 @@
255266
"is_runtime": true,
256267
"is_optional": false,
257268
"is_resolved": false,
269+
"is_direct": true,
258270
"resolved_package": {},
259271
"extra_data": {}
260272
}

0 commit comments

Comments
 (0)