license-normaliser
Comprehensive license normalsation with a three-level hierarchy.
license-normaliser is a comprehensive license normalisation library that maps any license representation (SPDX tokens, URLs, prose descriptions) to a canonical three-level hierarchy.
Features
Three-level hierarchy - LicenseFamily → LicenseName → LicenseVersion.
Wide format support - SPDX tokens, URLs, prose descriptions.
Creative Commons support - Full CC family with versions and IGO variants.
Publisher-specific licenses - Springer, Nature, Elsevier, Wiley, ACS, and more.
File-driven data - Add aliases, URLs, and patterns by editing JSON files. No Python code changes required for new synonyms.
Pluggable data sources - Drop in a new DataSource class to ingest any external license registry automatically.
Strict mode - Raise LicenseNotFoundError instead of silently returning "unknown".
Caching - LRU caching for performance.
CLI - Command-line interface with --strict support.
Hierarchy
The library uses a three-level hierarchy:
LicenseFamily - broad bucket: "cc", "osi", "copyleft", "publisher-tdm", …
LicenseName - version-free: "cc-by", "cc-by-nc-nd", "mit", "wiley-tdm"
LicenseVersion - fully resolved: "cc-by-3.0", "cc-by-nc-nd-4.0"
Installation
With uv:
uv pip install license-normaliser
Or with pip:
pip install license-normaliser
Quick start
from license_normaliser import normalise_license
v = normalise_license("CC BY-NC-ND 4.0")
str(v) # "cc-by-nc-nd-4.0" ← LicenseVersion
str(v.license) # "cc-by-nc-nd" ← LicenseName
str(v.license.family) # "cc" ← LicenseFamily
Strict mode
By default, unresolvable inputs return an "unknown" result. Pass strict=True to raise LicenseNotFoundError instead:
from license_normaliser import normalise_license
from license_normaliser.exceptions import LicenseNotFoundError
# Silent fallback (default)
v = normalise_license("some-unknown-string")
v.family.key # "unknown"
# Strict: raises on unresolvable input
try:
v = normalise_license("some-unknown-string", strict=True)
except LicenseNotFoundError as exc:
print(exc.raw) # original input
print(exc.cleaned) # cleaned form that failed lookup
Trace / Explain
Set ENABLE_LICENSE_NORMALISER_TRACE=1 or pass trace=True to get resolution traces showing how the license was matched:
from license_normaliser import normalise_license
# Via function
v = normalise_license("cc by-nc-nd 3.0 igo", trace=True)
print(v.explain())
# Via class
from license_normaliser import LicenseNormaliser
ln = LicenseNormaliser(trace=True)
v = ln.normalise_license("MIT")
print(v.explain())
Output shows the resolution pipeline (alias → registry → url → prose → fallback) and which source file + line matched:
Input: 'cc by-nc-nd 3.0 igo' → 'cc by-nc-nd 3.0 igo'
[✓] alias: 'cc by-nc-nd 3.0 igo' → 'cc-by-nc-nd-3.0-igo' (line 139 in aliases.json)
Result:
version_key: 'cc-by-nc-nd-3.0-igo'
name_key: 'cc-by-nc-nd'
family_key: 'cc'
The trace can also be accessed via v._trace for programmatic use.
Batch normalisation
from license_normaliser import normalise_licenses
results = normalise_licenses(["MIT", "Apache-2.0", "CC BY 4.0"])
for r in results:
print(r.key)
# Strict batch - raises on first unresolvable
results = normalise_licenses(["MIT", "Apache-2.0"], strict=True)
Custom plugins
The LicenseNormaliser class lets you inject custom plugin classes for specialised use cases:
from license_normaliser import LicenseNormaliser
from license_normaliser.parsers.alias import AliasParser
from license_normaliser.parsers.spdx import SPDXParser
# Use only SPDX + Alias plugins (no CC, no publisher URLs)
ln = LicenseNormaliser(
registry=[SPDXParser],
alias=[AliasParser],
family=[AliasParser],
name=[AliasParser],
cache=True,
cache_maxsize=8192,
)
# MIT resolves via SPDX parser
assert str(ln.normalise_license("MIT")) == "mit"
# CC BY resolves via Alias
assert str(ln.normalise_license("CC BY-NC-ND 4.0")) == "cc-by-nc-nd-4.0"
For caching, LicenseNormaliser wraps the resolution method with lru_cache. Disable it by passing cache=False for debugging:
from license_normaliser import LicenseNormaliser
ln = LicenseNormaliser(cache=False)
result = ln.normalise_license("MIT")
Update data sources (CLI)
license-normaliser update-data --force
# Fetches fresh SPDX + OpenDefinition JSONs into src/license_normaliser/data/
Integration tests (public API only)
All integration tests live in src/license_normaliser/tests/test_integration.py and only import the public API.
CLI usage
Normalise a single license:
license-normaliser normalise "MIT"
# Output: mit
license-normaliser normalise --full "CC BY 4.0"
# Output:
# Key: cc-by-4.0
# URL: https://creativecommons.org/licenses/by/4.0/
# License: cc-by
# Family: cc
license-normaliser normalise --strict "totally-unknown"
# Exits with code 1 and prints an error
Batch normalise:
license-normaliser batch MIT "Apache-2.0" "CC BY 4.0"
license-normaliser batch --strict MIT "Apache-2.0"
Exceptions
from license_normaliser.exceptions import (
LicenseNormaliserError, # base class
LicenseNotFoundError, # raised by strict mode
)
Testing
All tests run inside Docker:
make test
To test a specific Python version:
make test-env ENV=py312
License
MIT
Metadata
Release files for license-normaliser 0.3.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| license_normaliser-0.3.2.tar.gz | 174.2 kB | Without provenance | Details for license_normaliser-0.3.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| license_normaliser-0.3.2-py3-none-any.whl | Python 3 | none | any | Details for license_normaliser-0.3.2-py3-none-any.whl |
Total release size: 360.8 kB
Release files / license_normaliser-0.3.2.tar.gz
| Download URL | license_normaliser-0.3.2.tar.gz |
|---|---|
| Size | 174.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
60441fc1df23127e3bee3a5cb7d560a04fc7931bf63193ed2b8062dbf60ec27d
|
|
BLAKE2b-256 checksum How to use checksums |
be4b52789a44f29292479532f117114150844e578b2d974bb676345a939fcaf8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.11
|
Release files / license_normaliser-0.3.2-py3-none-any.whl
| Download URL | license_normaliser-0.3.2-py3-none-any.whl |
|---|---|
| Size | 186.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b6feb358ca7fe1f42744095de3814c52d87e8c344f5678a9e48f990ebc987169
|
|
BLAKE2b-256 checksum How to use checksums |
e52ee934e335a3630c5d3f1c054606292b70e257ec2a3931645b81b9f264d7a4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.11
|
Security for license-normaliser 0.3.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.