CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-reuse

A tool for compliance with the REUSE recommendations for software licensing and copyright management.

Pending
Overview
Eval results
Files

global-licensing.mddocs/

Global Licensing Configuration

REUSE supports project-wide licensing configuration through global licensing files. This system allows you to specify licensing and copyright information for multiple files using configuration files rather than individual file headers.

Capabilities

Abstract Base Class

Foundation for all global licensing implementations.

class GlobalLicensing(ABC):
    """
    Abstract base class for global licensing configurations.
    
    Provides the interface for different global licensing file formats
    including .reuse/dep5 and REUSE.toml files.
    """
    
    source: str  # Source file path
    
    @classmethod
    @abstractmethod
    def from_file(cls, path: StrPath, **kwargs: Any) -> "GlobalLicensing":
        """Parse the file and create a GlobalLicensing object from its contents."""
    
    @abstractmethod
    def reuse_info_of(self, path: StrPath) -> dict[PrecedenceType, list[ReuseInfo]]:
        """
        Get REUSE information for a file based on global configuration.
        
        Args:
            path: File path to get information for (accepts str or Path-like)
            
        Returns:
            Dictionary mapping precedence types to lists of ReuseInfo objects
        """

Precedence System

Enumeration defining precedence levels for global licensing information.

class PrecedenceType(Enum):
    """
    Precedence levels for global licensing.
    
    Defines the order in which licensing information sources are applied,
    with higher precedence values taking priority over lower ones.
    """
    AGGREGATE = "aggregate"      # Aggregate the results from the file with the results from the global licensing file
    CLOSEST = "closest"          # Use the results that are closest to the covered file  
    OVERRIDE = "override"        # Only use the results from the global licensing file

DEP5 Format Support

Implementation for .reuse/dep5 files following the Debian DEP5 specification.

class ReuseDep5(GlobalLicensing):
    """
    Implementation for .reuse/dep5 files.
    
    Supports the Debian DEP5 (Machine-readable debian/copyright) format
    for specifying copyright and licensing information for multiple files
    using glob patterns.
    """
    
    def __init__(self, path: Path):
        """
        Initialize DEP5 global licensing.
        
        Args:
            path: Path to the .reuse/dep5 file
            
        Raises:
            GlobalLicensingParseError: If file cannot be parsed
            FileNotFoundError: If dep5 file doesn't exist
        """
    
    def reuse_info_of(self, path: Path) -> list[ReuseInfo]:
        """
        Get REUSE info based on DEP5 configuration.
        
        Args:
            path: File path to check against DEP5 patterns
            
        Returns:
            List of ReuseInfo matching the file path
        """

Usage Examples:

from reuse.global_licensing import ReuseDep5
from pathlib import Path

# Create DEP5 instance
try:
    dep5_file = Path(".reuse/dep5")
    dep5 = ReuseDep5(dep5_file)
    
    # Get licensing info for specific files
    source_file = Path("src/example.py")
    reuse_info = dep5.reuse_info_of(source_file)
    
    for info in reuse_info:
        print(f"Licenses: {info.spdx_expressions}")
        print(f"Copyright: {info.copyright_lines}")
        print(f"Source: {info.source_type}")
        
except FileNotFoundError:
    print("No .reuse/dep5 file found")
except GlobalLicensingParseError as e:
    print(f"Error parsing DEP5 file: {e}")

REUSE.toml Format Support

Implementation for REUSE.toml files using the modern TOML-based configuration format.

class ReuseTOML(GlobalLicensing):
    """
    Implementation for REUSE.toml files.
    
    Supports the modern REUSE.toml format for specifying copyright
    and licensing information using TOML syntax and glob patterns.
    """
    
    def __init__(self, path: Path):
        """
        Initialize REUSE.toml global licensing.
        
        Args:
            path: Path to the REUSE.toml file
            
        Raises:
            GlobalLicensingParseError: If TOML file cannot be parsed
            FileNotFoundError: If REUSE.toml file doesn't exist
        """
    
    def reuse_info_of(self, path: Path) -> list[ReuseInfo]:
        """
        Get REUSE info based on REUSE.toml configuration.
        
        Args:
            path: File path to check against TOML patterns
            
        Returns:
            List of ReuseInfo matching the file path
        """

Usage Examples:

from reuse.global_licensing import ReuseTOML
from pathlib import Path

# Create REUSE.toml instance
try:
    toml_file = Path("REUSE.toml")
    reuse_toml = ReuseTOML(toml_file)
    
    # Get licensing info for files
    test_files = [
        Path("src/main.py"),
        Path("tests/test_example.py"),
        Path("docs/api.md")
    ]
    
    for file_path in test_files:
        reuse_info = reuse_toml.reuse_info_of(file_path)
        if reuse_info:
            print(f"\n{file_path}:")
            for info in reuse_info:
                print(f"  Licenses: {list(info.spdx_expressions)}")
                print(f"  Copyright: {list(info.copyright_lines)}")
        else:
            print(f"{file_path}: No global licensing info")
            
except FileNotFoundError:
    print("No REUSE.toml file found")
except GlobalLicensingParseError as e:
    print(f"Error parsing REUSE.toml: {e}")

Nested REUSE.toml Support

Implementation for nested REUSE.toml configurations in subdirectories.

class NestedReuseTOML(GlobalLicensing):
    """
    Implementation for nested REUSE.toml configurations.
    
    Supports hierarchical REUSE.toml files in subdirectories,
    allowing different parts of a project to have different
    global licensing configurations.
    """
    
    def __init__(self, paths: list[Path]):
        """
        Initialize nested REUSE.toml licensing.
        
        Args:
            paths: List of paths to REUSE.toml files in hierarchical order
            
        Raises:
            GlobalLicensingParseError: If any TOML file cannot be parsed
        """
    
    def reuse_info_of(self, path: Path) -> list[ReuseInfo]:
        """
        Get REUSE info from nested TOML configurations.
        
        Args:
            path: File path to check against nested configurations
            
        Returns:
            List of ReuseInfo from applicable nested configurations
        """

Annotation Items

Data structure for individual annotation entries in global licensing files.

class AnnotationsItem:
    """
    Individual annotation item in global licensing.
    
    Represents a single annotation entry that maps file patterns
    to copyright and licensing information.
    """
    
    def __init__(
        self,
        path_patterns: list[str],
        copyright_lines: set[str],
        spdx_expressions: set[str],
        contributor_lines: set[str] = None
    ):
        """
        Initialize annotation item.
        
        Args:
            path_patterns: List of glob patterns for matching files
            copyright_lines: Set of copyright statements
            spdx_expressions: Set of SPDX license identifiers
            contributor_lines: Optional set of contributor information
        """
    
    def matches_path(self, path: Path) -> bool:
        """
        Check if path matches any of the patterns.
        
        Args:
            path: File path to check
            
        Returns:
            True if path matches any pattern in this annotation
        """

Global Licensing File Formats

DEP5 Format Example

The .reuse/dep5 file uses the Debian DEP5 format:

Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: example-project
Upstream-Contact: Jane Doe <jane@example.com>
Source: https://github.com/example/project

Files: src/*.py
Copyright: 2023 Jane Doe <jane@example.com>
License: MIT

Files: tests/*
Copyright: 2023 Jane Doe <jane@example.com>
License: MIT

Files: docs/*.md
Copyright: 2023 Jane Doe <jane@example.com>
License: CC-BY-4.0

REUSE.toml Format Example

The REUSE.toml file uses TOML syntax:

version = 1
SPDX-PackageName = "example-project"
SPDX-PackageSupplier = "Jane Doe <jane@example.com>"
SPDX-PackageDownloadLocation = "https://github.com/example/project"

[[annotations]]
path = ["src/*.py", "src/**/*.py"]
precedence = "aggregate"
SPDX-FileCopyrightText = [
    "2023 Jane Doe <jane@example.com>",
    "2023 Example Corp"
]
SPDX-License-Identifier = ["MIT", "Apache-2.0"]

[[annotations]]
path = "tests/**"
precedence = "aggregate"  
SPDX-FileCopyrightText = "2023 Jane Doe <jane@example.com>"
SPDX-License-Identifier = "MIT"

[[annotations]]
path = "docs/*.md"
precedence = "aggregate"
SPDX-FileCopyrightText = "2023 Jane Doe <jane@example.com>"
SPDX-License-Identifier = "CC-BY-4.0"

Exception Handling

Global licensing operations can raise specific exceptions for error handling.

class GlobalLicensingParseError(ReuseError):
    """Error parsing GlobalLicensing file."""
    
    def __init__(self, *args, source: Optional[str] = None):
        """
        Initialize parse error.
        
        Args:
            *args: Error message arguments
            source: Optional source context for the error
        """
        super().__init__(*args)
        self.source = source

class GlobalLicensingParseTypeError(GlobalLicensingParseError, TypeError):
    """Type error while parsing GlobalLicensing file."""
    pass

class GlobalLicensingParseValueError(GlobalLicensingParseError, ValueError):
    """Value error while parsing GlobalLicensing file."""
    pass

class GlobalLicensingConflictError(ReuseError):
    """Conflicting global licensing files in project."""
    pass

Complete Global Licensing Example

from reuse.global_licensing import ReuseTOML, ReuseDep5, GlobalLicensingConflictError
from reuse.exceptions import GlobalLicensingParseError
from pathlib import Path

def analyze_global_licensing(project_root: Path) -> dict:
    """Comprehensive global licensing analysis."""
    
    result = {
        "has_global_licensing": False,
        "format": None,
        "config_file": None,
        "annotations_count": 0,
        "error": None
    }
    
    # Check for REUSE.toml
    toml_path = project_root / "REUSE.toml"
    dep5_path = project_root / ".reuse" / "dep5"
    
    try:
        if toml_path.exists() and dep5_path.exists():
            result["error"] = "Both REUSE.toml and .reuse/dep5 exist (conflict)"
            return result
        
        elif toml_path.exists():
            global_licensing = ReuseTOML(toml_path)
            result["has_global_licensing"] = True
            result["format"] = "REUSE.toml"
            result["config_file"] = str(toml_path)
            
        elif dep5_path.exists():
            global_licensing = ReuseDep5(dep5_path)
            result["has_global_licensing"] = True
            result["format"] = "DEP5"
            result["config_file"] = str(dep5_path)
            
        else:
            return result
        
        # Test against sample files
        test_files = [
            project_root / "src" / "main.py",
            project_root / "tests" / "test.py",
            project_root / "docs" / "README.md"
        ]
        
        covered_files = 0
        for test_file in test_files:
            reuse_info = global_licensing.reuse_info_of(test_file)
            if reuse_info:
                covered_files += 1
        
        result["covered_files"] = covered_files
        result["total_test_files"] = len(test_files)
        
    except GlobalLicensingParseError as e:
        result["error"] = f"Parse error: {e}"
    except FileNotFoundError as e:
        result["error"] = f"File not found: {e}"
    except Exception as e:
        result["error"] = f"Unexpected error: {e}"
    
    return result

# Usage
project_analysis = analyze_global_licensing(Path("/path/to/project"))
print(f"Global licensing format: {project_analysis['format']}")
print(f"Config file: {project_analysis['config_file']}")
print(f"Files covered: {project_analysis.get('covered_files', 0)}")
if project_analysis['error']:
    print(f"Error: {project_analysis['error']}")

Install with Tessl CLI

npx tessl i tessl/pypi-reuse

docs

cli.md

comment-handling.md

global-licensing.md

index.md

project-management.md

report-generation.md

reuse-info.md

vcs-integration.md

tile.json