Coverage for pyTooling/Licensing/__init__.py: 92%
74 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-25 22:22 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-25 22:22 +0000
1# ==================================================================================================================== #
2# _____ _ _ _ _ _ #
3# _ __ _ |_ _|__ ___ | (_)_ __ __ _ | | (_) ___ ___ _ __ ___(_)_ __ __ _ #
4# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` | | | | |/ __/ _ \ '_ \/ __| | '_ \ / _` | #
5# | |_) | |_| || | (_) | (_) | | | | | | (_| |_| |___| | (_| __/ | | \__ \ | | | | (_| | #
6# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)_____|_|\___\___|_| |_|___/_|_| |_|\__, | #
7# |_| |___/ |___/ |___/ #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2017-2025 Patrick Lehmann - Bötzingen, Germany #
15# #
16# Licensed under the Apache License, Version 2.0 (the "License"); #
17# you may not use this file except in compliance with the License. #
18# You may obtain a copy of the License at #
19# #
20# http://www.apache.org/licenses/LICENSE-2.0 #
21# #
22# Unless required by applicable law or agreed to in writing, software #
23# distributed under the License is distributed on an "AS IS" BASIS, #
24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
25# See the License for the specific language governing permissions and #
26# limitations under the License. #
27# #
28# SPDX-License-Identifier: Apache-2.0 #
29# ==================================================================================================================== #
30#
31"""
32The Licensing module implements mapping tables for various license names and identifiers.
34.. seealso::
36 List of SPDX identifiers:
38 * https://spdx.org/licenses/
39 * https://github.com/spdx/license-list-XML
41 List of `Python classifiers <https://pypi.org/classifiers/>`__
43.. hint:: See :ref:`high-level help <LICENSING>` for explanations and usage examples.
44"""
45from dataclasses import dataclass
46from sys import version_info # needed for versions before Python 3.11
47from typing import Any, Dict, Optional as Nullable
50try:
51 from pyTooling.Decorators import export, readonly
52 from pyTooling.MetaClasses import ExtendedType
53except (ImportError, ModuleNotFoundError): # pragma: no cover
54 print("[pyTooling.Licensing] Could not import from 'pyTooling.*'!")
56 try:
57 from Decorators import export, readonly
58 from MetaClasses import ExtendedType
59 except (ImportError, ModuleNotFoundError) as ex: # pragma: no cover
60 print("[pyTooling.Licensing] Could not import directly!")
61 raise ex
64__all__ = [
65 "PYTHON_LICENSE_NAMES",
67 "Apache_2_0_License",
68 "BSD_3_Clause_License",
69 "GPL_2_0_or_later",
70 "MIT_License",
72 "SPDX_INDEX"
73]
76@export
77@dataclass
78class PythonLicenseName:
79 """A *data class* to represent the license's short name and the package classifier for a license."""
81 ShortName: str #: License's short name
82 Classifier: str #: Package classifier for a license.
84 def __str__(self) -> str:
85 """
86 The string representation of this name tuple returns the short name of the license.
88 :returns: Short name of the license.
89 """
90 return self.ShortName
93#: Mapping of SPDX identifiers to Python license names
94PYTHON_LICENSE_NAMES: Dict[str, PythonLicenseName] = {
95 "Apache-2.0": PythonLicenseName("Apache 2.0", "Apache Software License"),
96 "BSD-3-Clause": PythonLicenseName("BSD", "BSD License"),
97 "MIT": PythonLicenseName("MIT", "MIT License"),
98 "GPL-2.0-or-later": PythonLicenseName("GPL-2.0-or-later", "GNU General Public License v2 or later (GPLv2+)"),
99}
102@export
103class License(metaclass=ExtendedType, slots=True):
104 """Representation of a license."""
106 _spdxIdentifier: str #: Unique SPDX identifier.
107 _name: str #: Name of the license.
108 _osiApproved: bool #: OSI approval status
109 _fsfApproved: bool #: FSF approval status
111 def __init__(self, spdxIdentifier: str, name: str, osiApproved: bool = False, fsfApproved: bool = False):
112 self._spdxIdentifier = spdxIdentifier
113 self._name = name
114 self._osiApproved = osiApproved
115 self._fsfApproved = fsfApproved
117 @readonly
118 def Name(self) -> str:
119 """
120 Returns the license' name.
122 :returns: License name.
123 """
124 return self._name
126 @readonly
127 def SPDXIdentifier(self) -> str:
128 """
129 Returns the license' unique `SPDX identifier <https://spdx.org/licenses/>`__.
131 :returns: The the unique SPDX identifier.
132 """
133 return self._spdxIdentifier
135 @readonly
136 def OSIApproved(self) -> bool:
137 """
138 Returns true, if the license is approved by OSI (`Open Source Initiative <https://opensource.org/>`__).
140 :returns: ``True``, if the license is approved by the Open Source Initiative.
141 """
142 return self._osiApproved
144 @readonly
145 def FSFApproved(self) -> bool:
146 """
147 Returns true, if the license is approved by FSF (`Free Software Foundation <https://www.fsf.org/>`__).
149 :returns: ``True``, if the license is approved by the Free Software Foundation.
150 """
151 return self._fsfApproved
153 @readonly
154 def PythonLicenseName(self) -> str:
155 """
156 Returns the Python license name for this license if it's defined.
158 :returns: The Python license name.
159 :raises ValueError: If there is no license name defined for the license. |br| (See and check :data:`~pyTooling.Licensing.PYTHON_LICENSE_NAMES`)
160 """
161 try:
162 item: PythonLicenseName = PYTHON_LICENSE_NAMES[self._spdxIdentifier]
163 except KeyError as ex:
164 raise ValueError("License has no Python specify information.") from ex
166 return item.ShortName
168 @readonly
169 def PythonClassifier(self) -> str:
170 """
171 Returns the Python package classifier for this license if it's defined.
173 :returns: The Python package classifier.
174 :raises ValueError: If there is no classifier defined for the license. |br| (See and check :data:`~pyTooling.Licensing.PYTHON_LICENSE_NAMES`)
176 .. seealso::
178 List of `Python classifiers <https://pypi.org/classifiers/>`__
179 """
180 try:
181 item: PythonLicenseName = PYTHON_LICENSE_NAMES[self._spdxIdentifier]
182 except KeyError as ex:
183 raise ValueError(f"License has no Python specify information.") from ex
185 osi = "OSI Approved :: " if self._osiApproved else ""
186 return f"License :: {osi}{item.Classifier}"
188 def __eq__(self, other: Any) -> bool:
189 """
190 Returns true, if both licenses are identical (comparison based on SPDX identifiers).
192 :returns: ``True``, if both licenses are identical.
193 :raises TypeError: If second operand is not of type :class:`License` or :class:`str`.
194 """
195 if isinstance(other, License):
196 return self._spdxIdentifier == other._spdxIdentifier
197 else:
198 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by equal operator.")
199 if version_info >= (3, 11): # pragma: no cover
200 ex.add_note(f"Supported types for second operand: License, str")
201 raise ex
203 def __ne__(self, other: Any) -> bool:
204 """
205 Returns true, if both licenses are not identical (comparison based on SPDX identifiers).
207 :returns: ``True``, if both licenses are not identical.
208 :raises TypeError: If second operand is not of type :class:`License` or :class:`str`.
209 """
210 if isinstance(other, License):
211 return self._spdxIdentifier != other._spdxIdentifier
212 else:
213 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by unequal operator.")
214 if version_info >= (3, 11): # pragma: no cover
215 ex.add_note(f"Supported types for second operand: License, str")
216 raise ex
218 def __le__(self, other: Any) -> bool:
219 """Returns true, if both licenses are compatible."""
220 raise NotImplementedError("License compatibility check is not yet implemented.")
222 def __ge__(self, other: Any) -> bool:
223 """Returns true, if both licenses are compatible."""
224 raise NotImplementedError("License compatibility check is not yet implemented.")
226 def __repr__(self) -> str:
227 """
228 Returns the internal unique representation (a.k.a SPDX identifier).
230 :returns: SPDX identifier of the license.
231 """
232 return self._spdxIdentifier
234 def __str__(self) -> str:
235 """
236 Returns the license' name.
238 :returns: Name of the license.
239 """
240 return self._name
243Apache_2_0_License = License("Apache-2.0", "Apache License 2.0", True, True)
244BSD_3_Clause_License = License("BSD-3-Clause", "BSD 3-Clause Revised License", True, True)
245GPL_2_0_or_later = License("GPL-2.0-or-later", "GNU General Public License v2.0 or later", True, True)
246MIT_License = License("MIT", "MIT License", True, True)
249#: Mapping of predefined licenses
250SPDX_INDEX: Dict[str, License] = {
251 "Apache-2.0": Apache_2_0_License,
252 "BSD-3-Clause": BSD_3_Clause_License,
253 "GPL-2.0-or-later": GPL_2_0_or_later,
254 "MIT": MIT_License
255}