CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-license-expression

A comprehensive utility library to parse, compare, simplify and normalize license expressions using boolean logic

Pending
Overview
Eval results
Files

expressions.mddocs/

Expression Utilities

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.

Capabilities

Expression Combination

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
    """

Symbol Validation

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
    """

Symbol Utility Functions

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
    """

Usage Examples

Combining Expressions

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'

Working with License Expression Objects

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'

Custom Licensing Instance

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 Handling

# 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)

Symbol Validation

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)

Symbol Conversion and Processing

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']

Advanced Token Processing

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.

Error Handling

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

docs

constants.md

expressions.md

factories.md

index.md

licensing.md

symbols.md

tile.json