64 from email.message import Message |
64 from email.message import Message |
65 from typing import Callable, Dict, Iterator, Optional, Sequence |
65 from typing import Callable, Dict, Iterator, Optional, Sequence |
66 |
66 |
67 |
67 |
68 __pkgname__ = "pip-licenses" |
68 __pkgname__ = "pip-licenses" |
69 __version__ = "4.3.4" |
69 __version__ = "4.4.0" |
70 __author__ = "raimon" |
70 __author__ = "raimon" |
71 __license__ = "MIT" |
71 __license__ = "MIT" |
72 __summary__ = ( |
72 __summary__ = ( |
73 "Dump the software license list of Python packages installed with pip." |
73 "Dump the software license list of Python packages installed with pip." |
74 ) |
74 ) |
324 cast(List[str], pkg_info["license_classifier"]), |
324 cast(List[str], pkg_info["license_classifier"]), |
325 cast(str, pkg_info["license"]), |
325 cast(str, pkg_info["license"]), |
326 ) |
326 ) |
327 |
327 |
328 if fail_on_licenses: |
328 if fail_on_licenses: |
329 failed_licenses = case_insensitive_set_intersect( |
329 failed_licenses = set() |
330 license_names, fail_on_licenses |
330 if not args.partial_match: |
331 ) |
331 failed_licenses = case_insensitive_set_intersect( |
|
332 license_names, fail_on_licenses |
|
333 ) |
|
334 else: |
|
335 failed_licenses = case_insensitive_partial_match_set_intersect( |
|
336 license_names, fail_on_licenses |
|
337 ) |
332 if failed_licenses: |
338 if failed_licenses: |
333 sys.stderr.write( |
339 sys.stderr.write( |
334 "fail-on license {} was found for package " |
340 "fail-on license {} was found for package " |
335 "{}:{}\n".format( |
341 "{}:{}\n".format( |
336 "; ".join(sorted(failed_licenses)), |
342 "; ".join(sorted(failed_licenses)), |
339 ) |
345 ) |
340 ) |
346 ) |
341 sys.exit(1) |
347 sys.exit(1) |
342 |
348 |
343 if allow_only_licenses: |
349 if allow_only_licenses: |
344 uncommon_licenses = case_insensitive_set_diff( |
350 uncommon_licenses = set() |
345 license_names, allow_only_licenses |
351 if not args.partial_match: |
346 ) |
352 uncommon_licenses = case_insensitive_set_diff( |
|
353 license_names, allow_only_licenses |
|
354 ) |
|
355 else: |
|
356 uncommon_licenses = case_insensitive_partial_match_set_diff( |
|
357 license_names, allow_only_licenses |
|
358 ) |
347 if len(uncommon_licenses) == len(license_names): |
359 if len(uncommon_licenses) == len(license_names): |
348 sys.stderr.write( |
360 sys.stderr.write( |
349 "license {} not in allow-only licenses was found" |
361 "license {} not in allow-only licenses was found" |
350 " for package {}:{}\n".format( |
362 " for package {}:{}\n".format( |
351 "; ".join(sorted(uncommon_licenses)), |
363 "; ".join(sorted(uncommon_licenses)), |
421 if elem.lower() in set_b_lower: |
433 if elem.lower() in set_b_lower: |
422 common_items.add(elem) |
434 common_items.add(elem) |
423 return common_items |
435 return common_items |
424 |
436 |
425 |
437 |
|
438 def case_insensitive_partial_match_set_intersect(set_a, set_b): |
|
439 common_items = set() |
|
440 for item_a in set_a: |
|
441 for item_b in set_b: |
|
442 if item_b.lower() in item_a.lower(): |
|
443 common_items.add(item_a) |
|
444 return common_items |
|
445 |
|
446 |
|
447 def case_insensitive_partial_match_set_diff(set_a, set_b): |
|
448 uncommon_items = set_a.copy() |
|
449 for item_a in set_a: |
|
450 for item_b in set_b: |
|
451 if item_b.lower() in item_a.lower(): |
|
452 uncommon_items.remove(item_a) |
|
453 return uncommon_items |
|
454 |
|
455 |
426 def case_insensitive_set_diff(set_a, set_b): |
456 def case_insensitive_set_diff(set_a, set_b): |
427 """Same as set.difference() but case-insensitive""" |
457 """Same as set.difference() but case-insensitive""" |
428 uncommon_items = set() |
458 uncommon_items = set() |
429 set_b_lower = {item.lower() for item in set_b} |
459 set_b_lower = {item.lower() for item in set_b} |
430 for elem in set_a: |
460 for elem in set_a: |
572 with_license_file: bool |
602 with_license_file: bool |
573 no_license_path: bool |
603 no_license_path: bool |
574 with_notice_file: bool |
604 with_notice_file: bool |
575 filter_strings: bool |
605 filter_strings: bool |
576 filter_code_page: str |
606 filter_code_page: str |
|
607 partial_match: bool |
577 fail_on: Optional[str] |
608 fail_on: Optional[str] |
578 allow_only: Optional[str] |
609 allow_only: Optional[str] |
579 |
610 |
580 |
611 |
581 class CompatibleArgumentParser(argparse.ArgumentParser): |
612 class CompatibleArgumentParser(argparse.ArgumentParser): |
836 type=str, |
867 type=str, |
837 default=None, |
868 default=None, |
838 help="fail (exit with code 1) on the first occurrence " |
869 help="fail (exit with code 1) on the first occurrence " |
839 "of the licenses not in the semicolon-separated list", |
870 "of the licenses not in the semicolon-separated list", |
840 ) |
871 ) |
|
872 verify_options.add_argument( |
|
873 "--partial-match", |
|
874 action="store_true", |
|
875 default=False, |
|
876 help="enables partial matching for --allow-only/--fail-on", |
|
877 ) |
841 |
878 |
842 return parser |
879 return parser |
843 |
880 |
844 |
881 |
845 def main(): # pragma: no cover |
882 def main(): # pragma: no cover |