0
# Core Licensing Operations
1
2
The Licensing class provides the main interface for parsing, validating, and manipulating license expressions. It extends the boolean algebra system to provide license-specific functionality including expression parsing, validation, equivalence testing, and containment analysis.
3
4
## Capabilities
5
6
### Licensing Class
7
8
The primary class for working with license expressions, providing comprehensive parsing, validation, and comparison capabilities.
9
10
```python { .api }
11
class Licensing:
12
"""
13
Main entry point for license expression operations.
14
Extends boolean.BooleanAlgebra with license-specific functionality.
15
"""
16
17
def __init__(self, symbols=None, quiet: bool = True):
18
"""
19
Initialize Licensing with optional license symbols.
20
21
Parameters:
22
- symbols: Iterable of LicenseSymbol objects or strings
23
- quiet: Print warnings and errors found in symbols unless True (default: True)
24
"""
25
26
def parse(self, expression: str, validate: bool = False, strict: bool = False, simple: bool = False, **kwargs) -> LicenseExpression:
27
"""
28
Parse a license expression string into a LicenseExpression object.
29
30
Parameters:
31
- expression: License expression string to parse
32
- validate: Whether to validate symbols against known licenses
33
- strict: Whether to use strict validation (raises exceptions for unknown symbols)
34
- simple: Whether to return a simple expression without normalization
35
- **kwargs: Additional keyword arguments
36
37
Returns:
38
LicenseExpression object representing the parsed expression
39
40
Raises:
41
- ExpressionParseError: If parsing fails or strict=True with unknown symbols
42
- ExpressionError: If validation fails with unknown symbols
43
"""
44
45
def validate(self, expression: str, strict: bool = True, **kwargs) -> ExpressionInfo:
46
"""
47
Validate a license expression and return detailed validation information.
48
49
Parameters:
50
- expression: License expression string to validate
51
- strict: Whether to use strict validation (default: True)
52
- **kwargs: Additional keyword arguments
53
54
Returns:
55
ExpressionInfo object containing validation results, errors, and normalized expression
56
"""
57
58
def is_equivalent(self, expr1, expr2) -> bool:
59
"""
60
Test if two license expressions are logically equivalent.
61
62
Parameters:
63
- expr1: First expression (string or LicenseExpression)
64
- expr2: Second expression (string or LicenseExpression)
65
66
Returns:
67
True if expressions are equivalent, False otherwise
68
"""
69
70
def contains(self, expr1, expr2) -> bool:
71
"""
72
Test if the first expression contains (implies) the second expression.
73
74
Parameters:
75
- expr1: Container expression (string or LicenseExpression)
76
- expr2: Contained expression (string or LicenseExpression)
77
78
Returns:
79
True if expr1 contains expr2, False otherwise
80
"""
81
82
def license_symbols(self, expression, unique: bool = True, decompose: bool = True, **kwargs) -> list:
83
"""
84
Extract all license symbols from an expression.
85
86
Parameters:
87
- expression: License expression (string or LicenseExpression)
88
- unique: Only return unique symbols (default: True)
89
- decompose: Decompose composite symbols like LicenseWithExceptionSymbol (default: True)
90
- **kwargs: Additional keyword arguments
91
92
Returns:
93
List of LicenseSymbol objects found in the expression
94
"""
95
96
def primary_license_symbol(self, expression, decompose: bool = True, **kwargs):
97
"""
98
Return the left-most license symbol of an expression or None.
99
100
Parameters:
101
- expression: License expression (string or LicenseExpression)
102
- decompose: Only return license symbol from decomposed composite symbols (default: True)
103
- **kwargs: Additional keyword arguments
104
105
Returns:
106
First LicenseSymbol object found in the expression, or None
107
"""
108
109
def primary_license_key(self, expression, **kwargs) -> str:
110
"""
111
Return the left-most license key of an expression or None.
112
113
Parameters:
114
- expression: License expression (string or LicenseExpression)
115
- **kwargs: Additional keyword arguments
116
117
Returns:
118
First license key found in the expression, or None
119
"""
120
121
def license_keys(self, expression, unique: bool = True, **kwargs) -> list:
122
"""
123
Return a list of license keys used in an expression.
124
125
Parameters:
126
- expression: License expression (string or LicenseExpression)
127
- unique: Only return unique keys (default: True)
128
- **kwargs: Additional keyword arguments
129
130
Returns:
131
List of license key strings found in the expression
132
"""
133
134
def unknown_license_symbols(self, expression, unique: bool = True, **kwargs) -> list:
135
"""
136
Return a list of unknown license symbols used in an expression.
137
138
Parameters:
139
- expression: License expression (string or LicenseExpression)
140
- unique: Only return unique symbols (default: True)
141
- **kwargs: Additional keyword arguments
142
143
Returns:
144
List of unknown LicenseSymbol objects found in the expression
145
"""
146
147
def unknown_license_keys(self, expression, unique: bool = True, **kwargs) -> list:
148
"""
149
Return a list of unknown license keys used in an expression.
150
151
Parameters:
152
- expression: License expression (string or LicenseExpression)
153
- unique: Only return unique keys (default: True)
154
- **kwargs: Additional keyword arguments
155
156
Returns:
157
List of unknown license key strings found in the expression
158
"""
159
160
def validate_license_keys(self, expression):
161
"""
162
Validate license keys in an expression, raising ExpressionError for unknown keys.
163
164
Parameters:
165
- expression: License expression (string or LicenseExpression)
166
167
Raises:
168
- ExpressionError: If unknown license keys are found
169
"""
170
171
def dedup(self, expression):
172
"""
173
Return a deduplicated LicenseExpression given a license expression.
174
175
Deduplication is similar to simplification but specialized for license expressions.
176
Unlike simplification, choices (OR expressions) are kept as-is to preserve options.
177
178
Parameters:
179
- expression: License expression (string or LicenseExpression)
180
181
Returns:
182
Deduplicated LicenseExpression object
183
"""
184
185
def tokenize(self, expression: str, strict: bool = False, simple: bool = False):
186
"""
187
Return an iterable of 3-tuple describing each token in an expression string.
188
189
Parameters:
190
- expression: License expression string to tokenize
191
- strict: Use strict validation for WITH expressions (default: False)
192
- simple: Use simple tokenizer assuming no spaces in license symbols (default: False)
193
194
Returns:
195
Iterator of (token_obj, token_string, position) tuples
196
"""
197
198
def get_advanced_tokenizer(self):
199
"""
200
Return an AdvancedTokenizer instance for this Licensing.
201
202
Returns:
203
AdvancedTokenizer instance that recognizes known symbol keys and aliases
204
"""
205
206
def simple_tokenizer(self, expression: str):
207
"""
208
Return an iterable of Token from an expression string using simple tokenization.
209
210
Parameters:
211
- expression: License expression string to tokenize
212
213
Returns:
214
Iterator of Token objects from simple tokenization
215
"""
216
217
def advanced_tokenizer(self, expression: str):
218
"""
219
Return an iterable of Token from an expression string using advanced tokenization.
220
221
Parameters:
222
- expression: License expression string to tokenize
223
224
Returns:
225
Iterator of Token objects from advanced tokenization
226
"""
227
```
228
229
### Usage Examples
230
231
#### Basic Parsing
232
233
```python
234
from license_expression import Licensing
235
236
licensing = Licensing()
237
expr = licensing.parse('MIT or Apache-2.0')
238
print(str(expr)) # 'MIT OR Apache-2.0'
239
```
240
241
#### Validation with Error Handling
242
243
```python
244
# Validate an expression with unknown licenses
245
result = licensing.validate('MIT and UnknownLicense')
246
print(result.errors) # ['Unknown license key(s): UnknownLicense']
247
print(result.invalid_symbols) # ['UnknownLicense']
248
249
# Strict parsing raises exceptions
250
try:
251
licensing.parse('MIT and UnknownLicense', validate=True, strict=True)
252
except ExpressionParseError as e:
253
print(f"Parse error: {e}")
254
```
255
256
#### Expression Comparison
257
258
```python
259
# Test equivalence
260
expr1 = 'MIT or (Apache-2.0 and GPL-2.0)'
261
expr2 = '(GPL-2.0 and Apache-2.0) or MIT'
262
print(licensing.is_equivalent(expr1, expr2)) # True
263
264
# Test containment
265
broad_expr = 'MIT or Apache-2.0 or GPL-2.0'
266
specific_expr = 'MIT'
267
print(licensing.contains(broad_expr, specific_expr)) # True
268
```
269
270
#### Symbol Extraction
271
272
```python
273
expression = 'MIT with Exception or (Apache-2.0 and GPL-2.0+)'
274
symbols = licensing.license_symbols(expression)
275
for symbol in symbols:
276
print(f"Key: {symbol.key}, Exception: {symbol.is_exception}")
277
```
278
279
### Boolean Operations
280
281
The Licensing class provides boolean operators for combining expressions:
282
283
```python { .api }
284
class Licensing:
285
AND: type # Boolean AND operator class
286
OR: type # Boolean OR operator class
287
288
def __call__(self, *args, **kwargs):
289
"""Create boolean expressions using the licensing instance as callable."""
290
```
291
292
#### Boolean Operation Examples
293
294
```python
295
# Create expressions using boolean operations
296
mit = licensing.parse('MIT')
297
apache = licensing.parse('Apache-2.0')
298
299
# Combine with AND
300
combined_and = licensing.AND(mit, apache)
301
print(str(combined_and)) # 'MIT AND Apache-2.0'
302
303
# Combine with OR
304
combined_or = licensing.OR(mit, apache)
305
print(str(combined_or)) # 'MIT OR Apache-2.0'
306
```
307
308
### Expression Simplification
309
310
```python
311
# Simplify complex expressions
312
complex_expr = licensing.parse('MIT or (MIT and Apache-2.0) or MIT')
313
simplified = complex_expr.simplify()
314
print(str(simplified)) # 'MIT'
315
316
# Simplify with normalization
317
redundant = licensing.parse('(A and B) or (B and A) or A')
318
simplified = redundant.simplify()
319
print(str(simplified)) # 'A OR (A AND B)' or similar simplified form
320
```
321
322
### Advanced License Analysis
323
324
```python
325
from license_expression import get_spdx_licensing
326
327
licensing = get_spdx_licensing()
328
expression = 'MIT or (Apache-2.0 and GPL-2.0+) or MIT'
329
330
# Get primary license information
331
primary_symbol = licensing.primary_license_symbol(expression)
332
primary_key = licensing.primary_license_key(expression)
333
print(f"Primary license: {primary_key}") # 'MIT'
334
335
# Get all license keys (with and without duplicates)
336
all_keys = licensing.license_keys(expression, unique=False)
337
unique_keys = licensing.license_keys(expression, unique=True)
338
print(f"All keys: {all_keys}") # ['MIT', 'Apache-2.0', 'GPL-2.0+', 'MIT']
339
print(f"Unique keys: {unique_keys}") # ['MIT', 'Apache-2.0', 'GPL-2.0+']
340
341
# Find unknown licenses
342
unknown_keys = licensing.unknown_license_keys('MIT and UnknownLicense')
343
print(f"Unknown keys: {unknown_keys}") # ['UnknownLicense']
344
```
345
346
### Expression Deduplication
347
348
```python
349
# Deduplication preserves license choices (unlike simplification)
350
complex_expr = 'MIT or Apache-2.0 or MIT or (GPL-2.0 and MIT)'
351
deduplicated = licensing.dedup(complex_expr)
352
print(str(deduplicated)) # Removes MIT duplicates but preserves structure
353
354
# Compare with simplification
355
simplified = licensing.parse(complex_expr).simplify()
356
print(str(simplified)) # May be more aggressively simplified
357
```
358
359
### Advanced Tokenization
360
361
```python
362
expression = 'MIT WITH Classpath-exception-2.0 OR Apache-2.0'
363
364
# Tokenize with different methods
365
for token_obj, token_string, position in licensing.tokenize(expression):
366
print(f"Token: '{token_string}' at position {position}, type: {type(token_obj)}")
367
368
# Get advanced tokenizer for reuse
369
tokenizer = licensing.get_advanced_tokenizer()
370
```