or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mdcomment-handling.mdglobal-licensing.mdindex.mdproject-management.mdreport-generation.mdreuse-info.mdvcs-integration.md

global-licensing.mddocs/

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

```