A tool for compliance with the REUSE recommendations for software licensing and copyright management.
—
REUSE provides comprehensive compliance reporting capabilities with multiple output formats. The reporting system generates detailed analysis of project compliance status, missing information, and file-by-file breakdowns.
Core report classes for comprehensive project analysis.
class ProjectReport:
"""
Main project compliance report.
Contains comprehensive analysis of all files in a project,
including compliance status, missing information, and detailed
file-by-file breakdowns.
"""
def __init__(
self,
file_reports: list[FileReport],
missing_licenses: set[str],
unused_licenses: set[str],
read_errors: list[tuple[Path, Exception]]
):
"""
Initialize project report.
Args:
file_reports: List of individual file reports
missing_licenses: Set of referenced but missing license files
unused_licenses: Set of license files not referenced by any file
read_errors: List of files that couldn't be read with their errors
"""
class ProjectSubsetReport:
"""
Subset report for specific files.
Used when analyzing only a portion of the project files
rather than the complete project.
"""
def __init__(self, file_reports: list[FileReport]):
"""
Initialize subset report.
Args:
file_reports: List of file reports for the subset
"""
class FileReport:
"""
Report for individual file compliance.
Contains detailed information about a single file's
REUSE compliance status, including copyright, licensing,
and any issues found.
"""
def __init__(
self,
path: Path,
reuse_infos: list[ReuseInfo],
missing_copyright: bool,
missing_license: bool,
bad_licenses: set[str]
):
"""
Initialize file report.
Args:
path: File path being reported on
reuse_infos: List of REUSE information found for the file
missing_copyright: Whether file is missing copyright information
missing_license: Whether file is missing license information
bad_licenses: Set of invalid or problematic license identifiers
"""Protocol definition for report implementations.
class ProjectReportSubsetProtocol(Protocol):
"""
Protocol for report subset implementations.
Defines the interface that report classes must implement
to be compatible with formatting functions.
"""
file_reports: list[FileReport]
def is_compliant(self) -> bool:
"""Check if all files in the report are compliant."""
def non_compliant_files(self) -> list[FileReport]:
"""Get list of non-compliant files."""
def files_missing_copyright(self) -> list[FileReport]:
"""Get files missing copyright information."""
def files_missing_license(self) -> list[FileReport]:
"""Get files missing license information."""Format reports as human-readable plain text output.
def format_plain(report: ProjectReport) -> str:
"""
Format report as plaintext for stdout output.
Args:
report: ProjectReport to format
Returns:
Multi-line string with human-readable report
Note:
Includes summary statistics, file-by-file analysis,
missing licenses, unused licenses, and overall compliance status.
"""Usage Examples:
from reuse.lint import format_plain
from reuse.project import Project
from reuse.report import ProjectReport
from pathlib import Path
# Generate project report (this would typically be done internally)
project = Project.from_directory(Path.cwd())
# ... report generation logic ...
# Format as plain text
plain_report = format_plain(project_report)
print(plain_report)
# Example output:
# ========================= SUMMARY =========================
#
# * Bad licenses: 0
# * Deprecated licenses: 0
# * Licenses without file extension: 0
# * Missing licenses: MIT
# * Unused licenses: 0
# * Used licenses: GPL-3.0-or-later
# * Read errors: 0
# * Files with copyright information: 15 / 20
# * Files with license information: 18 / 20
#
# ========================= MISSING COPYRIGHT ===============
#
# The following files have no copyright information:
# * src/utils.py
# * tests/test_helper.py
#
# ======================= MISSING LICENSES ==================
#
# The following files have no license information:
# * src/config.py
# * README.md
#
# Congratulations! Your project is compliant with version 3.3 of the REUSE Specification :-)Format reports as structured JSON for programmatic processing.
def format_json(report: ProjectReport) -> str:
"""
Format report as JSON string.
Args:
report: ProjectReport to format
Returns:
JSON string with structured report data
Note:
Includes all report data in structured format suitable
for consumption by other tools and scripts.
"""Usage Examples:
from reuse.lint import format_json
import json
# Format as JSON
json_report = format_json(project_report)
report_data = json.loads(json_report)
print(f"Compliant: {report_data['summary']['compliant']}")
print(f"Total files: {report_data['summary']['file_count']}")
# Access detailed file information
for file_info in report_data['files']:
if not file_info['compliant']:
print(f"Non-compliant: {file_info['path']}")
if file_info['missing_copyright']:
print(" - Missing copyright")
if file_info['missing_license']:
print(" - Missing license")
# Example JSON structure:
# {
# "summary": {
# "compliant": false,
# "file_count": 20,
# "compliant_files": 15,
# "non_compliant_files": 5,
# "missing_licenses": ["MIT"],
# "unused_licenses": [],
# "bad_licenses": [],
# "read_errors": 0
# },
# "files": [
# {
# "path": "src/main.py",
# "compliant": true,
# "missing_copyright": false,
# "missing_license": false,
# "licenses": ["GPL-3.0-or-later"],
# "copyrights": ["2023 Jane Doe"]
# }
# ]
# }Format reports as line-based output for processing by text tools.
def format_lines_subset(report: ProjectReportSubsetProtocol) -> str:
"""
Format subset report as lines.
Args:
report: Report implementing ProjectReportSubsetProtocol
Returns:
Line-based string output with one item per line
"""
def format_lines(report: ProjectReport) -> str:
"""
Format full report as lines.
Args:
report: ProjectReport to format
Returns:
Line-based string output suitable for grep, awk, etc.
"""Usage Examples:
from reuse.lint import format_lines, format_lines_subset
# Format full report as lines
lines_report = format_lines(project_report)
print(lines_report)
# Example output:
# NON_COMPLIANT src/utils.py
# MISSING_COPYRIGHT src/utils.py
# MISSING_LICENSE src/config.py
# COMPLIANT src/main.py
# COMPLIANT tests/test_main.py
# Format subset report
subset_files = [report for report in project_report.file_reports[:5]]
subset_report = ProjectSubsetReport(subset_files)
subset_lines = format_lines_subset(subset_report)
print(subset_lines)
# Process with shell tools
# reuse lint --format=lines | grep "NON_COMPLIANT" | cut -d' ' -f2Functions for analyzing and extracting information from reports.
# Report analysis methods (available on report classes)
def is_compliant(self) -> bool:
"""Check if the project/subset is fully compliant."""
def non_compliant_files(self) -> list[FileReport]:
"""Get list of files that are not compliant."""
def files_missing_copyright(self) -> list[FileReport]:
"""Get files missing copyright information."""
def files_missing_license(self) -> list[FileReport]:
"""Get files missing license information."""
def files_with_bad_licenses(self) -> list[FileReport]:
"""Get files with invalid license identifiers."""Usage Examples:
# Analyze project compliance
if project_report.is_compliant():
print("Project is fully REUSE compliant!")
else:
print("Project has compliance issues:")
# Show files missing copyright
missing_copyright = project_report.files_missing_copyright()
if missing_copyright:
print(f"\nFiles missing copyright ({len(missing_copyright)}):")
for file_report in missing_copyright:
print(f" - {file_report.path}")
# Show files missing license
missing_license = project_report.files_missing_license()
if missing_license:
print(f"\nFiles missing license ({len(missing_license)}):")
for file_report in missing_license:
print(f" - {file_report.path}")
# Show files with bad licenses
bad_license_files = project_report.files_with_bad_licenses()
if bad_license_files:
print(f"\nFiles with invalid licenses ({len(bad_license_files)}):")
for file_report in bad_license_files:
print(f" - {file_report.path}: {file_report.bad_licenses}")Examples of custom report processing and analysis.
from reuse.report import ProjectReport, FileReport
from reuse.project import Project
from pathlib import Path
import json
from typing import Dict, List
def generate_compliance_dashboard(project_path: Path) -> Dict:
"""Generate a compliance dashboard with detailed metrics."""
project = Project.from_directory(project_path)
# Note: In practice, you'd use the actual REUSE linting functionality
# This is a simplified example showing report structure usage
dashboard = {
"project_root": str(project_path),
"compliance_overview": {},
"file_categories": {
"compliant": [],
"missing_copyright": [],
"missing_license": [],
"bad_licenses": []
},
"license_analysis": {
"used_licenses": set(),
"missing_licenses": set(),
"unused_licenses": set()
},
"file_type_breakdown": {}
}
# This would typically use the actual report generation
# For demonstration, we'll show the structure
return dashboard
def export_compliance_report(report: ProjectReport, output_dir: Path) -> None:
"""Export compliance report in multiple formats."""
output_dir.mkdir(exist_ok=True)
# Plain text report
plain_path = output_dir / "compliance-report.txt"
with open(plain_path, 'w') as f:
f.write(format_plain(report))
# JSON report
json_path = output_dir / "compliance-report.json"
with open(json_path, 'w') as f:
f.write(format_json(report))
# Lines format for processing
lines_path = output_dir / "compliance-report.lines"
with open(lines_path, 'w') as f:
f.write(format_lines(report))
# Summary CSV
csv_path = output_dir / "compliance-summary.csv"
with open(csv_path, 'w') as f:
f.write("path,compliant,missing_copyright,missing_license,bad_licenses\n")
for file_report in report.file_reports:
compliant = not (file_report.missing_copyright or
file_report.missing_license or
file_report.bad_licenses)
bad_licenses_str = ";".join(file_report.bad_licenses)
f.write(f"{file_report.path},{compliant},"
f"{file_report.missing_copyright},"
f"{file_report.missing_license},"
f'"{bad_licenses_str}"\n')
print(f"Reports exported to {output_dir}")
# Usage
# dashboard = generate_compliance_dashboard(Path("/path/to/project"))
# export_compliance_report(project_report, Path("./reports"))Examples of integrating REUSE reports with other tools and workflows.
def ci_compliance_check(project_path: Path) -> bool:
"""CI/CD integration example for compliance checking."""
project = Project.from_directory(project_path)
# Generate report (simplified - would use actual REUSE linting)
# For CI integration, typically you'd:
# 1. Generate report
# 2. Check compliance status
# 3. Export results
# 4. Return exit code
# if not report.is_compliant():
# print("REUSE compliance check failed!")
# print(format_plain(report))
# return False
print("REUSE compliance check passed!")
return True
def generate_license_inventory(report: ProjectReport) -> Dict[str, List[str]]:
"""Generate license inventory from compliance report."""
inventory = {}
for file_report in report.file_reports:
for reuse_info in file_report.reuse_infos:
for license_expr in reuse_info.spdx_expressions:
license_id = str(license_expr)
if license_id not in inventory:
inventory[license_id] = []
inventory[license_id].append(str(file_report.path))
return inventory
# Usage in CI/CD pipelines:
# exit_code = 0 if ci_compliance_check(Path(".")) else 1
# sys.exit(exit_code)Install with Tessl CLI
npx tessl i tessl/pypi-reuse