A comprehensive utility library to parse, compare, simplify and normalize license expressions using boolean logic
—
License symbols represent individual licenses and exceptions within license expressions. The library provides several symbol classes to handle different types of license identifiers, including standard licenses, license-like symbols, and complex WITH exception constructs.
The primary class for representing individual licenses within expressions.
class LicenseSymbol:
"""
Represents a license symbol with key, aliases, and metadata.
"""
def __init__(self, key: str, aliases: tuple = tuple(), is_deprecated: bool = False, is_exception: bool = False, *args, **kwargs):
"""
Create a license symbol.
Parameters:
- key: Primary identifier for the license
- aliases: Tuple of alternative names/identifiers (default: empty tuple)
- is_deprecated: Whether this license identifier is deprecated (default: False)
- is_exception: Whether this symbol represents a license exception (default: False)
- *args, **kwargs: Additional arguments for extensibility
"""
@property
def key(self) -> str:
"""Primary license identifier."""
@property
def aliases(self) -> tuple:
"""Tuple of alternative identifiers for this license."""
@property
def is_deprecated(self) -> bool:
"""True if this license identifier is deprecated."""
@property
def is_exception(self) -> bool:
"""True if this symbol represents a license exception."""
def __eq__(self, other) -> bool:
"""License symbols are equal if they have the same key."""
def __hash__(self) -> int:
"""Hash based on the license key."""
def __str__(self) -> str:
"""String representation using the key."""A symbol class for licenses that should match similar or related licenses.
class LicenseSymbolLike(LicenseSymbol):
"""
License symbol that matches similar licenses with flexible matching.
Extends LicenseSymbol with similarity-based matching capabilities.
"""
def __init__(self, key: str, aliases: list = None, is_exception: bool = False):
"""
Create a license-like symbol with flexible matching.
Parameters:
- key: Primary identifier for the license
- aliases: List of alternative names/identifiers
- is_exception: Whether this symbol represents a license exception
"""Represents complex license WITH exception constructs.
class LicenseWithExceptionSymbol:
"""
Represents a license WITH exception construct (e.g., 'GPL-2.0 WITH Classpath-exception-2.0').
"""
def __init__(self, license_symbol: LicenseSymbol, exception_symbol: LicenseSymbol, strict: bool = False):
"""
Create a license WITH exception symbol.
Parameters:
- license_symbol: The main license symbol
- exception_symbol: The exception symbol (must have is_exception=True)
- strict: Validate that license_symbol is not an exception and exception_symbol is an exception (default: False)
"""
@property
def license_symbol(self) -> LicenseSymbol:
"""The main license symbol."""
@property
def exception_symbol(self) -> LicenseSymbol:
"""The exception symbol."""
@property
def key(self) -> str:
"""Combined key representing the license WITH exception."""
def __eq__(self, other) -> bool:
"""Equality based on both license and exception symbols."""
def __str__(self) -> str:
"""String representation as 'license WITH exception'."""Abstract base class for all license symbols.
class BaseSymbol:
"""
Base class for all license symbols providing common functionality.
"""
def render(self, template: str = None) -> str:
"""
Render the symbol using a template string.
Parameters:
- template: Format template (default: '{symbol.key}')
Returns:
Formatted string representation
"""
def decompose(self):
"""
Yield the underlying symbols of this symbol.
For most symbols, this yields the symbol itself.
"""
def __contains__(self, other) -> bool:
"""
Test if the other symbol is contained in this symbol.
"""Base interface class for renderable objects.
class Renderable:
"""
Interface for renderable objects that can be formatted as strings.
"""
def render(self, template: str = "{symbol.key}", *args, **kwargs) -> str:
"""
Return a formatted string rendering for this object.
Parameters:
- template: Format string template
- *args, **kwargs: Additional arguments for rendering
Returns:
Formatted string representation
"""
def render_as_readable(self, template: str = "{symbol.key}", *args, **kwargs) -> str:
"""
Return a formatted string with extra parentheses for improved readability.
Parameters:
- template: Format string template
- *args, **kwargs: Additional arguments for rendering
Returns:
Formatted string with improved readability
"""Base class for renderable boolean functions (AND/OR operators).
class RenderableFunction(Renderable):
"""
Base class for renderable boolean functions like AND and OR operators.
"""
def render(self, template: str = "{symbol.key}", *args, **kwargs) -> str:
"""
Render a boolean expression recursively applying the template to symbols.
Parameters:
- template: Format string template for symbols
- *args, **kwargs: Additional arguments passed to symbol render methods
Returns:
Formatted boolean expression string
"""from license_expression import LicenseSymbol
# Create a standard license symbol
mit = LicenseSymbol('MIT', ['MIT License', 'Expat'])
print(mit.key) # 'MIT'
print(mit.aliases) # ('MIT License', 'Expat')
print(mit.is_exception) # False
# Create an exception symbol
classpath = LicenseSymbol('Classpath-exception-2.0', is_exception=True)
print(classpath.is_exception) # Truefrom license_expression import LicenseSymbolLike
# Create a symbol that matches similar licenses
generic_gpl = LicenseSymbolLike('GPL', ['GNU GPL', 'General Public License'])from license_expression import LicenseSymbol, LicenseWithExceptionSymbol
# Create license and exception symbols
gpl = LicenseSymbol('GPL-2.0')
classpath = LicenseSymbol('Classpath-exception-2.0', is_exception=True)
# Combine into WITH exception symbol
gpl_with_classpath = LicenseWithExceptionSymbol(gpl, classpath)
print(str(gpl_with_classpath)) # 'GPL-2.0 WITH Classpath-exception-2.0'
print(gpl_with_classpath.license_symbol.key) # 'GPL-2.0'
print(gpl_with_classpath.exception_symbol.key) # 'Classpath-exception-2.0'# Symbol equality is based on keys
mit1 = LicenseSymbol('MIT', ['MIT License'])
mit2 = LicenseSymbol('MIT', ['Expat License'])
print(mit1 == mit2) # True - same key
# Different keys are not equal
mit = LicenseSymbol('MIT')
apache = LicenseSymbol('Apache-2.0')
print(mit == apache) # False# Symbols can be used in sets (hashable by key)
symbols = {
LicenseSymbol('MIT'),
LicenseSymbol('Apache-2.0'),
LicenseSymbol('MIT', ['Expat']) # Duplicate key, won't be added twice
}
print(len(symbols)) # 2
# Use as dictionary keys
license_info = {
LicenseSymbol('MIT'): 'Permissive license',
LicenseSymbol('GPL-2.0'): 'Copyleft license'
}# Render symbols with custom templates
mit = LicenseSymbol('MIT', ['MIT License'])
print(mit.render()) # 'MIT' (default)
print(mit.render('{symbol.key}')) # 'MIT'
print(mit.render('License: {symbol.key}')) # 'License: MIT'The library provides utilities for validating collections of symbols:
from license_expression import validate_symbols, LicenseSymbol
symbols = [
LicenseSymbol('MIT'),
LicenseSymbol('Apache-2.0'),
LicenseSymbol('GPL-2.0', is_exception=True), # Invalid: GPL-2.0 is not an exception
]
warnings, errors = validate_symbols(symbols, validate_keys=True)
print(errors) # List of validation errorsInstall with Tessl CLI
npx tessl i tessl/pypi-license-expression