Skip to content

Policy

Policy evaluation: which detected licenses are acceptable, and for which packages. Deliberately separate from detection — detection says what license a package carries, policy says whether that's acceptable for this project.

trustedlicenses.Policy(allowed_categories, ignored_packages=frozenset(), project_license_keys=frozenset(), trust_corrected_licenses=False, verified_packages=dict(), verified_statements=dict()) dataclass

A project's license policy.

Attributes:

Name Type Description
allowed_categories frozenset[str]

scancode license categories this project accepts (e.g. Permissive, Public Domain, Copyleft Limited). A distribution passes when at least one of its detected licenses falls into one of these categories -- see :func:evaluate for why "at least one" rather than "none disallowed".

ignored_packages frozenset[str]

Canonical (PEP 503 normalised) names of distributions exempted from the check entirely, regardless of what they detect as.

project_license_keys frozenset[str]

SPDX identifiers the consuming project's own declared license resolves to (from its [project.license], per :pep:639). Empty when the project declares nothing resolvable. Used only for the informational compatibility notes in :func:evaluate -- never affects pass/fail.

trust_corrected_licenses bool

Trust every free-text correction (:attr:~trustedlicenses.detection.DistributionLicence.suggested) project-wide. Off by default: a project shouldn't start silently passing packages it previously flagged just because detection got smarter -- see :func:_trusted_suggestion_ids.

verified_packages Mapping[str, tuple[str, str]]

Canonical package name -> the exact (declared statement, corrected SPDX id) pair a human verified for that package. Only trusted while the pair still matches what's currently detected -- if the package's declared statement ever changes, the pin silently stops applying and the package needs re-review, rather than trusting a new, unreviewed string under the old id.

verified_statements Mapping[str, str]

Declared statement text -> the SPDX id a human verified it corrects to, for any package with that exact statement (e.g. several internal packages sharing identical boilerplate). Same re-check-on-change property as verified_packages.

trustedlicenses.PolicyResult(failures, checked, compatibility_notes=tuple()) dataclass

The outcome of evaluating a :class:Policy against installed distributions.

Attributes:

Name Type Description
failures tuple[DistributionLicence, ...]

Distributions that detected no license in an allowed category, sorted by name.

checked int

How many distributions were evaluated (ignored ones excluded).

compatibility_notes tuple[str, ...]

Informational (never pass/fail-affecting) notes about a specific, FSF-documented copyleft compatibility concern between the consuming project's own declared license and a dependency's -- see :func:_compatibility_note. Empty when the project declares no resolvable license, or nothing triggered a note. These can fire even for a dependency that otherwise passed the category check: a category match (e.g. both "Copyleft") doesn't guarantee the specific licenses within it are compatible with each other.

Attributes

passed property

Whether every checked distribution had an allowed license.

trustedlicenses.detect_all(distributions_=None, *, exclude=frozenset())

Detect the license of every distribution, with no policy applied.

Used both by :func:evaluate and for report-only output when no policy is configured yet (see :func:trustedlicenses.cli.main) -- detection doesn't need a policy to run, only to judge.

Parameters:

Name Type Description Default
distributions_ Iterable[Distribution] | None

Distributions to inspect. Defaults to every distribution installed in the current environment; overridable for testing, or for inspecting an arbitrary install location (e.g. an isolated temporary directory a candidate package was resolved into).

None
exclude frozenset[str]

Canonical (PEP 503 normalised) names to skip entirely.

frozenset()

Returns:

Name Type Description
One DistributionLicence

class:~trustedlicenses.detection.DistributionLicence per distinct

...

(canonical-name-deduped) distribution, sorted by name.

Source code in src/trustedlicenses/policy.py
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
def detect_all(
    distributions_: Iterable[Distribution] | None = None,
    *,
    exclude: frozenset[str] = frozenset(),
) -> tuple[DistributionLicence, ...]:
    """Detect the license of every distribution, with no policy applied.

    Used both by :func:`evaluate` and for report-only output when no policy is
    configured yet (see :func:`trustedlicenses.cli.main`) -- detection doesn't need a
    policy to run, only to judge.

    Args:
        distributions_: Distributions to inspect. Defaults to every distribution
            installed in the current environment; overridable for testing, or for
            inspecting an arbitrary install location (e.g. an isolated temporary
            directory a candidate package was resolved into).
        exclude: Canonical (PEP 503 normalised) names to skip entirely.

    Returns:
        One :class:`~trustedlicenses.detection.DistributionLicence` per distinct
        (canonical-name-deduped) distribution, sorted by name.
    """
    # distributions() yields one entry per sys.path location a distribution is
    # importable from, so the same package can come back more than once -- and the
    # copies can differ (one stripped of its license file, one not). All copies are
    # inspected and the best result kept, so the outcome doesn't depend on sys.path order.
    to_inspect: dict[str, list[Distribution]] = {}
    for dist in distributions_ if distributions_ is not None else distributions():
        name = canonical_name(dist.metadata["Name"] or "")
        if not name or name in exclude:
            continue
        to_inspect.setdefault(name, []).append(dist)

    jobs = [(name, dist) for name, dists in to_inspect.items() for dist in dists]

    # Most packages resolve from declared metadata (microseconds); the minority that
    # fall back to the Rust text matcher can each take tens to hundreds of milliseconds
    # (see `trustedlicenses.rust_matcher.scan_license_text`'s docstring) -- a thread
    # pool actually parallelizes that work, since the matcher releases the GIL for the
    # scan itself, rather than running every package's detection strictly one at a time.
    with ThreadPoolExecutor() as executor:
        inspected = list(executor.map(lambda job: inspect_distribution(job[1], name=job[0]), jobs))

    by_name: dict[str, list[DistributionLicence]] = {}
    for result in inspected:
        by_name.setdefault(result.name, []).append(result)
    return tuple(sorted((_best_result(copies) for copies in by_name.values()), key=lambda result: result.name))

trustedlicenses.evaluate(policy, distributions_=None)

Detect every installed distribution and evaluate it against a policy.

Parameters:

Name Type Description Default
policy Policy

The policy to evaluate against.

required
distributions_ Iterable[Distribution] | None

Distributions to check. Defaults to every distribution installed in the current environment; overridable for testing.

None

Returns:

Type Description
PolicyResult

The distributions that failed the policy, and how many were checked.

Source code in src/trustedlicenses/policy.py
297
298
299
300
301
302
303
304
305
306
307
308
309
def evaluate(policy: Policy, distributions_: Iterable[Distribution] | None = None) -> PolicyResult:
    """Detect every installed distribution and evaluate it against a policy.

    Args:
        policy: The policy to evaluate against.
        distributions_: Distributions to check. Defaults to every distribution
            installed in the current environment; overridable for testing.

    Returns:
        The distributions that failed the policy, and how many were checked.
    """
    detected = detect_all(distributions_, exclude=policy.ignored_packages)
    return reevaluate(detected, policy)

trustedlicenses.format_failure(failure)

Render one policy failure as a human-readable line.

Parameters:

Name Type Description Default
failure DistributionLicence

The failing distribution.

required

Returns:

Type Description
str

A description of what was detected and why it was rejected.

Source code in src/trustedlicenses/policy.py
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
def format_failure(failure: DistributionLicence) -> str:
    """Render one policy failure as a human-readable line.

    Args:
        failure: The failing distribution.

    Returns:
        A description of what was detected and why it was rejected.
    """
    if not failure.keys:
        detail = "no license detected"
    else:
        detail = (
            f"detected {', '.join(sorted(failure.keys))} "
            f"(categories: {', '.join(sorted(failure.categories)) or 'none'})"
        )
    return f"  {failure.name}: {detail} -- from {failure.source}"

trustedlicenses.format_remediation(failure)

Suggest a policy change that would let one failure pass.

Parameters:

Name Type Description Default
failure DistributionLicence

The failing distribution.

required

Returns:

Type Description
str

A one-line suggestion: add the failure's own categories to

str

allowed-categories, or its name to ignored-packages. When nothing was

str

detected at all but a free-text correction was found, points at trusting that

str

instead (interactively, or via policy config) -- see :func:format_suggestion.

str

When nothing was detected and no correction applies either, points at manual

str

verification.

Source code in src/trustedlicenses/policy.py
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
def format_remediation(failure: DistributionLicence) -> str:
    """Suggest a policy change that would let one failure pass.

    Args:
        failure: The failing distribution.

    Returns:
        A one-line suggestion: add the failure's own categories to
        ``allowed-categories``, or its name to ``ignored-packages``. When nothing was
        detected at all but a free-text correction was found, points at trusting that
        instead (interactively, or via policy config) -- see :func:`format_suggestion`.
        When nothing was detected and no correction applies either, points at manual
        verification.
    """
    if not failure.categories:
        if failure.suggested:
            return format_suggestion(failure)
        return (
            f'    -> no license could be detected; verify "{failure.name}" manually, '
            "then add it to ignored-packages if acceptable"
        )
    categories = ", ".join(f'"{category}"' for category in sorted(failure.categories))
    return f'    -> add {categories} to allowed-categories, or "{failure.name}" to ignored-packages, to allow this'

trustedlicenses.format_suggestion(failure)

Describe an untrusted free-text correction, and how to trust it.

Parameters:

Name Type Description Default
failure DistributionLicence

A failing distribution with a non-empty suggested.

required

Returns:

Type Description
str

A one-line hint naming what the correction looks like and the three ways to

str

trust it -- visible from a plain (non-interactive) report, not just the

str

interactive review wizard.

Source code in src/trustedlicenses/policy.py
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
def format_suggestion(failure: DistributionLicence) -> str:
    """Describe an untrusted free-text correction, and how to trust it.

    Args:
        failure: A failing distribution with a non-empty ``suggested``.

    Returns:
        A one-line hint naming what the correction looks like and the three ways to
        trust it -- visible from a plain (non-interactive) report, not just the
        interactive review wizard.
    """
    statement, spdx_id = min(failure.suggested)
    return (
        f'    -> looks like {spdx_id} from its declared metadata ("{statement}"), not trusted by default -- '
        f"review interactively, or add trust-corrected-licenses / verified-packages / "
        f"verified-statements to your policy"
    )