0
# Global Licensing Configuration
1
2
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.
3
4
## Capabilities
5
6
### Abstract Base Class
7
8
Foundation for all global licensing implementations.
9
10
```python { .api }
11
class GlobalLicensing(ABC):
12
"""
13
Abstract base class for global licensing configurations.
14
15
Provides the interface for different global licensing file formats
16
including .reuse/dep5 and REUSE.toml files.
17
"""
18
19
source: str # Source file path
20
21
@classmethod
22
@abstractmethod
23
def from_file(cls, path: StrPath, **kwargs: Any) -> "GlobalLicensing":
24
"""Parse the file and create a GlobalLicensing object from its contents."""
25
26
@abstractmethod
27
def reuse_info_of(self, path: StrPath) -> dict[PrecedenceType, list[ReuseInfo]]:
28
"""
29
Get REUSE information for a file based on global configuration.
30
31
Args:
32
path: File path to get information for (accepts str or Path-like)
33
34
Returns:
35
Dictionary mapping precedence types to lists of ReuseInfo objects
36
"""
37
```
38
39
### Precedence System
40
41
Enumeration defining precedence levels for global licensing information.
42
43
```python { .api }
44
class PrecedenceType(Enum):
45
"""
46
Precedence levels for global licensing.
47
48
Defines the order in which licensing information sources are applied,
49
with higher precedence values taking priority over lower ones.
50
"""
51
AGGREGATE = "aggregate" # Aggregate the results from the file with the results from the global licensing file
52
CLOSEST = "closest" # Use the results that are closest to the covered file
53
OVERRIDE = "override" # Only use the results from the global licensing file
54
```
55
56
### DEP5 Format Support
57
58
Implementation for .reuse/dep5 files following the Debian DEP5 specification.
59
60
```python { .api }
61
class ReuseDep5(GlobalLicensing):
62
"""
63
Implementation for .reuse/dep5 files.
64
65
Supports the Debian DEP5 (Machine-readable debian/copyright) format
66
for specifying copyright and licensing information for multiple files
67
using glob patterns.
68
"""
69
70
def __init__(self, path: Path):
71
"""
72
Initialize DEP5 global licensing.
73
74
Args:
75
path: Path to the .reuse/dep5 file
76
77
Raises:
78
GlobalLicensingParseError: If file cannot be parsed
79
FileNotFoundError: If dep5 file doesn't exist
80
"""
81
82
def reuse_info_of(self, path: Path) -> list[ReuseInfo]:
83
"""
84
Get REUSE info based on DEP5 configuration.
85
86
Args:
87
path: File path to check against DEP5 patterns
88
89
Returns:
90
List of ReuseInfo matching the file path
91
"""
92
```
93
94
**Usage Examples:**
95
96
```python
97
from reuse.global_licensing import ReuseDep5
98
from pathlib import Path
99
100
# Create DEP5 instance
101
try:
102
dep5_file = Path(".reuse/dep5")
103
dep5 = ReuseDep5(dep5_file)
104
105
# Get licensing info for specific files
106
source_file = Path("src/example.py")
107
reuse_info = dep5.reuse_info_of(source_file)
108
109
for info in reuse_info:
110
print(f"Licenses: {info.spdx_expressions}")
111
print(f"Copyright: {info.copyright_lines}")
112
print(f"Source: {info.source_type}")
113
114
except FileNotFoundError:
115
print("No .reuse/dep5 file found")
116
except GlobalLicensingParseError as e:
117
print(f"Error parsing DEP5 file: {e}")
118
```
119
120
### REUSE.toml Format Support
121
122
Implementation for REUSE.toml files using the modern TOML-based configuration format.
123
124
```python { .api }
125
class ReuseTOML(GlobalLicensing):
126
"""
127
Implementation for REUSE.toml files.
128
129
Supports the modern REUSE.toml format for specifying copyright
130
and licensing information using TOML syntax and glob patterns.
131
"""
132
133
def __init__(self, path: Path):
134
"""
135
Initialize REUSE.toml global licensing.
136
137
Args:
138
path: Path to the REUSE.toml file
139
140
Raises:
141
GlobalLicensingParseError: If TOML file cannot be parsed
142
FileNotFoundError: If REUSE.toml file doesn't exist
143
"""
144
145
def reuse_info_of(self, path: Path) -> list[ReuseInfo]:
146
"""
147
Get REUSE info based on REUSE.toml configuration.
148
149
Args:
150
path: File path to check against TOML patterns
151
152
Returns:
153
List of ReuseInfo matching the file path
154
"""
155
```
156
157
**Usage Examples:**
158
159
```python
160
from reuse.global_licensing import ReuseTOML
161
from pathlib import Path
162
163
# Create REUSE.toml instance
164
try:
165
toml_file = Path("REUSE.toml")
166
reuse_toml = ReuseTOML(toml_file)
167
168
# Get licensing info for files
169
test_files = [
170
Path("src/main.py"),
171
Path("tests/test_example.py"),
172
Path("docs/api.md")
173
]
174
175
for file_path in test_files:
176
reuse_info = reuse_toml.reuse_info_of(file_path)
177
if reuse_info:
178
print(f"\n{file_path}:")
179
for info in reuse_info:
180
print(f" Licenses: {list(info.spdx_expressions)}")
181
print(f" Copyright: {list(info.copyright_lines)}")
182
else:
183
print(f"{file_path}: No global licensing info")
184
185
except FileNotFoundError:
186
print("No REUSE.toml file found")
187
except GlobalLicensingParseError as e:
188
print(f"Error parsing REUSE.toml: {e}")
189
```
190
191
### Nested REUSE.toml Support
192
193
Implementation for nested REUSE.toml configurations in subdirectories.
194
195
```python { .api }
196
class NestedReuseTOML(GlobalLicensing):
197
"""
198
Implementation for nested REUSE.toml configurations.
199
200
Supports hierarchical REUSE.toml files in subdirectories,
201
allowing different parts of a project to have different
202
global licensing configurations.
203
"""
204
205
def __init__(self, paths: list[Path]):
206
"""
207
Initialize nested REUSE.toml licensing.
208
209
Args:
210
paths: List of paths to REUSE.toml files in hierarchical order
211
212
Raises:
213
GlobalLicensingParseError: If any TOML file cannot be parsed
214
"""
215
216
def reuse_info_of(self, path: Path) -> list[ReuseInfo]:
217
"""
218
Get REUSE info from nested TOML configurations.
219
220
Args:
221
path: File path to check against nested configurations
222
223
Returns:
224
List of ReuseInfo from applicable nested configurations
225
"""
226
```
227
228
### Annotation Items
229
230
Data structure for individual annotation entries in global licensing files.
231
232
```python { .api }
233
class AnnotationsItem:
234
"""
235
Individual annotation item in global licensing.
236
237
Represents a single annotation entry that maps file patterns
238
to copyright and licensing information.
239
"""
240
241
def __init__(
242
self,
243
path_patterns: list[str],
244
copyright_lines: set[str],
245
spdx_expressions: set[str],
246
contributor_lines: set[str] = None
247
):
248
"""
249
Initialize annotation item.
250
251
Args:
252
path_patterns: List of glob patterns for matching files
253
copyright_lines: Set of copyright statements
254
spdx_expressions: Set of SPDX license identifiers
255
contributor_lines: Optional set of contributor information
256
"""
257
258
def matches_path(self, path: Path) -> bool:
259
"""
260
Check if path matches any of the patterns.
261
262
Args:
263
path: File path to check
264
265
Returns:
266
True if path matches any pattern in this annotation
267
"""
268
```
269
270
## Global Licensing File Formats
271
272
### DEP5 Format Example
273
274
The .reuse/dep5 file uses the Debian DEP5 format:
275
276
```
277
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
278
Upstream-Name: example-project
279
Upstream-Contact: Jane Doe <jane@example.com>
280
Source: https://github.com/example/project
281
282
Files: src/*.py
283
Copyright: 2023 Jane Doe <jane@example.com>
284
License: MIT
285
286
Files: tests/*
287
Copyright: 2023 Jane Doe <jane@example.com>
288
License: MIT
289
290
Files: docs/*.md
291
Copyright: 2023 Jane Doe <jane@example.com>
292
License: CC-BY-4.0
293
```
294
295
### REUSE.toml Format Example
296
297
The REUSE.toml file uses TOML syntax:
298
299
```toml
300
version = 1
301
SPDX-PackageName = "example-project"
302
SPDX-PackageSupplier = "Jane Doe <jane@example.com>"
303
SPDX-PackageDownloadLocation = "https://github.com/example/project"
304
305
[[annotations]]
306
path = ["src/*.py", "src/**/*.py"]
307
precedence = "aggregate"
308
SPDX-FileCopyrightText = [
309
"2023 Jane Doe <jane@example.com>",
310
"2023 Example Corp"
311
]
312
SPDX-License-Identifier = ["MIT", "Apache-2.0"]
313
314
[[annotations]]
315
path = "tests/**"
316
precedence = "aggregate"
317
SPDX-FileCopyrightText = "2023 Jane Doe <jane@example.com>"
318
SPDX-License-Identifier = "MIT"
319
320
[[annotations]]
321
path = "docs/*.md"
322
precedence = "aggregate"
323
SPDX-FileCopyrightText = "2023 Jane Doe <jane@example.com>"
324
SPDX-License-Identifier = "CC-BY-4.0"
325
```
326
327
## Exception Handling
328
329
Global licensing operations can raise specific exceptions for error handling.
330
331
```python { .api }
332
class GlobalLicensingParseError(ReuseError):
333
"""Error parsing GlobalLicensing file."""
334
335
def __init__(self, *args, source: Optional[str] = None):
336
"""
337
Initialize parse error.
338
339
Args:
340
*args: Error message arguments
341
source: Optional source context for the error
342
"""
343
super().__init__(*args)
344
self.source = source
345
346
class GlobalLicensingParseTypeError(GlobalLicensingParseError, TypeError):
347
"""Type error while parsing GlobalLicensing file."""
348
pass
349
350
class GlobalLicensingParseValueError(GlobalLicensingParseError, ValueError):
351
"""Value error while parsing GlobalLicensing file."""
352
pass
353
354
class GlobalLicensingConflictError(ReuseError):
355
"""Conflicting global licensing files in project."""
356
pass
357
```
358
359
## Complete Global Licensing Example
360
361
```python
362
from reuse.global_licensing import ReuseTOML, ReuseDep5, GlobalLicensingConflictError
363
from reuse.exceptions import GlobalLicensingParseError
364
from pathlib import Path
365
366
def analyze_global_licensing(project_root: Path) -> dict:
367
"""Comprehensive global licensing analysis."""
368
369
result = {
370
"has_global_licensing": False,
371
"format": None,
372
"config_file": None,
373
"annotations_count": 0,
374
"error": None
375
}
376
377
# Check for REUSE.toml
378
toml_path = project_root / "REUSE.toml"
379
dep5_path = project_root / ".reuse" / "dep5"
380
381
try:
382
if toml_path.exists() and dep5_path.exists():
383
result["error"] = "Both REUSE.toml and .reuse/dep5 exist (conflict)"
384
return result
385
386
elif toml_path.exists():
387
global_licensing = ReuseTOML(toml_path)
388
result["has_global_licensing"] = True
389
result["format"] = "REUSE.toml"
390
result["config_file"] = str(toml_path)
391
392
elif dep5_path.exists():
393
global_licensing = ReuseDep5(dep5_path)
394
result["has_global_licensing"] = True
395
result["format"] = "DEP5"
396
result["config_file"] = str(dep5_path)
397
398
else:
399
return result
400
401
# Test against sample files
402
test_files = [
403
project_root / "src" / "main.py",
404
project_root / "tests" / "test.py",
405
project_root / "docs" / "README.md"
406
]
407
408
covered_files = 0
409
for test_file in test_files:
410
reuse_info = global_licensing.reuse_info_of(test_file)
411
if reuse_info:
412
covered_files += 1
413
414
result["covered_files"] = covered_files
415
result["total_test_files"] = len(test_files)
416
417
except GlobalLicensingParseError as e:
418
result["error"] = f"Parse error: {e}"
419
except FileNotFoundError as e:
420
result["error"] = f"File not found: {e}"
421
except Exception as e:
422
result["error"] = f"Unexpected error: {e}"
423
424
return result
425
426
# Usage
427
project_analysis = analyze_global_licensing(Path("/path/to/project"))
428
print(f"Global licensing format: {project_analysis['format']}")
429
print(f"Config file: {project_analysis['config_file']}")
430
print(f"Files covered: {project_analysis.get('covered_files', 0)}")
431
if project_analysis['error']:
432
print(f"Error: {project_analysis['error']}")
433
```