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

constants.mddocs/

Constants and Error Handling

The license-expression library provides comprehensive error handling through exception classes and error constants. It also exposes token constants for advanced parsing and processing scenarios.

Capabilities

Exception Classes

Custom exception classes for handling license expression errors.

class ExpressionError(Exception):
    """
    Base exception class for license expression errors.
    Raised when general expression processing fails.
    """
    pass

class ParseError(Exception):
    """
    Base parsing error class imported from boolean.py.
    Contains information about parsing failures.
    """
    
    @property
    def token_type(self) -> int:
        """Type of token that caused the error."""
    
    @property
    def token_string(self) -> str:
        """String representation of the problematic token."""
    
    @property
    def position(self) -> int:
        """Position in the input string where the error occurred."""
    
    @property
    def error_code(self) -> int:
        """Numeric error code identifying the specific parsing error."""

class ExpressionParseError(ParseError, ExpressionError):
    """
    Exception raised during license expression parsing.
    Inherits from both ParseError (from boolean.py) and ExpressionError.
    Contains detailed information about parsing failures.
    """
    
    @property
    def token_type(self) -> int:
        """Type of token that caused the error."""
    
    @property
    def token_string(self) -> str:
        """String representation of the problematic token."""
    
    @property
    def position(self) -> int:
        """Position in the input string where the error occurred."""
    
    @property
    def error_code(self) -> int:
        """Numeric error code identifying the specific parsing error."""

Expression Validation Info

Data structure containing validation results.

class ExpressionInfo:
    """
    Contains detailed information about expression validation results.
    """
    
    original_expression: str
    """The original expression string that was validated."""
    
    normalized_expression: str
    """Normalized version of the expression, or None if validation failed."""
    
    errors: list
    """List of error messages describing validation failures."""
    
    invalid_symbols: list
    """List of unrecognized license symbols found in the expression."""

Parse Error Constants

Error codes specific to license expression parsing.

# License expression specific error codes
PARSE_EXPRESSION_NOT_UNICODE: int = 100
"""Expression string must be a string."""

PARSE_INVALID_EXCEPTION: int = 101
"""A license exception symbol can only be used as an exception in a 'WITH exception' statement."""

PARSE_INVALID_SYMBOL_AS_EXCEPTION: int = 102
"""A plain license symbol cannot be used as an exception in a 'WITH symbol' statement."""

PARSE_INVALID_SYMBOL: int = 103
"""A proper license symbol is needed."""

Boolean Parse Error Constants

Error codes inherited from the boolean.py library.

# Imported from boolean.py
PARSE_ERRORS: dict
"""Dictionary mapping error codes to error messages."""

PARSE_INVALID_EXPRESSION: int
"""Invalid expression structure."""

PARSE_INVALID_NESTING: int
"""Invalid parentheses nesting."""

PARSE_INVALID_OPERATOR_SEQUENCE: int
"""Invalid sequence of boolean operators."""

PARSE_INVALID_SYMBOL_SEQUENCE: int
"""Invalid sequence of symbols."""

PARSE_UNBALANCED_CLOSING_PARENS: int
"""Unbalanced closing parentheses."""

PARSE_UNKNOWN_TOKEN: int
"""Unknown token encountered during parsing."""

Token Constants

Constants representing different types of tokens in license expressions.

# Token type constants (imported from boolean.py)
TOKEN_SYMBOL: int
"""Token representing a license symbol."""

TOKEN_AND: int
"""Token representing the AND boolean operator."""

TOKEN_OR: int
"""Token representing the OR boolean operator."""

TOKEN_LPAR: int
"""Token representing a left parenthesis '('."""

TOKEN_RPAR: int
"""Token representing a right parenthesis ')'."""

TOKEN_WITH: int = 10
"""Token representing the WITH exception operator (license-expression specific)."""

Keyword Constants

Predefined keyword objects and collections for license expression parsing.

# Keyword objects for expression parsing
KW_LPAR: Keyword
"""Keyword object for left parenthesis '('."""

KW_RPAR: Keyword
"""Keyword object for right parenthesis ')'."""

KW_AND: Keyword
"""Keyword object for AND boolean operator."""

KW_OR: Keyword
"""Keyword object for OR boolean operator."""

KW_WITH: Keyword
"""Keyword object for WITH exception operator."""

# Keyword collections
KEYWORDS: tuple
"""Tuple containing all keyword objects (KW_LPAR, KW_RPAR, KW_AND, KW_OR, KW_WITH)."""

KEYWORDS_STRINGS: set
"""Set of keyword string values for quick lookup."""

OPERATORS: dict
"""Dictionary mapping operator strings to keyword objects."""

Helper Classes

Named tuple and helper classes for token processing.

class Keyword:
    """
    Named tuple representing a keyword token.
    """
    value: str
    """The string value of the keyword."""
    
    type: int
    """The token type of the keyword."""
    
    def __len__(self) -> int:
        """Return length of the keyword value."""

Usage Examples

Exception Handling

from license_expression import (
    get_spdx_licensing, 
    ExpressionError, 
    ExpressionParseError
)

licensing = get_spdx_licensing()

# Handle general expression errors
try:
    result = licensing.validate('unknown-license')
    if result.errors:
        print("Validation errors:", result.errors)
except ExpressionError as e:
    print(f"Expression error: {e}")

# Handle parsing errors with detailed information
try:
    licensing.parse('MIT and', validate=True, strict=True)
except ExpressionParseError as e:
    print(f"Parse error at position {e.position}: {e}")
    print(f"Error code: {e.error_code}")
    print(f"Problematic token: '{e.token_string}' (type: {e.token_type})")

Working with Error Codes

from license_expression import (
    PARSE_INVALID_EXCEPTION,
    PARSE_INVALID_SYMBOL_AS_EXCEPTION,
    PARSE_ERRORS,
    get_spdx_licensing
)

licensing = get_spdx_licensing()

# Test different error conditions
try:
    # Using a license as an exception (should fail)
    licensing.parse('MIT WITH Apache-2.0', validate=True, strict=True)
except ExpressionParseError as e:
    if e.error_code == PARSE_INVALID_SYMBOL_AS_EXCEPTION:
        print("Cannot use regular license as exception")
    print(f"Error message: {PARSE_ERRORS[e.error_code]}")

try:
    # Using an exception without WITH (should fail)
    licensing.parse('Classpath-exception-2.0', validate=True, strict=True)
except ExpressionParseError as e:
    if e.error_code == PARSE_INVALID_EXCEPTION:
        print("Exception symbols must be used with WITH")

Expression Validation Results

from license_expression import get_spdx_licensing

licensing = get_spdx_licensing()

# Validate expressions and examine results
expressions = [
    'MIT and Apache-2.0',           # Valid
    'UnknownLicense or MIT',        # Has unknown symbol
    'MIT WITH ClasspathException',  # Invalid exception usage
]

for expr in expressions:
    result = licensing.validate(expr)
    print(f"\nExpression: {result.original_expression}")
    print(f"Normalized: {result.normalized_expression}")
    print(f"Errors: {result.errors}")
    print(f"Invalid symbols: {result.invalid_symbols}")

Token Type Checking

from license_expression import (
    TOKEN_SYMBOL, TOKEN_AND, TOKEN_OR, 
    TOKEN_LPAR, TOKEN_RPAR, TOKEN_WITH
)

# These constants can be used for custom token processing
def analyze_token_type(token_type):
    token_names = {
        TOKEN_SYMBOL: "License Symbol",
        TOKEN_AND: "AND Operator", 
        TOKEN_OR: "OR Operator",
        TOKEN_LPAR: "Left Parenthesis",
        TOKEN_RPAR: "Right Parenthesis",
        TOKEN_WITH: "WITH Operator"
    }
    return token_names.get(token_type, f"Unknown token type: {token_type}")

# Example usage (for advanced scenarios)
print(analyze_token_type(TOKEN_SYMBOL))  # "License Symbol"
print(analyze_token_type(TOKEN_AND))     # "AND Operator"

Custom Error Handling

from license_expression import (
    get_spdx_licensing,
    ExpressionError,
    ExpressionParseError,
    PARSE_ERRORS
)

def safe_parse_expression(licensing, expression):
    """
    Safely parse an expression with comprehensive error handling.
    """
    try:
        return licensing.parse(expression, validate=True, strict=True)
    except ExpressionParseError as e:
        error_msg = PARSE_ERRORS.get(e.error_code, "Unknown parse error")
        return {
            'error': 'parse_error',
            'message': error_msg,
            'position': e.position,
            'token': e.token_string
        }
    except ExpressionError as e:
        return {
            'error': 'expression_error', 
            'message': str(e)
        }
    except Exception as e:
        return {
            'error': 'unexpected_error',
            'message': str(e)
        }

# Use the safe parser
licensing = get_spdx_licensing()
result = safe_parse_expression(licensing, 'MIT WITH Apache-2.0')
print(result)

Error Code Lookup

from license_expression import PARSE_ERRORS

# Display all available error codes and messages
print("Available parse error codes:")
for code, message in PARSE_ERRORS.items():
    print(f"  {code}: {message}")

Working with Keywords

from license_expression import Keyword

# Create keyword tokens (used internally by the parser)
and_keyword = Keyword('AND', TOKEN_AND)
print(f"Keyword: {and_keyword.value}, Type: {and_keyword.type}")
print(f"Length: {len(and_keyword)}")  # Length of 'AND' = 3

Error Message Customization

For applications that need to provide user-friendly error messages:

from license_expression import (
    get_spdx_licensing,
    ExpressionParseError,
    PARSE_INVALID_SYMBOL_AS_EXCEPTION,
    PARSE_INVALID_EXCEPTION
)

def user_friendly_error(expression, error):
    """Convert technical error to user-friendly message."""
    if isinstance(error, ExpressionParseError):
        if error.error_code == PARSE_INVALID_SYMBOL_AS_EXCEPTION:
            return f"'{error.token_string}' is a license, not an exception. Use it without 'WITH'."
        elif error.error_code == PARSE_INVALID_EXCEPTION:
            return f"'{error.token_string}' is an exception and must be used with 'WITH'."
    
    return f"Error parsing expression: {error}"

# Example usage
licensing = get_spdx_licensing()
try:
    licensing.parse('MIT WITH Apache-2.0', validate=True, strict=True)
except ExpressionParseError as e:
    friendly_msg = user_friendly_error('MIT WITH Apache-2.0', e)
    print(friendly_msg)

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