A tool for compliance with the REUSE recommendations for software licensing and copyright management.
—
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.
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
"""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 fileImplementation 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}")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}")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
"""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
"""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.0The 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"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."""
passfrom 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