licence-normaliser 0.3.2__tar.gz

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.
Files changed (53) hide show
  1. licence_normaliser-0.3.2/LICENSE +21 -0
  2. licence_normaliser-0.3.2/PKG-INFO +349 -0
  3. licence_normaliser-0.3.2/README.rst +297 -0
  4. licence_normaliser-0.3.2/pyproject.toml +331 -0
  5. licence_normaliser-0.3.2/setup.cfg +4 -0
  6. licence_normaliser-0.3.2/src/licence_normaliser/__init__.py +31 -0
  7. licence_normaliser-0.3.2/src/licence_normaliser/_cache.py +55 -0
  8. licence_normaliser-0.3.2/src/licence_normaliser/_core.py +17 -0
  9. licence_normaliser-0.3.2/src/licence_normaliser/_models.py +111 -0
  10. licence_normaliser-0.3.2/src/licence_normaliser/_normaliser.py +587 -0
  11. licence_normaliser-0.3.2/src/licence_normaliser/_trace.py +70 -0
  12. licence_normaliser-0.3.2/src/licence_normaliser/cli/__init__.py +8 -0
  13. licence_normaliser-0.3.2/src/licence_normaliser/cli/_main.py +165 -0
  14. licence_normaliser-0.3.2/src/licence_normaliser/data/aliases/aliases.json +725 -0
  15. licence_normaliser-0.3.2/src/licence_normaliser/data/creativecommons/creativecommons.json +227 -0
  16. licence_normaliser-0.3.2/src/licence_normaliser/data/opendefinition/opendefinition.json +1569 -0
  17. licence_normaliser-0.3.2/src/licence_normaliser/data/osi/osi.json +1 -0
  18. licence_normaliser-0.3.2/src/licence_normaliser/data/prose/prose_patterns.json +44 -0
  19. licence_normaliser-0.3.2/src/licence_normaliser/data/publishers/publishers.json +340 -0
  20. licence_normaliser-0.3.2/src/licence_normaliser/data/scancode_licensedb/scancode_licensedb.json +32283 -0
  21. licence_normaliser-0.3.2/src/licence_normaliser/data/spdx/spdx.json +9119 -0
  22. licence_normaliser-0.3.2/src/licence_normaliser/data/urls/url_map.json +129 -0
  23. licence_normaliser-0.3.2/src/licence_normaliser/defaults.py +239 -0
  24. licence_normaliser-0.3.2/src/licence_normaliser/exceptions.py +41 -0
  25. licence_normaliser-0.3.2/src/licence_normaliser/parsers/__init__.py +0 -0
  26. licence_normaliser-0.3.2/src/licence_normaliser/parsers/alias.py +150 -0
  27. licence_normaliser-0.3.2/src/licence_normaliser/parsers/creativecommons.py +280 -0
  28. licence_normaliser-0.3.2/src/licence_normaliser/parsers/opendefinition.py +64 -0
  29. licence_normaliser-0.3.2/src/licence_normaliser/parsers/osi.py +85 -0
  30. licence_normaliser-0.3.2/src/licence_normaliser/parsers/prose.py +93 -0
  31. licence_normaliser-0.3.2/src/licence_normaliser/parsers/publisher.py +87 -0
  32. licence_normaliser-0.3.2/src/licence_normaliser/parsers/scancode_licensedb.py +63 -0
  33. licence_normaliser-0.3.2/src/licence_normaliser/parsers/spdx.py +65 -0
  34. licence_normaliser-0.3.2/src/licence_normaliser/plugins.py +124 -0
  35. licence_normaliser-0.3.2/src/licence_normaliser/py.typed +0 -0
  36. licence_normaliser-0.3.2/src/licence_normaliser/tests/__init__.py +5 -0
  37. licence_normaliser-0.3.2/src/licence_normaliser/tests/conftest.py +22 -0
  38. licence_normaliser-0.3.2/src/licence_normaliser/tests/test_alias_expansion.py +276 -0
  39. licence_normaliser-0.3.2/src/licence_normaliser/tests/test_aliases.py +135 -0
  40. licence_normaliser-0.3.2/src/licence_normaliser/tests/test_cache.py +85 -0
  41. licence_normaliser-0.3.2/src/licence_normaliser/tests/test_cli.py +113 -0
  42. licence_normaliser-0.3.2/src/licence_normaliser/tests/test_core.py +232 -0
  43. licence_normaliser-0.3.2/src/licence_normaliser/tests/test_exceptions.py +92 -0
  44. licence_normaliser-0.3.2/src/licence_normaliser/tests/test_integration.py +469 -0
  45. licence_normaliser-0.3.2/src/licence_normaliser/tests/test_models.py +92 -0
  46. licence_normaliser-0.3.2/src/licence_normaliser/tests/test_prose.py +74 -0
  47. licence_normaliser-0.3.2/src/licence_normaliser/tests/test_publisher.py +460 -0
  48. licence_normaliser-0.3.2/src/licence_normaliser.egg-info/PKG-INFO +349 -0
  49. licence_normaliser-0.3.2/src/licence_normaliser.egg-info/SOURCES.txt +51 -0
  50. licence_normaliser-0.3.2/src/licence_normaliser.egg-info/dependency_links.txt +1 -0
  51. licence_normaliser-0.3.2/src/licence_normaliser.egg-info/entry_points.txt +2 -0
  52. licence_normaliser-0.3.2/src/licence_normaliser.egg-info/requires.txt +30 -0
  53. licence_normaliser-0.3.2/src/licence_normaliser.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Artur Barseghyan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,349 @@
1
+ Metadata-Version: 2.4
2
+ Name: licence-normaliser
3
+ Version: 0.3.2
4
+ Summary: Comprehensive license normalisation with a three-level hierarchy.
5
+ Author-email: Artur Barseghyan <artur.barseghyan@gmail.com>
6
+ Maintainer-email: Artur Barseghyan <artur.barseghyan@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/barseghyanartur/licence-normaliser/
9
+ Project-URL: Repository, https://github.com/barseghyanartur/licence-normaliser/
10
+ Project-URL: Issues, https://github.com/barseghyanartur/licence-normaliser/issues
11
+ Keywords: license,normalisation,spdx,creative commons,open source
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
21
+ Classifier: Programming Language :: Python
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/x-rst
25
+ License-File: LICENSE
26
+ Provides-Extra: all
27
+ Requires-Dist: licence-normaliser[build,dev,docs,test]; extra == "all"
28
+ Provides-Extra: dev
29
+ Requires-Dist: detect-secrets; extra == "dev"
30
+ Requires-Dist: doc8; extra == "dev"
31
+ Requires-Dist: ipython; extra == "dev"
32
+ Requires-Dist: mypy; extra == "dev"
33
+ Requires-Dist: ruff; extra == "dev"
34
+ Requires-Dist: uv; extra == "dev"
35
+ Provides-Extra: test
36
+ Requires-Dist: pytest; extra == "test"
37
+ Requires-Dist: pytest-cov; extra == "test"
38
+ Requires-Dist: pytest-codeblock; extra == "test"
39
+ Provides-Extra: docs
40
+ Requires-Dist: sphinx; extra == "docs"
41
+ Requires-Dist: sphinx-autobuild; extra == "docs"
42
+ Requires-Dist: sphinx-rtd-theme>=1.3.0; extra == "docs"
43
+ Requires-Dist: sphinx-no-pragma; extra == "docs"
44
+ Requires-Dist: sphinx-markdown-builder; extra == "docs"
45
+ Requires-Dist: sphinx-llms-txt-link; extra == "docs"
46
+ Requires-Dist: sphinx-source-tree; extra == "docs"
47
+ Provides-Extra: build
48
+ Requires-Dist: build; extra == "build"
49
+ Requires-Dist: twine; extra == "build"
50
+ Requires-Dist: wheel; extra == "build"
51
+ Dynamic: license-file
52
+
53
+ ==================
54
+ licence-normaliser
55
+ ==================
56
+
57
+ .. image:: https://raw.githubusercontent.com/barseghyanartur/licence-normaliser/main/docs/_static/licence_normaliser_logo.webp
58
+ :alt: licence-normaliser logo
59
+ :align: center
60
+
61
+ Comprehensive license normalsation with a three-level hierarchy.
62
+
63
+ .. image:: https://img.shields.io/pypi/v/licence-normaliser.svg
64
+ :target: https://pypi.python.org/pypi/licence-normaliser
65
+ :alt: PyPI Version
66
+
67
+ .. image:: https://img.shields.io/pypi/pyversions/licence-normaliser.svg
68
+ :target: https://pypi.python.org/pypi/licence-normaliser/
69
+ :alt: Supported Python versions
70
+
71
+ .. image:: https://github.com/barseghyanartur/licence-normaliser/actions/workflows/test.yml/badge.svg?branch=main
72
+ :target: https://github.com/barseghyanartur/licence-normaliser/actions
73
+ :alt: Build Status
74
+
75
+ .. image:: https://readthedocs.org/projects/licence-normaliser/badge/?version=latest
76
+ :target: http://licence-normaliser.readthedocs.io
77
+ :alt: Documentation Status
78
+
79
+ .. image:: https://img.shields.io/badge/docs-llms.txt-blue
80
+ :target: https://licence-normaliser.readthedocs.io/en/latest/llms.txt
81
+ :alt: llms.txt - documentation for LLMs
82
+
83
+ .. image:: https://img.shields.io/badge/license-MIT-blue.svg
84
+ :target: https://github.com/barseghyanartur/licence-normaliser/#License
85
+ :alt: MIT
86
+
87
+ .. image:: https://coveralls.io/repos/github/barseghyanartur/licence-normaliser/badge.svg?branch=main&service=github
88
+ :target: https://coveralls.io/github/barseghyanartur/licence-normaliser?branch=main
89
+ :alt: Coverage
90
+
91
+ ``licence-normaliser`` is a comprehensive license normalisation library that
92
+ maps any license representation (SPDX tokens, URLs, prose descriptions) to a
93
+ canonical three-level hierarchy.
94
+
95
+ Features
96
+ ========
97
+
98
+ - **Three-level hierarchy** - LicenseFamily → LicenseName → LicenseVersion.
99
+ - **Wide format support** - SPDX tokens, URLs, prose descriptions.
100
+ - **Creative Commons support** - Full CC family with versions and IGO variants.
101
+ - **Publisher-specific licenses** - Springer, Nature, Elsevier, Wiley, ACS,
102
+ and more.
103
+ - **File-driven data** - Add aliases, URLs, and patterns by editing JSON files.
104
+ No Python code changes required for new synonyms.
105
+ - **Pluggable parsers** - Drop in a new parser class to ingest
106
+ any external license registry. Parsers implement plugin interfaces
107
+ (``RegistryPlugin``, ``URLPlugin``, etc.).
108
+ - **Strict mode** - Raise ``LicenseNotFoundError`` instead of silently
109
+ returning ``"unknown"``.
110
+ - **Caching** - LRU caching for performance.
111
+ - **CLI** - Command-line interface with ``--strict`` and ``--explain`` support.
112
+
113
+ Hierarchy
114
+ =========
115
+
116
+ The library uses a three-level hierarchy:
117
+
118
+ 1. **LicenseFamily** - broad bucket: ``"cc"``, ``"osi"``, ``"copyleft"``,
119
+ ``"publisher-tdm"``, ...
120
+ 2. **LicenseName** - version-free: ``"cc-by"``, ``"cc-by-nc-nd"``, ``"mit"``,
121
+ ``"wiley-tdm"``
122
+ 3. **LicenseVersion** - fully resolved: ``"cc-by-3.0"``, ``"cc-by-nc-nd-4.0"``
123
+
124
+ Installation
125
+ ============
126
+
127
+ With ``uv``:
128
+
129
+ .. code-block:: sh
130
+
131
+ uv pip install licence-normaliser
132
+
133
+ Or with ``pip``:
134
+
135
+ .. code-block:: sh
136
+
137
+ pip install licence-normaliser
138
+
139
+ Quick start
140
+ ===========
141
+
142
+ .. code-block:: python
143
+ :name: test_quick_start
144
+
145
+ from licence_normaliser import normalise_license
146
+
147
+ v = normalise_license("CC BY-NC-ND 4.0")
148
+ str(v) # "cc-by-nc-nd-4.0" ← LicenseVersion
149
+ str(v.license) # "cc-by-nc-nd" ← LicenseName
150
+ str(v.license.family) # "cc" ← LicenseFamily
151
+
152
+ Strict mode
153
+ ===========
154
+
155
+ By default, unresolvable inputs return an ``"unknown"`` result. Pass
156
+ ``strict=True`` to raise ``LicenseNotFoundError`` instead:
157
+
158
+ .. code-block:: python
159
+ :name: test_strict_mode
160
+
161
+ from licence_normaliser import normalise_license
162
+ from licence_normaliser.exceptions import LicenseNotFoundError
163
+
164
+ # Silent fallback (default)
165
+ v = normalise_license("some-unknown-string")
166
+ v.family.key # "unknown"
167
+
168
+ # Strict: raises on unresolvable input
169
+ try:
170
+ v = normalise_license("some-unknown-string", strict=True)
171
+ except LicenseNotFoundError as exc:
172
+ print(exc.raw) # original input
173
+ print(exc.cleaned) # cleaned form that failed lookup
174
+
175
+ Trace / Explain
176
+ ===============
177
+
178
+ Set ``ENABLE_LICENCE_NORMALISER_TRACE=1`` or pass ``trace=True`` to get
179
+ resolution traces showing how the license was matched:
180
+
181
+ .. code-block:: python
182
+ :name: test_trace
183
+
184
+ from licence_normaliser import normalise_license
185
+
186
+ # Via function
187
+ v = normalise_license("cc by-nc-nd 3.0 igo", trace=True)
188
+ print(v.explain())
189
+
190
+ # Via class
191
+ from licence_normaliser import LicenseNormaliser
192
+ ln = LicenseNormaliser(trace=True)
193
+ v = ln.normalise_license("MIT")
194
+ print(v.explain())
195
+
196
+ Output shows the resolution pipeline (alias → registry → url → prose →
197
+ fallback) and which source file + line matched:
198
+
199
+ .. code-block:: text
200
+
201
+ Input: 'cc by-nc-nd 3.0 igo' → 'cc by-nc-nd 3.0 igo'
202
+ [✓] alias: 'cc by-nc-nd 3.0 igo' → 'cc-by-nc-nd-3.0-igo' (line 139 in aliases.json)
203
+
204
+ Result:
205
+ version_key: 'cc-by-nc-nd-3.0-igo'
206
+ name_key: 'cc-by-nc-nd'
207
+ family_key: 'cc'
208
+
209
+ The trace can also be accessed via ``v._trace`` for programmatic use.
210
+
211
+ Batch normalisation
212
+ ===================
213
+
214
+ .. code-block:: python
215
+ :name: test_batch_normalisation
216
+
217
+ from licence_normaliser import normalise_licenses
218
+
219
+ results = normalise_licenses(["MIT", "Apache-2.0", "CC BY 4.0"])
220
+ for r in results:
221
+ print(r.key)
222
+
223
+ # Strict batch - raises on first unresolvable
224
+ results = normalise_licenses(["MIT", "Apache-2.0"], strict=True)
225
+
226
+ Custom plugins
227
+ ==============
228
+
229
+ The ``LicenseNormaliser`` class lets you inject custom plugin classes for
230
+ specialised use cases:
231
+
232
+ .. code-block:: python
233
+ :name: test_custom_plugins
234
+
235
+ from licence_normaliser import LicenseNormaliser
236
+ from licence_normaliser.parsers.alias import AliasParser
237
+ from licence_normaliser.parsers.spdx import SPDXParser
238
+
239
+ # Use only SPDX + Alias plugins (no CC, no publisher URLs)
240
+ ln = LicenseNormaliser(
241
+ registry=[SPDXParser],
242
+ alias=[AliasParser],
243
+ family=[AliasParser],
244
+ name=[AliasParser],
245
+ cache=True,
246
+ cache_maxsize=8192,
247
+ )
248
+
249
+ # MIT resolves via SPDX parser
250
+ assert str(ln.normalise_license("MIT")) == "mit"
251
+
252
+ # CC BY resolves via Alias
253
+ assert str(ln.normalise_license("CC BY-NC-ND 4.0")) == "cc-by-nc-nd-4.0"
254
+
255
+ .. note::
256
+
257
+ Explicit plugin passing is optional — ``LicenseNormaliser()``
258
+ automatically loads defaults. Use the pattern above only if you need
259
+ custom plugins or reduce number of plugins loaded.
260
+
261
+ For caching, ``LicenseNormaliser`` wraps the resolution method
262
+ with ``lru_cache``.
263
+ Disable it by passing ``cache=False`` for debugging:
264
+
265
+ .. code-block:: python
266
+ :name: test_caching
267
+
268
+ from licence_normaliser import LicenseNormaliser
269
+
270
+ ln = LicenseNormaliser(cache=False)
271
+ result = ln.normalise_license("MIT")
272
+
273
+ Update data (CLI)
274
+ =================
275
+
276
+ .. code-block:: sh
277
+
278
+ licence-normaliser update-data --force
279
+ # Fetches fresh SPDX, OpenDefinition, OSI, CreativeCommons, and ScanCode JSONs
280
+
281
+ Integration tests (public API only)
282
+ ===================================
283
+
284
+ All integration tests live in
285
+ ``src/licence_normaliser/tests/test_integration.py``
286
+ and only import the public API.
287
+
288
+ CLI usage
289
+ =========
290
+
291
+ Normalise a single license:
292
+
293
+ .. code-block:: sh
294
+
295
+ licence-normaliser normalise "MIT"
296
+ # Output: mit
297
+
298
+ licence-normaliser normalise --full "CC BY 4.0"
299
+ # Output:
300
+ # Key: cc-by-4.0
301
+ # URL: https://creativecommons.org/licenses/by/4.0/
302
+ # License: cc-by
303
+ # Family: cc
304
+
305
+ licence-normaliser normalise --strict "totally-unknown"
306
+ # Exits with code 1 and prints an error
307
+
308
+ Batch normalise:
309
+
310
+ .. code-block:: sh
311
+
312
+ licence-normaliser batch MIT "Apache-2.0" "CC BY 4.0"
313
+ licence-normaliser batch --strict MIT "Apache-2.0"
314
+
315
+ Exceptions
316
+ ==========
317
+
318
+ .. code-block:: python
319
+ :name: test_exceptions
320
+
321
+ from licence_normaliser.exceptions import (
322
+ LicenseNormaliserError, # base class
323
+ LicenseNotFoundError, # raised by strict mode
324
+ )
325
+
326
+ Testing
327
+ =======
328
+
329
+ All tests run inside Docker:
330
+
331
+ .. code-block:: sh
332
+
333
+ make test
334
+
335
+ To test a specific Python version:
336
+
337
+ .. code-block:: sh
338
+
339
+ make test-env ENV=py312
340
+
341
+ License
342
+ =======
343
+
344
+ MIT
345
+
346
+ Author
347
+ ======
348
+
349
+ Artur Barseghyan <artur.barseghyan@gmail.com>
@@ -0,0 +1,297 @@
1
+ ==================
2
+ licence-normaliser
3
+ ==================
4
+
5
+ .. image:: https://raw.githubusercontent.com/barseghyanartur/licence-normaliser/main/docs/_static/licence_normaliser_logo.webp
6
+ :alt: licence-normaliser logo
7
+ :align: center
8
+
9
+ Comprehensive license normalsation with a three-level hierarchy.
10
+
11
+ .. image:: https://img.shields.io/pypi/v/licence-normaliser.svg
12
+ :target: https://pypi.python.org/pypi/licence-normaliser
13
+ :alt: PyPI Version
14
+
15
+ .. image:: https://img.shields.io/pypi/pyversions/licence-normaliser.svg
16
+ :target: https://pypi.python.org/pypi/licence-normaliser/
17
+ :alt: Supported Python versions
18
+
19
+ .. image:: https://github.com/barseghyanartur/licence-normaliser/actions/workflows/test.yml/badge.svg?branch=main
20
+ :target: https://github.com/barseghyanartur/licence-normaliser/actions
21
+ :alt: Build Status
22
+
23
+ .. image:: https://readthedocs.org/projects/licence-normaliser/badge/?version=latest
24
+ :target: http://licence-normaliser.readthedocs.io
25
+ :alt: Documentation Status
26
+
27
+ .. image:: https://img.shields.io/badge/docs-llms.txt-blue
28
+ :target: https://licence-normaliser.readthedocs.io/en/latest/llms.txt
29
+ :alt: llms.txt - documentation for LLMs
30
+
31
+ .. image:: https://img.shields.io/badge/license-MIT-blue.svg
32
+ :target: https://github.com/barseghyanartur/licence-normaliser/#License
33
+ :alt: MIT
34
+
35
+ .. image:: https://coveralls.io/repos/github/barseghyanartur/licence-normaliser/badge.svg?branch=main&service=github
36
+ :target: https://coveralls.io/github/barseghyanartur/licence-normaliser?branch=main
37
+ :alt: Coverage
38
+
39
+ ``licence-normaliser`` is a comprehensive license normalisation library that
40
+ maps any license representation (SPDX tokens, URLs, prose descriptions) to a
41
+ canonical three-level hierarchy.
42
+
43
+ Features
44
+ ========
45
+
46
+ - **Three-level hierarchy** - LicenseFamily → LicenseName → LicenseVersion.
47
+ - **Wide format support** - SPDX tokens, URLs, prose descriptions.
48
+ - **Creative Commons support** - Full CC family with versions and IGO variants.
49
+ - **Publisher-specific licenses** - Springer, Nature, Elsevier, Wiley, ACS,
50
+ and more.
51
+ - **File-driven data** - Add aliases, URLs, and patterns by editing JSON files.
52
+ No Python code changes required for new synonyms.
53
+ - **Pluggable parsers** - Drop in a new parser class to ingest
54
+ any external license registry. Parsers implement plugin interfaces
55
+ (``RegistryPlugin``, ``URLPlugin``, etc.).
56
+ - **Strict mode** - Raise ``LicenseNotFoundError`` instead of silently
57
+ returning ``"unknown"``.
58
+ - **Caching** - LRU caching for performance.
59
+ - **CLI** - Command-line interface with ``--strict`` and ``--explain`` support.
60
+
61
+ Hierarchy
62
+ =========
63
+
64
+ The library uses a three-level hierarchy:
65
+
66
+ 1. **LicenseFamily** - broad bucket: ``"cc"``, ``"osi"``, ``"copyleft"``,
67
+ ``"publisher-tdm"``, ...
68
+ 2. **LicenseName** - version-free: ``"cc-by"``, ``"cc-by-nc-nd"``, ``"mit"``,
69
+ ``"wiley-tdm"``
70
+ 3. **LicenseVersion** - fully resolved: ``"cc-by-3.0"``, ``"cc-by-nc-nd-4.0"``
71
+
72
+ Installation
73
+ ============
74
+
75
+ With ``uv``:
76
+
77
+ .. code-block:: sh
78
+
79
+ uv pip install licence-normaliser
80
+
81
+ Or with ``pip``:
82
+
83
+ .. code-block:: sh
84
+
85
+ pip install licence-normaliser
86
+
87
+ Quick start
88
+ ===========
89
+
90
+ .. code-block:: python
91
+ :name: test_quick_start
92
+
93
+ from licence_normaliser import normalise_license
94
+
95
+ v = normalise_license("CC BY-NC-ND 4.0")
96
+ str(v) # "cc-by-nc-nd-4.0" ← LicenseVersion
97
+ str(v.license) # "cc-by-nc-nd" ← LicenseName
98
+ str(v.license.family) # "cc" ← LicenseFamily
99
+
100
+ Strict mode
101
+ ===========
102
+
103
+ By default, unresolvable inputs return an ``"unknown"`` result. Pass
104
+ ``strict=True`` to raise ``LicenseNotFoundError`` instead:
105
+
106
+ .. code-block:: python
107
+ :name: test_strict_mode
108
+
109
+ from licence_normaliser import normalise_license
110
+ from licence_normaliser.exceptions import LicenseNotFoundError
111
+
112
+ # Silent fallback (default)
113
+ v = normalise_license("some-unknown-string")
114
+ v.family.key # "unknown"
115
+
116
+ # Strict: raises on unresolvable input
117
+ try:
118
+ v = normalise_license("some-unknown-string", strict=True)
119
+ except LicenseNotFoundError as exc:
120
+ print(exc.raw) # original input
121
+ print(exc.cleaned) # cleaned form that failed lookup
122
+
123
+ Trace / Explain
124
+ ===============
125
+
126
+ Set ``ENABLE_LICENCE_NORMALISER_TRACE=1`` or pass ``trace=True`` to get
127
+ resolution traces showing how the license was matched:
128
+
129
+ .. code-block:: python
130
+ :name: test_trace
131
+
132
+ from licence_normaliser import normalise_license
133
+
134
+ # Via function
135
+ v = normalise_license("cc by-nc-nd 3.0 igo", trace=True)
136
+ print(v.explain())
137
+
138
+ # Via class
139
+ from licence_normaliser import LicenseNormaliser
140
+ ln = LicenseNormaliser(trace=True)
141
+ v = ln.normalise_license("MIT")
142
+ print(v.explain())
143
+
144
+ Output shows the resolution pipeline (alias → registry → url → prose →
145
+ fallback) and which source file + line matched:
146
+
147
+ .. code-block:: text
148
+
149
+ Input: 'cc by-nc-nd 3.0 igo' → 'cc by-nc-nd 3.0 igo'
150
+ [✓] alias: 'cc by-nc-nd 3.0 igo' → 'cc-by-nc-nd-3.0-igo' (line 139 in aliases.json)
151
+
152
+ Result:
153
+ version_key: 'cc-by-nc-nd-3.0-igo'
154
+ name_key: 'cc-by-nc-nd'
155
+ family_key: 'cc'
156
+
157
+ The trace can also be accessed via ``v._trace`` for programmatic use.
158
+
159
+ Batch normalisation
160
+ ===================
161
+
162
+ .. code-block:: python
163
+ :name: test_batch_normalisation
164
+
165
+ from licence_normaliser import normalise_licenses
166
+
167
+ results = normalise_licenses(["MIT", "Apache-2.0", "CC BY 4.0"])
168
+ for r in results:
169
+ print(r.key)
170
+
171
+ # Strict batch - raises on first unresolvable
172
+ results = normalise_licenses(["MIT", "Apache-2.0"], strict=True)
173
+
174
+ Custom plugins
175
+ ==============
176
+
177
+ The ``LicenseNormaliser`` class lets you inject custom plugin classes for
178
+ specialised use cases:
179
+
180
+ .. code-block:: python
181
+ :name: test_custom_plugins
182
+
183
+ from licence_normaliser import LicenseNormaliser
184
+ from licence_normaliser.parsers.alias import AliasParser
185
+ from licence_normaliser.parsers.spdx import SPDXParser
186
+
187
+ # Use only SPDX + Alias plugins (no CC, no publisher URLs)
188
+ ln = LicenseNormaliser(
189
+ registry=[SPDXParser],
190
+ alias=[AliasParser],
191
+ family=[AliasParser],
192
+ name=[AliasParser],
193
+ cache=True,
194
+ cache_maxsize=8192,
195
+ )
196
+
197
+ # MIT resolves via SPDX parser
198
+ assert str(ln.normalise_license("MIT")) == "mit"
199
+
200
+ # CC BY resolves via Alias
201
+ assert str(ln.normalise_license("CC BY-NC-ND 4.0")) == "cc-by-nc-nd-4.0"
202
+
203
+ .. note::
204
+
205
+ Explicit plugin passing is optional — ``LicenseNormaliser()``
206
+ automatically loads defaults. Use the pattern above only if you need
207
+ custom plugins or reduce number of plugins loaded.
208
+
209
+ For caching, ``LicenseNormaliser`` wraps the resolution method
210
+ with ``lru_cache``.
211
+ Disable it by passing ``cache=False`` for debugging:
212
+
213
+ .. code-block:: python
214
+ :name: test_caching
215
+
216
+ from licence_normaliser import LicenseNormaliser
217
+
218
+ ln = LicenseNormaliser(cache=False)
219
+ result = ln.normalise_license("MIT")
220
+
221
+ Update data (CLI)
222
+ =================
223
+
224
+ .. code-block:: sh
225
+
226
+ licence-normaliser update-data --force
227
+ # Fetches fresh SPDX, OpenDefinition, OSI, CreativeCommons, and ScanCode JSONs
228
+
229
+ Integration tests (public API only)
230
+ ===================================
231
+
232
+ All integration tests live in
233
+ ``src/licence_normaliser/tests/test_integration.py``
234
+ and only import the public API.
235
+
236
+ CLI usage
237
+ =========
238
+
239
+ Normalise a single license:
240
+
241
+ .. code-block:: sh
242
+
243
+ licence-normaliser normalise "MIT"
244
+ # Output: mit
245
+
246
+ licence-normaliser normalise --full "CC BY 4.0"
247
+ # Output:
248
+ # Key: cc-by-4.0
249
+ # URL: https://creativecommons.org/licenses/by/4.0/
250
+ # License: cc-by
251
+ # Family: cc
252
+
253
+ licence-normaliser normalise --strict "totally-unknown"
254
+ # Exits with code 1 and prints an error
255
+
256
+ Batch normalise:
257
+
258
+ .. code-block:: sh
259
+
260
+ licence-normaliser batch MIT "Apache-2.0" "CC BY 4.0"
261
+ licence-normaliser batch --strict MIT "Apache-2.0"
262
+
263
+ Exceptions
264
+ ==========
265
+
266
+ .. code-block:: python
267
+ :name: test_exceptions
268
+
269
+ from licence_normaliser.exceptions import (
270
+ LicenseNormaliserError, # base class
271
+ LicenseNotFoundError, # raised by strict mode
272
+ )
273
+
274
+ Testing
275
+ =======
276
+
277
+ All tests run inside Docker:
278
+
279
+ .. code-block:: sh
280
+
281
+ make test
282
+
283
+ To test a specific Python version:
284
+
285
+ .. code-block:: sh
286
+
287
+ make test-env ENV=py312
288
+
289
+ License
290
+ =======
291
+
292
+ MIT
293
+
294
+ Author
295
+ ======
296
+
297
+ Artur Barseghyan <artur.barseghyan@gmail.com>