dcicutils 7.6.0.2b10__py3-none-any.whl → 7.7.0.1b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dcicutils/item_model_utils.py +224 -0
- dcicutils/lang_utils.py +7 -8
- dcicutils/qa_checkers.py +0 -9
- dcicutils/testing_utils.py +37 -0
- {dcicutils-7.6.0.2b10.dist-info → dcicutils-7.7.0.1b1.dist-info}/LICENSE.txt +1 -1
- {dcicutils-7.6.0.2b10.dist-info → dcicutils-7.7.0.1b1.dist-info}/METADATA +1 -1
- {dcicutils-7.6.0.2b10.dist-info → dcicutils-7.7.0.1b1.dist-info}/RECORD +9 -10
- {dcicutils-7.6.0.2b10.dist-info → dcicutils-7.7.0.1b1.dist-info}/entry_points.txt +0 -1
- dcicutils/contribution_scripts.py +0 -39
- dcicutils/contribution_utils.py +0 -570
- dcicutils/license_utils.py +0 -863
- {dcicutils-7.6.0.2b10.dist-info → dcicutils-7.7.0.1b1.dist-info}/WHEEL +0 -0
dcicutils/license_utils.py
DELETED
@@ -1,863 +0,0 @@
|
|
1
|
-
import contextlib
|
2
|
-
import datetime
|
3
|
-
import io
|
4
|
-
import json
|
5
|
-
# import logging
|
6
|
-
import os
|
7
|
-
import re
|
8
|
-
import subprocess
|
9
|
-
import warnings
|
10
|
-
|
11
|
-
try:
|
12
|
-
import piplicenses
|
13
|
-
except ImportError: # pragma: no cover - not worth unit testing this case
|
14
|
-
raise Exception("The dcicutils.license_utils module is intended for use at development time, not runtime."
|
15
|
-
" It does not export a requirement for the pip-licenses library,"
|
16
|
-
" but to use this in your unit tests, you are expected to assure a dev dependency on that library"
|
17
|
-
" as part of the [tool.poetry.dependencies] section of your pyproject.toml file."
|
18
|
-
" If you are trying to manually evaluate the utility of this library, you can"
|
19
|
-
" do 'pip install pip-licenses' and then retry importing this library.")
|
20
|
-
# or you can comment out the above raise of Exception and instead execute:
|
21
|
-
#
|
22
|
-
# subprocess.check_output('pip install pip-licenses'.split(' '))
|
23
|
-
# import piplicenses
|
24
|
-
|
25
|
-
from collections import defaultdict
|
26
|
-
from typing import Any, Dict, DefaultDict, List, Optional, Type, Union
|
27
|
-
|
28
|
-
# For obscure reasons related to how this file is used for early prototyping, these must use absolute references
|
29
|
-
# to modules, not relative references. Later when things are better installed, we can make refs relative again.
|
30
|
-
from dcicutils.lang_utils import there_are
|
31
|
-
from dcicutils.misc_utils import PRINT, get_error_message, local_attrs
|
32
|
-
|
33
|
-
|
34
|
-
# logging.basicConfig()
|
35
|
-
# logger = logging.getLogger(__name__)
|
36
|
-
|
37
|
-
_FRAMEWORK = 'framework'
|
38
|
-
_LANGUAGE = 'language'
|
39
|
-
_LICENSE = 'license'
|
40
|
-
_LICENSE_CLASSIFIER = 'license_classifier'
|
41
|
-
_LICENSES = 'licenses'
|
42
|
-
_NAME = 'name'
|
43
|
-
_STATUS = 'status'
|
44
|
-
|
45
|
-
|
46
|
-
class LicenseStatus:
|
47
|
-
ALLOWED = "ALLOWED"
|
48
|
-
SPECIALLY_ALLOWED = "SPECIALLY_ALLOWED"
|
49
|
-
FAILED = "FAILED"
|
50
|
-
EXPECTED_MISSING = "EXPECTED_MISSING"
|
51
|
-
UNEXPECTED_MISSING = "UNEXPECTED_MISSING"
|
52
|
-
|
53
|
-
|
54
|
-
class LicenseFramework:
|
55
|
-
|
56
|
-
NAME = None
|
57
|
-
|
58
|
-
@classmethod
|
59
|
-
def get_dependencies(cls):
|
60
|
-
raise NotImplementedError(f'{cls.__name__}.get_dependencies is not implemented.')
|
61
|
-
|
62
|
-
|
63
|
-
class LicenseAnalysis:
|
64
|
-
|
65
|
-
def __init__(self):
|
66
|
-
self.frameworks: List[Type[LicenseFramework]] = []
|
67
|
-
self.dependency_details: List[Dict[str, Any]] = []
|
68
|
-
self.unacceptable: DefaultDict[str, List[str]] = defaultdict(lambda: [])
|
69
|
-
self.unexpected_missing: List[str] = []
|
70
|
-
self.no_longer_missing: List[str] = []
|
71
|
-
self.miscellaneous: List[str] = []
|
72
|
-
|
73
|
-
|
74
|
-
FrameworkSpec = Union[str, LicenseFramework, Type[LicenseFramework]]
|
75
|
-
|
76
|
-
|
77
|
-
class LicenseFrameworkRegistry:
|
78
|
-
|
79
|
-
LICENSE_FRAMEWORKS: Dict[str, Type[LicenseFramework]] = {}
|
80
|
-
|
81
|
-
@classmethod
|
82
|
-
@contextlib.contextmanager
|
83
|
-
def temporary_registration_for_testing(cls):
|
84
|
-
# Enter dynamic context where any license frameworks that get registered during the context
|
85
|
-
# are discarded upon exiting the context.
|
86
|
-
with local_attrs(cls, LICENSE_FRAMEWORKS=cls.LICENSE_FRAMEWORKS.copy()):
|
87
|
-
yield
|
88
|
-
|
89
|
-
@classmethod
|
90
|
-
def register(cls, *, name):
|
91
|
-
"""
|
92
|
-
Declares a python license framework classs.
|
93
|
-
Mostly these names will be language names like 'python' or 'javascript',
|
94
|
-
but they might be names of other, non-linguistic frameworks (like 'cgap-pipeline', for example).
|
95
|
-
"""
|
96
|
-
def _decorator(framework_class):
|
97
|
-
if not issubclass(framework_class, LicenseFramework):
|
98
|
-
raise ValueError(f"The class {framework_class.__name__} does not inherit from LicenseFramework.")
|
99
|
-
framework_class.NAME = name
|
100
|
-
cls.LICENSE_FRAMEWORKS[name] = framework_class
|
101
|
-
return framework_class
|
102
|
-
return _decorator
|
103
|
-
|
104
|
-
@classmethod
|
105
|
-
def find_framework(cls, framework_spec: FrameworkSpec):
|
106
|
-
if isinstance(framework_spec, str):
|
107
|
-
return cls.LICENSE_FRAMEWORKS.get(framework_spec)
|
108
|
-
elif (isinstance(framework_spec, LicenseFramework)
|
109
|
-
or isinstance(framework_spec, type) and issubclass(framework_spec, LicenseFramework)):
|
110
|
-
return framework_spec
|
111
|
-
else:
|
112
|
-
raise ValueError(f"{framework_spec!r} must be an instance or subclass of LicenseFramework,"
|
113
|
-
f" or a name under which such a class is registered in the LicenseFrameworkRegistry.")
|
114
|
-
|
115
|
-
@classmethod
|
116
|
-
def all_frameworks(cls):
|
117
|
-
return sorted(cls.LICENSE_FRAMEWORKS.values(), key=lambda x: x.NAME)
|
118
|
-
|
119
|
-
|
120
|
-
@LicenseFrameworkRegistry.register(name='javascript')
|
121
|
-
class JavascriptLicenseFramework(LicenseFramework):
|
122
|
-
|
123
|
-
@classmethod
|
124
|
-
def implicated_licenses(cls, *, licenses_spec: str):
|
125
|
-
# We only care which licenses were mentioned, not what algebra is used on them.
|
126
|
-
# (Thankfully there are no NOTs, and that's probably not by accident, since that would be too big a set.)
|
127
|
-
# So for us, either (FOO AND BAR) or (FOO OR BAR) is the same because we want to treat it as "FOO,BAR".
|
128
|
-
# If all of those licenses match, all is good. That _does_ mean some things like (MIT OR GPL-3.0) will
|
129
|
-
# have trouble passing unless both MIT and GPL-3.0 are allowed.
|
130
|
-
licenses = sorted(map(lambda x: x.strip(),
|
131
|
-
(licenses_spec
|
132
|
-
.replace('(', '')
|
133
|
-
.replace(')', '')
|
134
|
-
.replace(' AND ', ',')
|
135
|
-
.replace(' OR ', ',')
|
136
|
-
).split(',')))
|
137
|
-
return licenses
|
138
|
-
|
139
|
-
@classmethod
|
140
|
-
def get_dependencies(cls):
|
141
|
-
output = subprocess.check_output(['npx', 'license-checker', '--summary', '--json'],
|
142
|
-
# This will output to stderr if there's an error,
|
143
|
-
# but it will still put {} on stdout, which is good enough for us.
|
144
|
-
stderr=subprocess.DEVNULL)
|
145
|
-
records = json.loads(output)
|
146
|
-
if not records:
|
147
|
-
# e.g., this happens if there's no javascript in the repo
|
148
|
-
raise Exception("No javascript license data was found.")
|
149
|
-
result = []
|
150
|
-
for name, record in records.items():
|
151
|
-
licenses_spec = record.get(_LICENSES)
|
152
|
-
if '(' in licenses_spec:
|
153
|
-
licenses = cls.implicated_licenses(licenses_spec=licenses_spec)
|
154
|
-
PRINT(f"Rewriting {licenses_spec!r} as {licenses!r}")
|
155
|
-
elif licenses_spec:
|
156
|
-
licenses = [licenses_spec]
|
157
|
-
else:
|
158
|
-
licenses = []
|
159
|
-
entry = {
|
160
|
-
_NAME: name.lstrip('@').split('@')[0], # e.g., @foo/bar@3.7
|
161
|
-
_LICENSES: licenses # TODO: could parse this better.
|
162
|
-
}
|
163
|
-
result.append(entry)
|
164
|
-
return result
|
165
|
-
|
166
|
-
|
167
|
-
@LicenseFrameworkRegistry.register(name='python')
|
168
|
-
class PythonLicenseFramework(LicenseFramework):
|
169
|
-
|
170
|
-
@classmethod
|
171
|
-
def _piplicenses_args(cls, _options: Optional[List[str]] = None):
|
172
|
-
parser = piplicenses.create_parser()
|
173
|
-
args = parser.parse_args(_options or [])
|
174
|
-
return args
|
175
|
-
|
176
|
-
@classmethod
|
177
|
-
def get_dependencies(cls):
|
178
|
-
args = cls._piplicenses_args()
|
179
|
-
result = []
|
180
|
-
entries = piplicenses.get_packages(args)
|
181
|
-
for entry in entries:
|
182
|
-
license_name = entry.get(_NAME)
|
183
|
-
licenses = entry.get(_LICENSE_CLASSIFIER) or []
|
184
|
-
entry = {
|
185
|
-
_NAME: license_name,
|
186
|
-
_LICENSES: licenses,
|
187
|
-
_LANGUAGE: 'python',
|
188
|
-
}
|
189
|
-
result.append(entry)
|
190
|
-
return sorted(result, key=lambda x: x.get(_NAME).lower())
|
191
|
-
|
192
|
-
|
193
|
-
class LicenseFileParser:
|
194
|
-
|
195
|
-
VERBOSE = False
|
196
|
-
|
197
|
-
SEPARATORS = '-.,'
|
198
|
-
SEPARATORS_AND_WHITESPACE = SEPARATORS + ' \t'
|
199
|
-
COPYRIGHT_SYMBOL = '\u00a9'
|
200
|
-
|
201
|
-
COPYRIGHT_LINE = re.compile(f"^Copyright"
|
202
|
-
f"(?: *(?:{COPYRIGHT_SYMBOL}|\\(c\\)))?"
|
203
|
-
f" *((?:(?:[{SEPARATORS}] *)?[1-9][0-9][0-9][0-9] *)+)*"
|
204
|
-
f" *(.+)$",
|
205
|
-
re.IGNORECASE)
|
206
|
-
|
207
|
-
COPYRIGHT_OWNER_SANS_SUFFIX = re.compile(f"^(.*[^{SEPARATORS}]) *[{SEPARATORS}] *All Rights Reserved[.]?$",
|
208
|
-
re.IGNORECASE)
|
209
|
-
|
210
|
-
@classmethod
|
211
|
-
def parse_simple_license_file(cls, *, filename):
|
212
|
-
"""
|
213
|
-
Licenses could be complicated, but we assume a file approximately of the form:
|
214
|
-
<license-title>
|
215
|
-
Copyright [<copyright-marker>] <copyright-year> <copyright-owner> [All Rights Reserved.]
|
216
|
-
<license-text>
|
217
|
-
where there is a single license named <license-title>, an actual unicode copyright sign or the letter
|
218
|
-
'c' in parentheses, a <copyright-year> (or years connected by hyphens and commas),
|
219
|
-
a <copyright-owner>, and optionally the words 'all rights reserved',
|
220
|
-
and finally the <license-text> of the single license named in the <license-title>.
|
221
|
-
|
222
|
-
Returns: a json dictionary containing the keys title, copyright-title, copyright-owner, copyright-year,
|
223
|
-
and copyright-text.
|
224
|
-
"""
|
225
|
-
with io.open(filename, 'r') as fp:
|
226
|
-
license_title = []
|
227
|
-
copyright_owners = []
|
228
|
-
primary_copyright_owner = None
|
229
|
-
copyright_seen = False
|
230
|
-
lines = []
|
231
|
-
for i, line in enumerate(fp):
|
232
|
-
line = line.strip(' \t\n\r')
|
233
|
-
if cls.VERBOSE: # pragma: no cover - this is just for debugging
|
234
|
-
PRINT(str(i).rjust(3), line)
|
235
|
-
m = cls.COPYRIGHT_LINE.match(line) if line[:1].isupper() else None
|
236
|
-
if not m:
|
237
|
-
lines.append(line)
|
238
|
-
else:
|
239
|
-
copyright_year = m.group(1).strip(cls.SEPARATORS_AND_WHITESPACE)
|
240
|
-
copyright_owner = m.group(2).rstrip(cls.SEPARATORS_AND_WHITESPACE)
|
241
|
-
m = cls.COPYRIGHT_OWNER_SANS_SUFFIX.match(copyright_owner)
|
242
|
-
if m:
|
243
|
-
copyright_owner = m.group(1)
|
244
|
-
if not copyright_seen:
|
245
|
-
primary_copyright_owner = copyright_owner
|
246
|
-
copyright_owners.append(copyright_owner)
|
247
|
-
if not copyright_seen:
|
248
|
-
license_title = '\n'.join(lines).strip('\n')
|
249
|
-
lines = []
|
250
|
-
else:
|
251
|
-
lines.append(line)
|
252
|
-
copyright_seen = True
|
253
|
-
if not copyright_seen:
|
254
|
-
raise Exception("Missing copyright line.")
|
255
|
-
license_text = '\n'.join(lines).strip('\n')
|
256
|
-
return {
|
257
|
-
'license-title': license_title,
|
258
|
-
'copyright-owner': primary_copyright_owner,
|
259
|
-
'copyright-owners': copyright_owners,
|
260
|
-
'copyright-year': copyright_year,
|
261
|
-
'license-text': license_text
|
262
|
-
}
|
263
|
-
|
264
|
-
@classmethod
|
265
|
-
def validate_simple_license_file(cls, *, filename: str,
|
266
|
-
check_license_title: Optional[str] = None, # a license name
|
267
|
-
check_copyright_year: Union[bool, str] = True,
|
268
|
-
check_copyright_owner: str = None, # a copyright owner
|
269
|
-
analysis: LicenseAnalysis = None):
|
270
|
-
def report(message):
|
271
|
-
if analysis:
|
272
|
-
analysis.miscellaneous.append(message)
|
273
|
-
else:
|
274
|
-
warnings.warn(message)
|
275
|
-
parsed = cls.parse_simple_license_file(filename=filename)
|
276
|
-
if check_license_title:
|
277
|
-
license_title = parsed['license-title']
|
278
|
-
if not re.match(check_license_title, license_title):
|
279
|
-
report(f"The license, {license_title!r}, was expected to match {check_license_title!r}.")
|
280
|
-
if check_copyright_year:
|
281
|
-
if check_copyright_year is True:
|
282
|
-
check_copyright_year = str(datetime.datetime.now().year)
|
283
|
-
copyright_year = parsed['copyright-year']
|
284
|
-
if not copyright_year.endswith(check_copyright_year):
|
285
|
-
report(f"The copyright year, {copyright_year!r}, should have {check_copyright_year!r} at the end.")
|
286
|
-
if check_copyright_owner:
|
287
|
-
copyright_owner = parsed['copyright-owner']
|
288
|
-
if not re.match(check_copyright_owner, copyright_owner):
|
289
|
-
report(f"The copyright owner, {copyright_owner!r}, was expected to match {check_copyright_owner!r}.")
|
290
|
-
|
291
|
-
|
292
|
-
class LicenseChecker:
|
293
|
-
"""
|
294
|
-
There are three important class variables to specify:
|
295
|
-
|
296
|
-
LICENSE_TITLE is a string naming the license to be expected in LICENSE.txt
|
297
|
-
|
298
|
-
COPYRIGHT_OWNER is the name of the copyright owner.
|
299
|
-
|
300
|
-
FRAMEWORKS will default to all defined frameworks (presently ['python', 'javascript'], but can be limited to
|
301
|
-
just ['python'] for example. It doesn't make a lot of sense to limit it to ['javascript'], though you could,
|
302
|
-
since you are using a Python library to do this, and it probably needs to have its dependencies checked.
|
303
|
-
|
304
|
-
ALLOWED is a list of license names as returned by the pip-licenses library.
|
305
|
-
|
306
|
-
EXPECTED_MISSING is a list of libraries that are expected to have no license information. This is so you don't
|
307
|
-
have to get warning fatigue by seeing a warning over and over for things you know about. If a new library
|
308
|
-
with no license info shows up that you don't expect, you should investigate it, make sure it's OK,
|
309
|
-
and then add it to this list.
|
310
|
-
|
311
|
-
EXCEPTIONS is a table (a dict) keyed on license names with entries that are lists of library names that are
|
312
|
-
allowed to use the indicated license even though the license might not be generally allowed. This should be
|
313
|
-
used for license types that might require special consideration. For example, some uses may be OK in a dev-only
|
314
|
-
situation like testing or documentation that are not OK in some other situation.
|
315
|
-
|
316
|
-
Note that if you don't like these license names, which are admittedly non-standard and do nt seem to use
|
317
|
-
SPDX naming conventions, you can customize the get_dependencies method to return a different
|
318
|
-
list, one of the form
|
319
|
-
[{"name": "libname", "license_classifier": ["license1", "license2", ...], "language": "python"}]
|
320
|
-
by whatever means you like and using whatever names you like.
|
321
|
-
"""
|
322
|
-
|
323
|
-
# Set this to True in subclasses if you want your organization's policy to be that you see
|
324
|
-
# some visible proof of which licenses were checked.
|
325
|
-
VERBOSE = True
|
326
|
-
|
327
|
-
LICENSE_TITLE = None
|
328
|
-
COPYRIGHT_OWNER = None
|
329
|
-
LICENSE_FRAMEWORKS = None
|
330
|
-
|
331
|
-
EXPECTED_MISSING_LICENSES = []
|
332
|
-
|
333
|
-
ALLOWED: List[str] = []
|
334
|
-
|
335
|
-
EXCEPTIONS: Dict[str, str] = {}
|
336
|
-
|
337
|
-
POSSIBLE_LICENSE_FILE_BASE_NAMES = ['LICENSE']
|
338
|
-
POSSIBLE_LICENSE_EXTENSIONS = ['', '.txt', '.text', '.md', '.rst']
|
339
|
-
|
340
|
-
@classmethod
|
341
|
-
def find_license_files(cls) -> List[str]:
|
342
|
-
results = []
|
343
|
-
for file_name in cls.POSSIBLE_LICENSE_FILE_BASE_NAMES:
|
344
|
-
for file_ext in cls.POSSIBLE_LICENSE_EXTENSIONS:
|
345
|
-
file = file_name + file_ext
|
346
|
-
if os.path.exists(file):
|
347
|
-
results.append(file)
|
348
|
-
return results
|
349
|
-
|
350
|
-
MULTIPLE_LICENSE_FILE_ADVICE = ("Multiple license files create a risk of inconsistency."
|
351
|
-
" Best practice is to have only one.")
|
352
|
-
|
353
|
-
@classmethod
|
354
|
-
def analyze_license_file(cls, *, analysis: LicenseAnalysis,
|
355
|
-
copyright_owner: Optional[str] = None,
|
356
|
-
license_title: Optional[str] = None) -> None:
|
357
|
-
|
358
|
-
copyright_owner = copyright_owner or cls.COPYRIGHT_OWNER
|
359
|
-
license_title = license_title or cls.LICENSE_TITLE
|
360
|
-
|
361
|
-
if copyright_owner is None:
|
362
|
-
analysis.miscellaneous.append(f"Class {cls.__name__} has no declared license owner.")
|
363
|
-
|
364
|
-
license_files = cls.find_license_files()
|
365
|
-
if not license_files:
|
366
|
-
analysis.miscellaneous.append("Missing license file.")
|
367
|
-
return
|
368
|
-
|
369
|
-
if len(license_files) > 1:
|
370
|
-
analysis.miscellaneous.append(
|
371
|
-
there_are(license_files, kind='license file', show=True, punctuate=True)
|
372
|
-
+ " " + cls.MULTIPLE_LICENSE_FILE_ADVICE
|
373
|
-
)
|
374
|
-
|
375
|
-
for license_file in license_files:
|
376
|
-
LicenseFileParser.validate_simple_license_file(filename=license_file,
|
377
|
-
check_copyright_owner=copyright_owner or cls.COPYRIGHT_OWNER,
|
378
|
-
check_license_title=license_title or cls.LICENSE_TITLE,
|
379
|
-
analysis=analysis)
|
380
|
-
|
381
|
-
@classmethod
|
382
|
-
def analyze_license_dependencies_for_framework(cls, *,
|
383
|
-
analysis: LicenseAnalysis,
|
384
|
-
framework: Type[LicenseFramework],
|
385
|
-
acceptable: Optional[List[str]] = None,
|
386
|
-
exceptions: Optional[Dict[str, str]] = None,
|
387
|
-
) -> None:
|
388
|
-
acceptable = (acceptable or []) + (cls.ALLOWED or [])
|
389
|
-
exceptions = dict(cls.EXCEPTIONS or {}, **(exceptions or {}))
|
390
|
-
|
391
|
-
try:
|
392
|
-
entries = framework.get_dependencies()
|
393
|
-
except Exception as e:
|
394
|
-
analysis.miscellaneous.append(f"License framework {framework.NAME!r} failed to get licenses:"
|
395
|
-
f" {get_error_message(e)}")
|
396
|
-
return
|
397
|
-
|
398
|
-
# We don't add this information until we've successfully retrieved dependency info
|
399
|
-
# (If we failed, we reported the problem as part of the analysis.)
|
400
|
-
analysis.frameworks.append(framework)
|
401
|
-
|
402
|
-
for entry in entries:
|
403
|
-
name = entry[_NAME]
|
404
|
-
license_names = entry[_LICENSES]
|
405
|
-
if not license_names:
|
406
|
-
if name in cls.EXPECTED_MISSING_LICENSES:
|
407
|
-
status = LicenseStatus.EXPECTED_MISSING
|
408
|
-
else:
|
409
|
-
status = LicenseStatus.UNEXPECTED_MISSING
|
410
|
-
analysis.unexpected_missing.append(name)
|
411
|
-
else:
|
412
|
-
if name in cls.EXPECTED_MISSING_LICENSES:
|
413
|
-
analysis.no_longer_missing.append(name)
|
414
|
-
status = LicenseStatus.ALLOWED
|
415
|
-
by_special_exception = False
|
416
|
-
for license_name in license_names:
|
417
|
-
special_exceptions = exceptions.get(license_name, [])
|
418
|
-
if license_name in acceptable:
|
419
|
-
pass
|
420
|
-
elif name in special_exceptions:
|
421
|
-
by_special_exception = True
|
422
|
-
else:
|
423
|
-
status = LicenseStatus.FAILED
|
424
|
-
analysis.unacceptable[license_name].append(name)
|
425
|
-
if status == LicenseStatus.ALLOWED and by_special_exception:
|
426
|
-
status = LicenseStatus.SPECIALLY_ALLOWED
|
427
|
-
analysis.dependency_details.append({
|
428
|
-
_NAME: name,
|
429
|
-
_FRAMEWORK: framework.NAME,
|
430
|
-
_LICENSES: license_names,
|
431
|
-
_STATUS: status
|
432
|
-
})
|
433
|
-
if cls.VERBOSE: # pragma: no cover - this is just for debugging
|
434
|
-
PRINT(f"Checked {framework.NAME} {name}:"
|
435
|
-
f" {'; '.join(license_names) if license_names else '---'} ({status})")
|
436
|
-
|
437
|
-
@classmethod
|
438
|
-
def analyze_license_dependencies_by_framework(cls, *,
|
439
|
-
analysis: LicenseAnalysis,
|
440
|
-
frameworks: Optional[List[FrameworkSpec]] = None,
|
441
|
-
acceptable: Optional[List[str]] = None,
|
442
|
-
exceptions: Optional[Dict[str, str]] = None,
|
443
|
-
) -> None:
|
444
|
-
|
445
|
-
if frameworks is None:
|
446
|
-
frameworks = cls.LICENSE_FRAMEWORKS
|
447
|
-
|
448
|
-
if frameworks is None:
|
449
|
-
frameworks = LicenseFrameworkRegistry.all_frameworks()
|
450
|
-
else:
|
451
|
-
frameworks = [LicenseFrameworkRegistry.find_framework(framework_spec)
|
452
|
-
for framework_spec in frameworks]
|
453
|
-
|
454
|
-
for framework in frameworks:
|
455
|
-
cls.analyze_license_dependencies_for_framework(analysis=analysis, framework=framework,
|
456
|
-
acceptable=acceptable, exceptions=exceptions)
|
457
|
-
|
458
|
-
@classmethod
|
459
|
-
def show_unacceptable_licenses(cls, *, analysis: LicenseAnalysis) -> LicenseAnalysis:
|
460
|
-
if analysis.unacceptable:
|
461
|
-
PRINT(there_are(analysis.unacceptable, kind="unacceptable license", show=False, punctuation_mark=':'))
|
462
|
-
for license, names in analysis.unacceptable.items():
|
463
|
-
PRINT(f" {license}: {', '.join(names)}")
|
464
|
-
return analysis
|
465
|
-
|
466
|
-
@classmethod
|
467
|
-
def validate(cls, frameworks: Optional[List[FrameworkSpec]] = None) -> None:
|
468
|
-
"""
|
469
|
-
This method is intended to be used in a unit test, as in:
|
470
|
-
|
471
|
-
from my_org_tools import MyOrgLicenseChecker
|
472
|
-
def test_license_compatibility():
|
473
|
-
MyOrgLicenseChecker.validate()
|
474
|
-
|
475
|
-
where my_org_tools has done something like:
|
476
|
-
|
477
|
-
from dcicutils.license_utils import LicenseChecker
|
478
|
-
class MyOrgLicenseChecker(LicenseChecker):
|
479
|
-
LICENSE_OWNER = "..."
|
480
|
-
ALLOWED = [...]
|
481
|
-
EXPECTED_MISSING_LICENSES = [...]
|
482
|
-
EXCEPTIONS = {...}
|
483
|
-
|
484
|
-
See the example of C4InfrastructureLicenseChecker we use in our own group for our own family of toools,
|
485
|
-
which we sometimes informally refer to collectively as 'C4'.
|
486
|
-
"""
|
487
|
-
analysis = LicenseAnalysis()
|
488
|
-
cls.analyze_license_dependencies_by_framework(analysis=analysis, frameworks=frameworks)
|
489
|
-
cls.analyze_license_file(analysis=analysis)
|
490
|
-
cls.show_unacceptable_licenses(analysis=analysis)
|
491
|
-
if analysis.unexpected_missing:
|
492
|
-
warnings.warn(there_are(analysis.unexpected_missing, kind='unexpectedly missing license', punctuate=True))
|
493
|
-
if analysis.no_longer_missing:
|
494
|
-
# This is not so major as to need a warning, but it's still something that should show up somewhere.
|
495
|
-
PRINT(there_are(analysis.no_longer_missing, kind='no-longer-missing license', punctuate=True))
|
496
|
-
for message in analysis.miscellaneous:
|
497
|
-
warnings.warn(message)
|
498
|
-
if analysis.unacceptable:
|
499
|
-
raise LicenseAcceptabilityCheckFailure(unacceptable_licenses=analysis.unacceptable)
|
500
|
-
|
501
|
-
|
502
|
-
class LicenseCheckFailure(Exception):
|
503
|
-
|
504
|
-
DEFAULT_MESSAGE = "License check failure."
|
505
|
-
|
506
|
-
def __init__(self, message=None):
|
507
|
-
super().__init__(message or self.DEFAULT_MESSAGE)
|
508
|
-
|
509
|
-
|
510
|
-
class LicenseOwnershipCheckFailure(LicenseCheckFailure):
|
511
|
-
|
512
|
-
DEFAULT_MESSAGE = "License ownership check failure."
|
513
|
-
|
514
|
-
|
515
|
-
class LicenseAcceptabilityCheckFailure(LicenseCheckFailure):
|
516
|
-
|
517
|
-
DEFAULT_MESSAGE = "License acceptability check failure."
|
518
|
-
|
519
|
-
def __init__(self, message=None, unacceptable_licenses=None):
|
520
|
-
self.unacceptable_licenses = unacceptable_licenses
|
521
|
-
if not message and unacceptable_licenses:
|
522
|
-
message = there_are(unacceptable_licenses, kind='unacceptable license')
|
523
|
-
super().__init__(message=message)
|
524
|
-
|
525
|
-
|
526
|
-
class C4InfrastructureLicenseChecker(LicenseChecker):
|
527
|
-
"""
|
528
|
-
This set of values is useful to us in Park Lab where these tools were developed.
|
529
|
-
If you're at some other organization, we recommend you make a class that has values
|
530
|
-
suitable to your own organizational needs.
|
531
|
-
"""
|
532
|
-
|
533
|
-
COPYRIGHT_OWNER = "President and Fellows of Harvard College"
|
534
|
-
LICENSE_TITLE = "(The )?MIT License"
|
535
|
-
LICENSE_FRAMEWORKS = ['python', 'javascript']
|
536
|
-
|
537
|
-
ALLOWED = [
|
538
|
-
|
539
|
-
# <<Despite its name, Zero-Clause BSD is an alteration of the ISC license,
|
540
|
-
# and is not textually derived from licenses in the BSD family.
|
541
|
-
# Zero-Clause BSD was originally approved under the name “Free Public License 1.0.0”>>
|
542
|
-
# Ref: https://opensource.org/license/0bsd/
|
543
|
-
'0BSD',
|
544
|
-
|
545
|
-
# Linking = Permissive, Private Use = Yes
|
546
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
547
|
-
'Academic Free License (AFL)',
|
548
|
-
'AFL-2.1',
|
549
|
-
|
550
|
-
# Linking = Permissive, Private Use = Yes
|
551
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
552
|
-
'Apache Software License',
|
553
|
-
'Apache-Style',
|
554
|
-
'Apache-2.0',
|
555
|
-
|
556
|
-
# Linking = Permissive, Private Use = Yes
|
557
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
558
|
-
'BSD License',
|
559
|
-
'BSD-2-Clause',
|
560
|
-
'BSD-3-Clause',
|
561
|
-
|
562
|
-
# Linking = Public Domain, Private Use = Public Domain
|
563
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
564
|
-
'CC0',
|
565
|
-
'CC0-1.0',
|
566
|
-
|
567
|
-
# Linking = Permissive, Private Use = Permissive
|
568
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
569
|
-
'CC-BY',
|
570
|
-
'CC-BY-3.0',
|
571
|
-
'CC-BY-4.0',
|
572
|
-
|
573
|
-
# Linking = Permissive, Private Use = ?
|
574
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
575
|
-
'CDDL',
|
576
|
-
|
577
|
-
# The original Eclipse Distribution License 1.0 is essentially a BSD-3-Clause license.
|
578
|
-
# Ref: https://www.eclipse.org/org/documents/edl-v10.php
|
579
|
-
'Eclipse Distribution License',
|
580
|
-
|
581
|
-
# Linking = Permissive, Private Use = Yes
|
582
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
583
|
-
'Eclipse Public License',
|
584
|
-
'EPL-2.0',
|
585
|
-
|
586
|
-
# Linking = Yes, Cat = Permissive Software Licenses
|
587
|
-
# Ref: https://en.wikipedia.org/wiki/Historical_Permission_Notice_and_Disclaimer
|
588
|
-
'Historical Permission Notice and Disclaimer (HPND)',
|
589
|
-
|
590
|
-
# Linking = Permissive, Private Use = Permissive
|
591
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
592
|
-
'ISC License (ISCL)',
|
593
|
-
'ISC',
|
594
|
-
|
595
|
-
# Linking = Permissive, Private Use = Yes
|
596
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
597
|
-
'MIT License',
|
598
|
-
'MIT',
|
599
|
-
|
600
|
-
# Linking = Permissive, Private Use = Yes
|
601
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
602
|
-
'Mozilla Public License 2.0 (MPL 2.0)',
|
603
|
-
'MPL-1.1',
|
604
|
-
'MPL-2.0',
|
605
|
-
|
606
|
-
# The SIL Open Font License appears to be a copyleft-style license that applies narrowly
|
607
|
-
# to icons and not to the entire codebase. It is advertised as OK for use even in commercial
|
608
|
-
# applications.
|
609
|
-
# Ref: https://fontawesome.com/license/free
|
610
|
-
'OFL-1.1',
|
611
|
-
|
612
|
-
# Ref: https://en.wikipedia.org/wiki/Public_domain
|
613
|
-
'Public Domain',
|
614
|
-
|
615
|
-
# Linking = Permissive, Private Use = Permissive
|
616
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
617
|
-
'Python Software Foundation License',
|
618
|
-
'Python-2.0',
|
619
|
-
|
620
|
-
# License = BSD-like
|
621
|
-
# Ref: https://en.wikipedia.org/wiki/Pylons_project
|
622
|
-
'Repoze Public License',
|
623
|
-
|
624
|
-
# Linking = Permissive/Public domain, Private Use = Permissive/Public domain
|
625
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
626
|
-
'The Unlicense (Unlicense)',
|
627
|
-
'Unlicense',
|
628
|
-
|
629
|
-
# Linking = Permissive, Private Use = ?
|
630
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
631
|
-
'W3C License',
|
632
|
-
'W3C-20150513',
|
633
|
-
|
634
|
-
# Linking = Permissive/Public Domain, Private Use = Yes
|
635
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
636
|
-
'WTFPL',
|
637
|
-
|
638
|
-
# Copyleft = No
|
639
|
-
# Ref: https://en.wikipedia.org/wiki/Zlib_License
|
640
|
-
# Linking = Permissive, Private Use = ? (for zlib/libpng license)
|
641
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
642
|
-
'Zlib',
|
643
|
-
|
644
|
-
# Copyleft = No, FSF/OSI-approved: Yes
|
645
|
-
# Ref: https://en.wikipedia.org/wiki/Zope_Public_License
|
646
|
-
'Zope Public License',
|
647
|
-
]
|
648
|
-
|
649
|
-
EXPECTED_MISSING_LICENSES = [
|
650
|
-
|
651
|
-
# This is a name we use for our C4 portals. And it isn't published.
|
652
|
-
# We inherited the name from the Stanford ENCODE group, which had an MIT-licensed repo we forked
|
653
|
-
'encoded', # cgap-portal, fourfront, and smaht-portal all call themselves this
|
654
|
-
|
655
|
-
# We believe that since these next here are part of the Pylons project, they're covered under
|
656
|
-
# the same license as the other Pylons projects. We're seeking clarification.
|
657
|
-
'pyramid-translogger',
|
658
|
-
'subprocess-middleware',
|
659
|
-
|
660
|
-
# This appears to be a mostly-MIT-style license.
|
661
|
-
# There are references to parts being in the public domain, though it's not obvious if that's meaningful.
|
662
|
-
# It's probably sufficient for our purposes to treat this as a permissive license.
|
663
|
-
# Ref: https://github.com/tlsfuzzer/python-ecdsa/blob/master/LICENSE
|
664
|
-
'ecdsa',
|
665
|
-
|
666
|
-
# This has an MIT license in its source repository
|
667
|
-
# Ref: https://github.com/xlwings/jsondiff/blob/master/LICENSE
|
668
|
-
'jsondiff',
|
669
|
-
|
670
|
-
# This has an MIT license in its source repository
|
671
|
-
# Ref: https://github.com/pkerpedjiev/negspy/blob/master/LICENSE
|
672
|
-
'negspy',
|
673
|
-
|
674
|
-
# This license statement is complicated, but seems adequately permissive.
|
675
|
-
# Ref: https://foss.heptapod.net/python-libs/passlib/-/blob/branch/stable/LICENSE
|
676
|
-
'passlib',
|
677
|
-
|
678
|
-
# This seems to be a BSD-3-Clause license.
|
679
|
-
# Ref: https://github.com/protocolbuffers/protobuf/blob/main/LICENSE
|
680
|
-
# pypi agrees in the Meta section of protobuf's page, where it says "3-Clause BSD License"
|
681
|
-
# Ref: https://pypi.org/project/protobuf/
|
682
|
-
'protobuf',
|
683
|
-
|
684
|
-
# The WTFPL license is permissive.
|
685
|
-
# Ref: https://github.com/mk-fg/pretty-yaml/blob/master/COPYING
|
686
|
-
'pyaml',
|
687
|
-
|
688
|
-
# This uses a BSD license
|
689
|
-
# Ref: https://github.com/eliben/pycparser/blob/master/LICENSE
|
690
|
-
'pycparser',
|
691
|
-
|
692
|
-
# The source repo for pyDes says this is under an MIT license
|
693
|
-
# Ref: https://github.com/twhiteman/pyDes/blob/master/LICENSE.txt
|
694
|
-
# pypi, probably wrongly, thinks this is in the public domain (as of 2023-07-21)
|
695
|
-
# Ref: https://pypi.org/project/pyDes/
|
696
|
-
'pyDes',
|
697
|
-
|
698
|
-
# This uses an MIT license
|
699
|
-
# Ref: https://github.com/pysam-developers/pysam/blob/master/COPYING
|
700
|
-
'pysam',
|
701
|
-
|
702
|
-
# The version of python-lambda that we forked calls itself this (and publishes at pypi under this name)
|
703
|
-
"python-lambda-4dn",
|
704
|
-
|
705
|
-
# This is MIT-licensed:
|
706
|
-
# Ref: https://github.com/themiurgo/ratelim/blob/master/LICENSE
|
707
|
-
# pypi agrees
|
708
|
-
# Ref: https://pypi.org/project/ratelim/
|
709
|
-
'ratelim',
|
710
|
-
|
711
|
-
# This is a BSD-3-Clause-Modification license
|
712
|
-
# Ref: https://github.com/repoze/repoze.debug/blob/master/LICENSE.txt
|
713
|
-
'repoze.debug',
|
714
|
-
|
715
|
-
# This is an Apache-2.0 license
|
716
|
-
# Ref: https://github.com/getsentry/responses/blob/master/LICENSE
|
717
|
-
'responses',
|
718
|
-
|
719
|
-
# This seems to get flagged sometimes, but is not the pypi snovault library, it's what our dcicsnovault
|
720
|
-
# calls itself internally.. In any case, it's under MIT license and OK.
|
721
|
-
# Ref: https://github.com/4dn-dcic/snovault/blob/master/LICENSE.txt
|
722
|
-
'snovault',
|
723
|
-
|
724
|
-
# PyPi identifies the supervisor library license as "BSD-derived (http://www.repoze.org/LICENSE.txt)"
|
725
|
-
# Ref: https://pypi.org/project/supervisor/
|
726
|
-
# In fact, though, the license is a bit more complicated, though apparently still permissive.
|
727
|
-
# Ref: https://github.com/Supervisor/supervisor/blob/main/LICENSES.txt
|
728
|
-
'supervisor',
|
729
|
-
|
730
|
-
# This seems to be a BSD-3-Clause-Modification license.
|
731
|
-
# Ref: https://github.com/Pylons/translationstring/blob/master/LICENSE.txt
|
732
|
-
'translationstring',
|
733
|
-
|
734
|
-
# This seems to be a BSD-3-Clause-Modification license.
|
735
|
-
# Ref: https://github.com/Pylons/venusian/blob/master/LICENSE.txt
|
736
|
-
'venusian',
|
737
|
-
|
738
|
-
# PyPi identifies zope.deprecation as using the "Zope Public License (ZPL 2.1)" license.
|
739
|
-
# Ref: https://github.com/zopefoundation/Zope/blob/master/LICENSE.txt
|
740
|
-
'zope.deprecation',
|
741
|
-
|
742
|
-
# Below are licenses last known to have licenses missing in pip-licenses and need to be investigated further.
|
743
|
-
# Note well that just because pip-licenses doesn't know the license doesn't mean the software has
|
744
|
-
# no license. It may just mean the library is poorly registered in pypi. Some licenses have to be
|
745
|
-
# found by looking at the library's documentation or source files.
|
746
|
-
|
747
|
-
# (all of these have been classified at this point)
|
748
|
-
|
749
|
-
]
|
750
|
-
|
751
|
-
EXCEPTIONS = {
|
752
|
-
|
753
|
-
'BSD*': [
|
754
|
-
# Although modified to insert the author name into the license text itself,
|
755
|
-
# the license for these libraries are essentially BSD-3-Clause.
|
756
|
-
'formatio',
|
757
|
-
'samsam',
|
758
|
-
|
759
|
-
# There are some slightly different versions of what appear to be BSD licenses here,
|
760
|
-
# but clearly the license is permissive.
|
761
|
-
# Ref: https://www.npmjs.com/package/mutation-observer?activeTab=readme
|
762
|
-
'mutation-observer',
|
763
|
-
],
|
764
|
-
|
765
|
-
'Custom: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global': [
|
766
|
-
# The use of this URL appears to be a syntax error in the definition of entries-ponyfill
|
767
|
-
# In fact this seems to be covered by a CC0-1.0 license.
|
768
|
-
# Ref: https://unpkg.com/browse/object.entries-ponyfill@1.0.1/LICENSE
|
769
|
-
'object.entries-ponyfill',
|
770
|
-
],
|
771
|
-
|
772
|
-
'Custom: https://github.com/saikocat/colorbrewer.': [
|
773
|
-
# The use of this URL appears to be a syntax error in the definition of cartocolor
|
774
|
-
# In fact, this seems to be covered by a CC-BY-3.0 license.
|
775
|
-
# Ref: https://www.npmjs.com/package/cartocolor?activeTab=readme
|
776
|
-
'cartocolor',
|
777
|
-
],
|
778
|
-
|
779
|
-
'Custom: https://travis-ci.org/component/emitter.png': [
|
780
|
-
# The use of this png appears to be a syntax error in the definition of emitter-component.
|
781
|
-
# In fact, emitter-component uses an MIT License
|
782
|
-
# Ref: https://www.npmjs.com/package/emitter-component
|
783
|
-
# Ref: https://github.com/component/emitter/blob/master/LICENSE
|
784
|
-
'emitter-component',
|
785
|
-
],
|
786
|
-
|
787
|
-
# The 'turfs-jsts' repository (https://github.com/DenisCarriere/turf-jsts/blob/master/README.md)
|
788
|
-
# seems to lack a license, but appears to be forked from the jsts library that uses
|
789
|
-
# the Eclipse Public License 1.0 and Eclipse Distribution License 1.0, so probably a permissive
|
790
|
-
# license is intended.
|
791
|
-
'Custom: https://travis-ci.org/DenisCarriere/turf-jsts.svg': [
|
792
|
-
'turf-jsts'
|
793
|
-
],
|
794
|
-
|
795
|
-
# Linking = With Restrictions, Private Use = Yes
|
796
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
797
|
-
'GNU Lesser General Public License v3 or later (LGPLv3+)': [
|
798
|
-
'pytest-redis', # used only privately in testing, not used in server code, not modified, not distributed
|
799
|
-
'mirakuru', # required by pytest-redis (used only where it's used)
|
800
|
-
],
|
801
|
-
|
802
|
-
# DFSG = Debian Free Software Guidelines
|
803
|
-
# Ref: https://en.wikipedia.org/wiki/Debian_Free_Software_Guidelines
|
804
|
-
# Used as an apparent modifier to other licenses, to say they are approved per Debian.
|
805
|
-
# For example in this case, pytest-timeout has license: DFSG approved, MIT License,
|
806
|
-
# but is really just an MIT License that someone has checked is DFSG approved.
|
807
|
-
'DFSG approved': [
|
808
|
-
'pytest-timeout', # MIT Licensed
|
809
|
-
],
|
810
|
-
|
811
|
-
'GNU General Public License (GPL)': [
|
812
|
-
'docutils', # Used only privately as a separate documentation-generation task for ReadTheDocs
|
813
|
-
],
|
814
|
-
|
815
|
-
# Linking = With Restrictions, Private Use = Yes
|
816
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
817
|
-
# 'GNU Lesser General Public License v3 or later (LGPLv3+)',
|
818
|
-
|
819
|
-
# Linking = With Restrictions, Private Use = Yes
|
820
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
821
|
-
'GNU Library or Lesser General Public License (LGPL)': [
|
822
|
-
'psycopg2', # Used at runtime during server operation, but not modified or distributed
|
823
|
-
'psycopg2-binary', # Used at runtime during server operation, but not modified or distributed
|
824
|
-
'chardet', # Potentially used downstream in loadxl to detect charset for text files
|
825
|
-
'pyzmq', # Used in post-deploy-perf-tests, not distributed, and not modified or distributed
|
826
|
-
],
|
827
|
-
|
828
|
-
'MIT*': [
|
829
|
-
|
830
|
-
# This library uses a mix of licenses, but they (MIT, CC0) generally seem permissive.
|
831
|
-
# (It also mentions that some tools for building/testing use other libraries.)
|
832
|
-
# Ref: https://github.com/requirejs/domReady/blob/master/LICENSE
|
833
|
-
'domready',
|
834
|
-
|
835
|
-
# This library is under 'COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.1'
|
836
|
-
# Ref: https://github.com/javaee/jsonp/blob/master/LICENSE.txt
|
837
|
-
# About CDDL ...
|
838
|
-
# Linking = Permissive, Private Use = ?
|
839
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
840
|
-
'jsonp',
|
841
|
-
|
842
|
-
# This library says pretty clearly it intends MIT license.
|
843
|
-
# Ref: https://www.npmjs.com/package/component-indexof
|
844
|
-
# Linking = Permissive, Private Use = Yes
|
845
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
846
|
-
'component-indexof',
|
847
|
-
|
848
|
-
# These look like a pretty straight MIT license.
|
849
|
-
# Linking = Permissive, Private Use = Yes
|
850
|
-
# Ref: https://en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses
|
851
|
-
'mixin', # LICENSE file at https://www.npmjs.com/package/mixin?activeTab=code
|
852
|
-
'stack-trace', # https://github.com/stacktracejs/stacktrace.js/blob/master/LICENSE
|
853
|
-
'typed-function', # LICENSE at https://www.npmjs.com/package/typed-function?activeTab=code
|
854
|
-
|
855
|
-
],
|
856
|
-
}
|
857
|
-
|
858
|
-
|
859
|
-
class C4PythonInfrastructureLicenseChecker(C4InfrastructureLicenseChecker):
|
860
|
-
"""
|
861
|
-
For situations like dcicutils and dcicsnovault where there's no Javascript, this will test just Python.
|
862
|
-
"""
|
863
|
-
LICENSE_FRAMEWORKS = ['python']
|