A comprehensive utility library to parse, compare, simplify and normalize license expressions using boolean logic
—
Utility functions for manipulating, combining, and validating license expressions. These functions provide additional capabilities beyond the core Licensing class, including expression combination, symbol validation, and specialized processing functions.
Combine multiple license expressions into a single expression using boolean logic.
def combine_expressions(
expressions,
relation: str = "AND",
unique: bool = True,
licensing = None
) -> LicenseExpression:
"""
Combine multiple license expressions with the specified boolean relation.
Parameters:
- expressions: List or tuple of license expression strings or LicenseExpression objects
- relation: Boolean relation to use ("AND" or "OR")
- unique: Remove duplicate expressions before combining
- licensing: Licensing instance to use for parsing (default: creates new instance)
Returns:
Combined LicenseExpression object, or None if expressions is empty
Raises:
- TypeError: If expressions is not a list/tuple or relation is invalid
"""Validate collections of license symbols for correctness and consistency.
def validate_symbols(symbols, validate_keys: bool = False) -> tuple:
"""
Validate a collection of license symbols.
Parameters:
- symbols: Iterable of LicenseSymbol objects to validate
- validate_keys: Whether to validate that keys are proper license identifiers
Returns:
Tuple of (warnings, errors) where each is a list of validation messages
"""Convert and process license symbols.
def as_symbols(symbols) -> list:
"""
Convert symbol strings/objects to LicenseSymbol instances.
Parameters:
- symbols: Iterable of strings or LicenseSymbol objects
Returns:
List of LicenseSymbol objects
"""
def ordered_unique(seq) -> list:
"""
Return unique items from sequence while preserving order.
Parameters:
- seq: Input sequence to deduplicate
Returns:
List with unique items in original order
"""from license_expression import combine_expressions, get_spdx_licensing
# Combine with default AND relation
expressions = ['MIT', 'Apache-2.0', 'BSD-3-Clause']
combined = combine_expressions(expressions)
print(str(combined)) # 'MIT AND Apache-2.0 AND BSD-3-Clause'
# Combine with OR relation
combined_or = combine_expressions(expressions, relation='OR')
print(str(combined_or)) # 'MIT OR Apache-2.0 OR BSD-3-Clause'
# Remove duplicates (default behavior)
duplicate_expressions = ['MIT', 'Apache-2.0', 'MIT', 'BSD-3-Clause']
combined_unique = combine_expressions(duplicate_expressions)
print(str(combined_unique)) # 'MIT AND Apache-2.0 AND BSD-3-Clause'
# Keep duplicates
combined_with_dupes = combine_expressions(duplicate_expressions, unique=False)
print(str(combined_with_dupes)) # 'MIT AND Apache-2.0 AND MIT AND BSD-3-Clause'from license_expression import combine_expressions, get_spdx_licensing
licensing = get_spdx_licensing()
# Combine parsed expressions
expr1 = licensing.parse('MIT OR Apache-2.0')
expr2 = licensing.parse('GPL-2.0')
expr3 = licensing.parse('BSD-3-Clause')
combined = combine_expressions([expr1, expr2, expr3], relation='OR')
print(str(combined)) # '(MIT OR Apache-2.0) OR GPL-2.0 OR BSD-3-Clause'from license_expression import combine_expressions, Licensing, LicenseSymbol
# Create custom licensing with specific symbols
custom_symbols = [
LicenseSymbol('CustomLicense1'),
LicenseSymbol('CustomLicense2'),
]
custom_licensing = Licensing(custom_symbols)
# Use custom licensing for combination
expressions = ['CustomLicense1', 'CustomLicense2']
combined = combine_expressions(expressions, licensing=custom_licensing)
print(str(combined)) # 'CustomLicense1 AND CustomLicense2'# Single expression returns the expression itself
single = combine_expressions(['MIT'])
print(str(single)) # 'MIT'
# Empty expressions return None
empty = combine_expressions([])
print(empty) # None
# None input returns None
none_result = combine_expressions(None)
print(none_result) # None (but raises TypeError)from license_expression import validate_symbols, LicenseSymbol
# Create test symbols with various issues
symbols = [
LicenseSymbol('MIT'),
LicenseSymbol('Apache-2.0'),
LicenseSymbol('MIT'), # Duplicate key
LicenseSymbol('CustomLicense', ['MIT']), # Alias conflicts with existing key
'InvalidSymbol', # Not a LicenseSymbol object
LicenseSymbol('GPL-2.0', is_exception=True), # Invalid exception
]
warnings, errors = validate_symbols(symbols)
print("Warnings:", warnings)
print("Errors:", errors)
# Validate with key checking
warnings, errors = validate_symbols(symbols, validate_keys=True)
print("Key validation errors:", errors)from license_expression import as_symbols, ordered_unique
# Convert mixed symbol types to LicenseSymbol objects
mixed_symbols = ['MIT', LicenseSymbol('Apache-2.0'), 'GPL-2.0']
symbol_objects = as_symbols(mixed_symbols)
for symbol in symbol_objects:
print(type(symbol), symbol.key) # All are LicenseSymbol instances
# Remove duplicates while preserving order
duplicate_list = ['MIT', 'Apache-2.0', 'MIT', 'GPL-2.0', 'Apache-2.0']
unique_list = ordered_unique(duplicate_list)
print(unique_list) # ['MIT', 'Apache-2.0', 'GPL-2.0']The library includes internal functions for advanced expression parsing and token processing:
def build_symbols_from_unknown_tokens(tokens) -> dict:
"""Build license symbols from unknown tokens during parsing."""
def build_token_groups_for_with_subexpression(tokens) -> list:
"""Build token groups for WITH subexpression processing."""
def is_with_subexpression(tokens_tripple) -> bool:
"""Check if a token triplet represents a WITH subexpression."""
def replace_with_subexpression_by_license_symbol(tokens, strict: bool = False) -> list:
"""Replace WITH subexpressions with license symbols in token stream."""Note: These functions are primarily for internal use and advanced customization scenarios.
Expression utility functions include comprehensive error handling:
from license_expression import combine_expressions
# Invalid relation parameter
try:
combine_expressions(['MIT', 'Apache-2.0'], relation='INVALID')
except TypeError as e:
print(f"Error: {e}")
# Invalid expressions parameter type
try:
combine_expressions('MIT') # String instead of list
except TypeError as e:
print(f"Error: {e}")
# Invalid expressions in list
try:
combine_expressions([123, 'MIT']) # Number in list
except Exception as e:
print(f"Error: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-license-expression