22import subprocess
33import sys
44import urllib .request
5+ from dataclasses import dataclass
56from pathlib import Path
67from typing import Literal , assert_never
78
9+ from pydantic import TypeAdapter
10+
811
912class LicenseError (Exception ):
1013 # License違反があった場合、このエラーを出します。
@@ -44,9 +47,33 @@ def get_license_text(text_url: str) -> str:
4447
4548
4649def generate_licenses () -> list [License ]:
47- # pip
50+ raw_licenses = acquire_licenses_of_pip_managed_libraries ()
51+ licenses = update_licenses (raw_licenses )
52+ validate_license_compliance (licenses )
53+ add_licenses_manually (licenses )
54+
55+ return licenses
56+
57+
58+ @dataclass
59+ class PipLicense :
60+ """`pip-license` により得られる依存ライブラリの情報"""
61+
62+ License : str
63+ Name : str
64+ URL : str
65+ Version : str
66+ LicenseText : str
67+
68+
69+ _pip_licenses_adapter = TypeAdapter (list [PipLicense ])
70+
71+
72+ def acquire_licenses_of_pip_managed_libraries () -> list [PipLicense ]:
73+ """Pipで管理されている依存ライブラリのライセンス情報を取得する。"""
74+ # ライセンス一覧を取得する
4875 try :
49- pip_licenses_output = subprocess .run (
76+ pip_licenses_json = subprocess .run (
5077 [
5178 sys .executable ,
5279 "-m" ,
@@ -62,16 +89,13 @@ def generate_licenses() -> list[License]:
6289 ).stdout .decode ()
6390 except subprocess .CalledProcessError as e :
6491 raise Exception (f"command output:\n { e .stderr and e .stderr .decode ()} " ) from e
65-
66- licenses_json = json .loads (pip_licenses_output )
67- licenses = generate_licenses_from_licenses_json (licenses_json )
68- validate_license_compliance (licenses )
69- add_licenses_manually (licenses )
70-
92+ # ライセンス情報の形式をチェックする
93+ licenses = _pip_licenses_adapter .validate_json (pip_licenses_json )
7194 return licenses
7295
7396
74- def generate_licenses_from_licenses_json (licenses_json : dict ) -> list [License ]:
97+ def update_licenses (raw_licenses : list [PipLicense ]) -> list [License ]:
98+ """pip から取得したライセンス情報を更新する。"""
7599 package_to_license_url = {
76100 "distlib" : "https://bitbucket.org/pypa/distlib/raw/7d93712134b28401407da27382f2b6236c87623a/LICENSE.txt" ,
77101 "future" : "https://raw.githubusercontent.com/PythonCharmers/python-future/master/LICENSE.txt" ,
@@ -85,37 +109,37 @@ def generate_licenses_from_licenses_json(licenses_json: dict) -> list[License]:
85109 "webencodings" : "https://raw.githubusercontent.com/gsnedders/python-webencodings/fa2cb5d75ab41e63ace691bc0825d3432ba7d694/LICENSE" ,
86110 }
87111
88- licenses = []
112+ updated_licenses = []
89113
90- for license_json in licenses_json :
91- package_name : str = license_json [ " Name" ] .lower ()
114+ for raw_license in raw_licenses :
115+ package_name = raw_license . Name .lower ()
92116
93- if license_json ["LicenseText" ] == "UNKNOWN" :
94- if package_name == "core" and license_json ["Version" ] == "0.0.0" :
117+ # ライセンス文が pip から取得できていない場合、pip 外から補う
118+ if raw_license .LicenseText == "UNKNOWN" :
119+ if package_name == "core" and raw_license .Version == "0.0.0" :
95120 continue
96121 if package_name not in package_to_license_url :
97122 # ライセンスがpypiに無い
98123 raise Exception (f"No License info provided for { package_name } " )
99- # ライセンス文を pip 外で取得されたもので上書きする
100124 text_url = package_to_license_url [package_name ]
101- license_json [ " LicenseText" ] = get_license_text (text_url )
125+ raw_license . LicenseText = get_license_text (text_url )
102126
103127 # soxr
104128 if package_name == "soxr" :
105129 text_url = "https://raw.githubusercontent.com/dofuuz/python-soxr/v0.3.6/LICENSE.txt"
106- license_json [ " LicenseText" ] = get_license_text (text_url )
130+ raw_license . LicenseText = get_license_text (text_url )
107131
108- licenses .append (
132+ updated_licenses .append (
109133 License (
110- package_name = license_json [ " Name" ] ,
111- package_version = license_json [ " Version" ] ,
112- license_name = license_json [ " License" ] ,
113- license_text = license_json [ " LicenseText" ] ,
134+ package_name = raw_license . Name ,
135+ package_version = raw_license . Version ,
136+ license_name = raw_license . License ,
137+ license_text = raw_license . LicenseText ,
114138 license_text_type = "raw" ,
115139 )
116140 )
117141
118- return licenses
142+ return updated_licenses
119143
120144
121145def validate_license_compliance (licenses : list [License ]) -> None :
0 commit comments