Python API
Everything you normally need is exported from the top-level package:
from osslili import (
LicenseCopyrightDetector,
DetectionResult,
DetectedLicense,
CopyrightInfo,
Config,
)
Quick start
from osslili import LicenseCopyrightDetector
detector = LicenseCopyrightDetector()
result = detector.process_local_path("/path/to/project")
primary = result.get_primary_license()
if primary:
print(f"{primary.spdx_id} ({primary.confidence:.2f} via {primary.detection_method})")
for c in result.copyrights:
print(c.statement)
LicenseCopyrightDetector
LicenseCopyrightDetector(config: Optional[Config] = None)
Omit config for defaults. The detector loads its SPDX data lazily, so constructing
one is cheap; reuse a single instance across scans to avoid reloading it.
process_local_path
process_local_path(path: str, extract_archives: bool = True) -> DetectionResult
Scan a directory, a file, or an archive. With extract_archives=True an archive is
extracted to a temporary directory and scanned in place.
result = detector.process_local_path("./package-1.0.0.tar.gz")
result = detector.process_local_path("./src", extract_archives=False)
extract_package_metadata
extract_package_metadata(path: str) -> DetectionResult
Read only the declared license from package metadata files, skipping all text analysis. Use it when you want the declared license and nothing inferred — it is substantially faster than a full scan.
result = detector.extract_package_metadata("./package.json")
generate_evidence
generate_evidence(results: List[DetectionResult], detail_level: str = "detailed") -> str
Render results as an evidence JSON string. detail_level is "minimal",
"summary", "detailed", or "full". Note that it takes a list of results.
print(detector.generate_evidence([result], detail_level="full"))
generate_kissbom
generate_kissbom(results: List[DetectionResult]) -> str
generate_cyclonedx
generate_cyclonedx(results: List[DetectionResult], format_type: str = "json") -> str
format_type is "json" or "xml".
with open("sbom.json", "w") as f:
f.write(detector.generate_cyclonedx([result]))
DetectionResult
| Field | Type | Description |
|---|---|---|
path |
str |
The scanned path |
licenses |
List[DetectedLicense] |
Every detection, sorted by confidence |
copyrights |
List[CopyrightInfo] |
Extracted copyright statements |
errors |
List[str] |
Non-fatal errors encountered |
confidence_scores |
Dict[str, float] |
Highest confidence per category (license, copyright) |
processing_time |
float |
Seconds elapsed |
package_name |
Optional[str] |
Package name, when determinable |
package_version |
Optional[str] |
Package version, when determinable |
Methods
get_primary_license() -> Optional[DetectedLicense]
get_own_licenses() -> List[DetectedLicense]
get_third_party_licenses() -> List[DetectedLicense]
to_dict() -> Dict[str, Any]
get_primary_license() returns the project’s own best-supported license, or None.
It never returns a license sourced from a bundled third-party notice file — if only
third-party notices were found it returns None, and those licenses remain available
through get_third_party_licenses().
This distinction matters when determining what a project is licensed under. A
vendored THIRD_PARTY_NOTICES file contains dependencies’ licenses, and counting them
as the project’s own can turn a permissive project into an apparently copyleft one:
result = detector.process_local_path("./project")
own = result.get_own_licenses()
third_party = result.get_third_party_licenses()
print("This project:", sorted({l.spdx_id for l in own}))
print("Bundled deps:", sorted({l.spdx_id for l in third_party}))
DetectedLicense
| Field | Type | Description |
|---|---|---|
spdx_id |
str |
SPDX identifier, always validated against the SPDX list |
name |
str |
Human-readable license name |
text |
Optional[str] |
License text, when captured |
confidence |
float |
0.0–1.0 |
detection_method |
str |
hash, dice-sorensen, tlsh, regex, tag, keyword, filename |
source_file |
Optional[str] |
File the detection came from |
category |
Optional[str] |
declared, detected, referenced, third-party |
match_type |
Optional[str] |
How it matched, e.g. license_file, package_metadata, keyword |
See Detection for what these values mean.
CopyrightInfo
| Field | Type | Description |
|---|---|---|
holder |
str |
Copyright holder |
years |
Optional[List[int]] |
Years claimed |
statement |
str |
The full statement as it appears |
source_file |
Optional[str] |
File it came from |
confidence |
float |
0.0–1.0 |
Enums
from osslili.core.models import LicenseCategory, DetectionMethod
LicenseCategory.DECLARED # "declared"
LicenseCategory.DETECTED # "detected"
LicenseCategory.REFERENCED # "referenced"
LicenseCategory.THIRD_PARTY # "third-party"
DetectionMethod.HASH # "hash"
DetectionMethod.DICE_SORENSEN # "dice-sorensen"
DetectionMethod.TLSH # "tlsh"
DetectionMethod.REGEX # "regex"
DetectionMethod.TAG # "tag"
DetectionMethod.KEYWORD # "keyword"
DetectionMethod.FILENAME # "filename"
Compare against .value when reading a DetectedLicense, since its fields hold
strings:
declared = [l for l in result.licenses if l.category == LicenseCategory.DECLARED.value]
Examples
Scanning several projects
from pathlib import Path
from osslili import LicenseCopyrightDetector
detector = LicenseCopyrightDetector()
results = [detector.process_local_path(str(p)) for p in Path("./repos").iterdir() if p.is_dir()]
with open("sbom.json", "w") as f:
f.write(detector.generate_cyclonedx(results))
Deep scan with a tuned configuration
from osslili import LicenseCopyrightDetector, Config
config = Config()
config.license_files_only = False # scan all source files
config.deep_scan = True
config.thread_count = 8
config.max_recursion_depth = -1 # unlimited
result = LicenseCopyrightDetector(config).process_local_path("./project")
Failing a build on an unexpected license
ALLOWED = {"MIT", "Apache-2.0", "BSD-3-Clause", "ISC"}
result = detector.process_local_path("./project")
unexpected = {l.spdx_id for l in result.get_own_licenses()} - ALLOWED
if unexpected:
raise SystemExit(f"Unexpected licenses: {sorted(unexpected)}")
Use get_own_licenses() rather than result.licenses here — bundled third-party
notices would otherwise fail the check for licenses the project does not itself carry.