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.
@@ -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']