Module continuous_delivery_scripts.spdx_report.licence_assessment

Explainable, directional licence-risk screening for third-party reports.

Rules are data, not legal conclusions. The embedded TOML file can be reviewed and selectively overridden by a project without changing this evaluator.

Functions

def verified_spdx_licence_expression(value: str | None) ‑> str | None
Expand source code
def verified_spdx_licence_expression(value: Optional[str]) -> Optional[str]:
    """Extract a verified SPDX choice without treating exemption prose as a licence.

    Accept a complete expression, or an explicit 'either X or Y' choice at
    the end of a manual review explanation. Match spelled-out licence names
    exactly; fuzzy matching could silently select unrelated licence terms.
    """
    if not isinstance(value, str) or not value.strip():
        return None
    licensing = get_spdx_licensing()

    def parse_known_expression(text: str) -> Optional[str]:
        try:
            expression = licensing.parse(normalise_proprietary_licence(text), strict=True)
        except (ExpressionError, ValueError):
            return None
        if expression is None:
            return None
        for symbol in expression.symbols:
            if isinstance(symbol, LicenseWithExceptionSymbol):
                if symbol.exception_symbol.key not in licensing.known_symbols:
                    return None
                identifiers = (symbol.license_symbol.key,)
            elif isinstance(symbol, LicenseSymbol):
                if symbol.is_exception:
                    return None
                identifiers = (symbol.key,)
            else:
                return None
            if any(
                identifier not in licensing.known_symbols and not re.fullmatch(r"LicenseRef-[A-Za-z0-9.-]+", identifier)
                for identifier in identifiers
            ):
                return None
        return str(expression.simplify())

    whole_expression = parse_known_expression(value)
    if whole_expression:
        return whole_expression
    choices = re.search(r"\beither\s+(.+?)\s+or\s+(.+)$", value.strip(), flags=re.IGNORECASE)
    if not choices:
        return None
    prefix = value.strip()[: choices.start()].strip()
    if prefix and (
        re.search(r"\b(?:not|never|neither|example|hypothetical)\b", prefix, flags=re.IGNORECASE)
        or not re.search(
            r"(?:\bdual[- ]licen[cs]e(?:d)?\s*[-:;,]?|\blicen[cs]ed\s+under)\s*$",
            prefix,
            flags=re.IGNORECASE,
        )
    ):
        return None
    alternatives = []
    for alternative in choices.groups():
        candidate = re.sub(r"^the\s+", "", alternative.strip(" .;"), flags=re.IGNORECASE)
        expression = parse_known_expression(candidate)
        if expression is None:
            licence = OPENSOURCE_LICENCES.get_licence(candidate, allow_fuzzy=False)
            expression = parse_known_expression(licence.identifier) if licence else None
        if expression is None:
            return None
        alternatives.append(expression)
    return parse_known_expression(f"({alternatives[0]}) OR ({alternatives[1]})")

Extract a verified SPDX choice without treating exemption prose as a licence.

Accept a complete expression, or an explicit 'either X or Y' choice at the end of a manual review explanation. Match spelled-out licence names exactly; fuzzy matching could silently select unrelated licence terms.

Classes

class LicenceAssessment (*values)
Expand source code
class LicenceAssessment(Enum):
    """Outcomes of an advisory assessment, separate from allowlist compliance."""

    ALLOW = "ALLOW"
    REVIEW = "REVIEW"
    MANUALLY_REVIEWED = "MANUALLY_REVIEWED"
    DENY = "DENY"
    UNKNOWN = "UNKNOWN"

Outcomes of an advisory assessment, separate from allowlist compliance.

Ancestors

  • enum.Enum

Class variables

var ALLOW

The type of the None singleton.

var DENY

The type of the None singleton.

var MANUALLY_REVIEWED

The type of the None singleton.

var REVIEW

The type of the None singleton.

var UNKNOWN

The type of the None singleton.

class LicenceAssessmentPolicy (default: dict,
override: dict | None = None,
inline_override: dict | None = None)
Expand source code
class LicenceAssessmentPolicy:
    """Packaged screening rules with optional file and inline project overlays."""

    def __init__(self, default: dict, override: Optional[dict] = None, inline_override: Optional[dict] = None) -> None:
        """Validate and merge classifications, rules and package exceptions."""
        self.classifications: Dict[str, LicenceCategory] = {}
        self.scancode_categories: Dict[str, LicenceCategory] = {}
        self.rules: Dict[str, _Rule] = {}
        self.packages: Dict[str, _PackageOverride] = {}
        self.fail_on: Tuple[LicenceAssessment, ...] = ()
        for data, source in ((default, "built-in"), (override, "project"), (inline_override, "pyproject.toml")):
            if data is None:
                continue
            self._apply(data, source)

    @classmethod
    def from_config(cls) -> "LicenceAssessmentPolicy":
        """Load packaged defaults, an optional project file, then inline rules."""
        resource = resources.files("continuous_delivery_scripts.spdx_report").joinpath(
            "data", "licence_assessment.toml"
        )
        default = toml.loads(resource.read_text(encoding="utf8"))
        override_path = configuration.get_value_or_default(ConfigurationVariable.LICENCE_ASSESSMENT_RULES_PATH, None)
        override = toml.load(Path(override_path)) if override_path else None
        inline_override = configuration.get_value_or_default(ConfigurationVariable.LICENCE_ASSESSMENT_RULES, None)
        policy = cls(default, override, inline_override)
        fail_on = configuration.get_value_or_default(ConfigurationVariable.LICENCE_ASSESSMENT_FAIL_ON, None)
        if fail_on is not None:
            policy._set_fail_on(fail_on, "ProjectConfig.LICENCE_ASSESSMENT_FAIL_ON")
        return policy

    def _set_fail_on(self, values: Any, source: str) -> None:
        """Validate the same status list for inline, file and top-level settings."""
        if not isinstance(values, list) or any(not isinstance(value, str) for value in values):
            raise ValueError(f"{source}: fail_on must be a list of assessment statuses")
        self.fail_on = tuple(_require_status(value, f"{source} fail_on") for value in values)
        if LicenceAssessment.ALLOW in self.fail_on:
            raise ValueError(f"{source}: ALLOW cannot be a failing assessment status")

    def _apply(self, data: dict, source: str) -> None:
        if not isinstance(data, dict) or set(data) - {
            "schema_version",
            "settings",
            "classifications",
            "scancode_categories",
            "rules",
            "packages",
        }:
            raise ValueError(f"{source}: unexpected licence assessment policy fields")
        if data.get("schema_version") != 1:
            raise ValueError(f"{source}: licence assessment policy requires schema_version = 1")
        settings = data.get("settings", {})
        if not isinstance(settings, dict) or set(settings) - {"fail_on"}:
            raise ValueError(f"{source}: unknown licence assessment settings")
        if "fail_on" in settings:
            self._set_fail_on(settings["fail_on"], source)
        classifications = data.get("classifications", {})
        if not isinstance(classifications, dict):
            raise ValueError(f"{source}: classifications must be a table")
        for identifier, category in classifications.items():
            self.classifications[_require_text(identifier, "Licence identifier")] = _require_category(
                category, identifier
            )
        categories = data.get("scancode_categories", {})
        if not isinstance(categories, dict):
            raise ValueError(f"{source}: scancode_categories must be a table")
        for category_name, classification in categories.items():
            self.scancode_categories[_require_text(category_name, "ScanCode category")] = _require_category(
                classification, category_name
            )
        entries = data.get("rules", [])
        if not isinstance(entries, list):
            raise ValueError(f"{source}: rules must be an array of tables")
        parsed_rules = [_parse_rule(entry, source) for entry in entries]
        if len({rule.id for rule in parsed_rules}) != len(parsed_rules):
            raise ValueError(f"{source}: duplicate licence assessment rule IDs")
        self.rules.update((rule.id, rule) for rule in parsed_rules)
        self.packages.update(_parse_packages(data.get("packages", []), source))

Packaged screening rules with optional file and inline project overlays.

Validate and merge classifications, rules and package exceptions.

Static methods

def from_config() ‑> LicenceAssessmentPolicy

Load packaged defaults, an optional project file, then inline rules.

class LicenceAssessmentResult (status: LicenceAssessment,
project_licence: str,
dependency_licence: str,
reason: str,
rule: str,
source: str,
selected_licence: str | None = None,
automatic_status: LicenceAssessment | None = None,
manual_review_reason: str = '',
scancode_licences: Tuple[ScanCodeLicenceInfo, ...] = (),
discovered_licence: str | None = None,
assessed_licence_source: str = 'discovered metadata')
Expand source code
@dataclass(frozen=True)
class LicenceAssessmentResult:
    """A reportable, auditable assessment of one dependency."""

    status: LicenceAssessment
    project_licence: str
    dependency_licence: str
    reason: str
    rule: str
    source: str
    selected_licence: Optional[str] = None
    automatic_status: Optional[LicenceAssessment] = None
    manual_review_reason: str = ""
    scancode_licences: Tuple[ScanCodeLicenceInfo, ...] = ()
    discovered_licence: Optional[str] = None
    assessed_licence_source: str = "discovered metadata"

    def as_report(self) -> Dict[str, Any]:
        """Return JSON- and template-friendly values."""
        return {
            "status": self.status.value,
            "project_licence": self.project_licence,
            "dependency_licence": self.dependency_licence,
            "discovered_licence": self.discovered_licence or self.dependency_licence,
            "assessed_licence_source": self.assessed_licence_source,
            "reason": self.reason,
            "rule": self.rule,
            "source": self.source,
            "selected_licence": self.selected_licence or "",
            "automatic_status": (self.automatic_status or self.status).value,
            "manual_review": {
                "reviewed": self.status is LicenceAssessment.MANUALLY_REVIEWED,
                "reason": self.manual_review_reason,
            },
            "scancode_licences": [
                {"identifier": info.identifier, "category": info.category, "url": info.url}
                for info in self.scancode_licences
            ],
        }

A reportable, auditable assessment of one dependency.

Instance variables

var assessed_licence_source : str

The type of the None singleton.

var automatic_status : LicenceAssessment | None

The type of the None singleton.

var dependency_licence : str

The type of the None singleton.

var discovered_licence : str | None

The type of the None singleton.

var manual_review_reason : str

The type of the None singleton.

var project_licence : str

The type of the None singleton.

var reason : str

The type of the None singleton.

var rule : str

The type of the None singleton.

var scancode_licences : Tuple[ScanCodeLicenceInfo, ...]

The type of the None singleton.

var selected_licence : str | None

The type of the None singleton.

var source : str

The type of the None singleton.

var status : LicenceAssessment

The type of the None singleton.

Methods

def as_report(self) ‑> Dict[str, Any]
Expand source code
def as_report(self) -> Dict[str, Any]:
    """Return JSON- and template-friendly values."""
    return {
        "status": self.status.value,
        "project_licence": self.project_licence,
        "dependency_licence": self.dependency_licence,
        "discovered_licence": self.discovered_licence or self.dependency_licence,
        "assessed_licence_source": self.assessed_licence_source,
        "reason": self.reason,
        "rule": self.rule,
        "source": self.source,
        "selected_licence": self.selected_licence or "",
        "automatic_status": (self.automatic_status or self.status).value,
        "manual_review": {
            "reviewed": self.status is LicenceAssessment.MANUALLY_REVIEWED,
            "reason": self.manual_review_reason,
        },
        "scancode_licences": [
            {"identifier": info.identifier, "category": info.category, "url": info.url}
            for info in self.scancode_licences
        ],
    }

Return JSON- and template-friendly values.

class LicenceAssessor (policy: LicenceAssessmentPolicy,
lookup_scancode: bool = False)
Expand source code
class LicenceAssessor:
    """Screen an SPDX dependency expression against a project licence."""

    def __init__(self, policy: LicenceAssessmentPolicy, lookup_scancode: bool = False) -> None:
        """Use the same policy for reports and optional CI gating."""
        self.policy = policy
        self._licensing = get_spdx_licensing()
        self.manual_reviews = _load_manual_reviews()
        self._scancode = ScanCodeLicenceDB() if lookup_scancode else None
        self._scancode_references: Dict[str, ScanCodeLicenceInfo] = {}

    def _lookup_info(self, identifier: str) -> Optional[ScanCodeLicenceInfo]:
        """Consult the exact SPDX key only when lookup is explicitly enabled."""
        if self._scancode is None:
            return None
        info = self._scancode.find(identifier)
        if info:
            self._scancode_references[identifier] = info
        return info

    def _category_for(self, identifier: str) -> Optional[LicenceCategory]:
        """Prefer reviewed project and embedded classifications over LicenseDB."""
        category = self.policy.classifications.get(identifier)
        if category is not None:
            return category
        info = self._lookup_info(identifier)
        return self.policy.scancode_categories.get(info.category) if info else None

    def _attach_lookup_references(
        self, result: LicenceAssessmentResult, project: str, dependency: str
    ) -> LicenceAssessmentResult:
        """Preserve where externally obtained licence information came from."""
        if not self._scancode_references:
            return result
        identifiers = set()
        for expression in (project, dependency):
            try:
                parsed = self._licensing.parse(normalise_proprietary_licence(expression))
                for symbol in parsed.symbols:
                    if isinstance(symbol, LicenseWithExceptionSymbol):
                        identifiers.add(symbol.license_symbol.key)
                    elif isinstance(symbol, LicenseSymbol):
                        identifiers.add(symbol.key)
            except (ValueError, AttributeError):
                continue
        references = tuple(
            self._scancode_references[key] for key in sorted(identifiers & self._scancode_references.keys())
        )
        if not references:
            return result
        categories = ", ".join(f"{info.identifier}: {info.category}" for info in references)
        return replace(
            result,
            reason=(
                f"{result.reason} ScanCode LicenseDB describes {categories}; "
                "its category is not a compatibility verdict."
            ),
            source=f"{result.source} + ScanCode LicenseDB",
            scancode_licences=references,
        )

    def _result(
        self,
        status: LicenceAssessment,
        project: str,
        dependency: str,
        reason: str,
        rule: str,
        source: str = "built-in",
        selected: Optional[str] = None,
    ) -> LicenceAssessmentResult:
        return LicenceAssessmentResult(status, project, dependency, reason, rule, source, selected)

    def assess(
        self,
        project_licence: str,
        dependency_licence: str,
        dependency_name: str = "",
        dependency_version: str = "",
        project_unknown: bool = False,
        dependency_unknown: bool = False,
        verified_licence: Optional[str] = None,
    ) -> LicenceAssessmentResult:
        """Keep the automatic finding while recording a matching manual review."""
        manual_licence = verified_spdx_licence_expression(verified_licence)
        assessed_licence = dependency_licence
        automatic = self._assess_automatic(
            project_licence,
            dependency_licence,
            dependency_name,
            dependency_version,
            project_unknown,
            dependency_unknown,
        )
        if automatic.status is LicenceAssessment.UNKNOWN and automatic.rule != "project-unknown" and manual_licence:
            assessed_licence = manual_licence
            automatic = self._assess_automatic(
                project_licence, assessed_licence, dependency_name, dependency_version, project_unknown, False
            )
            source_details = (
                "the discovered licence was unknown"
                if dependency_unknown
                else (
                    "the discovered licence could not be assessed reliably; "
                    "verify any obligations omitted by the manual value"
                )
            )
            automatic = replace(
                automatic,
                discovered_licence=dependency_licence,
                assessed_licence_source="manual licence review",
                reason=(
                    f"Assessed using manually verified {manual_licence} because {source_details}. " + automatic.reason
                ),
            )
        elif automatic.status is LicenceAssessment.UNKNOWN and verified_licence and not manual_licence:
            automatic = replace(
                automatic,
                reason=automatic.reason + " The manual review record is not a recognised SPDX licence expression.",
            )
        automatic = self._attach_lookup_references(automatic, project_licence, assessed_licence)
        review = self.manual_reviews.get(dependency_name)
        if (
            automatic.status is LicenceAssessment.REVIEW
            and review
            and review.matches(assessed_licence, dependency_version)
        ):
            return replace(
                automatic,
                status=LicenceAssessment.MANUALLY_REVIEWED,
                automatic_status=LicenceAssessment.REVIEW,
                manual_review_reason=review.reason,
            )
        return automatic

    def _assess_automatic(
        self,
        project_licence: str,
        dependency_licence: str,
        dependency_name: str,
        dependency_version: str,
        project_unknown: bool,
        dependency_unknown: bool,
    ) -> LicenceAssessmentResult:
        """Return a conservative, directional result without invoking licence discovery."""
        project = project_licence or "Unknown"
        dependency = dependency_licence or "Unknown"
        if project_unknown or project.casefold() in ("unknown", "none", "noassertion"):
            return self._result(
                LicenceAssessment.UNKNOWN,
                project,
                dependency,
                "Project licence is not reliably known.",
                "project-unknown",
            )
        matching = [
            item
            for item in self.policy.packages.values()
            if item.name.casefold() == dependency_name.casefold()
            and (item.version is None or item.version == dependency_version)
        ]
        if matching:
            most_specific = sorted(matching, key=lambda item: item.version is not None, reverse=True)
            if len(most_specific) > 1 and most_specific[0].version == most_specific[1].version:
                raise ValueError(f"Conflicting licence assessment overrides for {dependency_name}")
            override = most_specific[0]
            return self._result(override.status, project, dependency, override.reason, override.id, "project")
        if dependency_unknown or dependency.casefold() in ("unknown", "none", "noassertion"):
            return self._result(
                LicenceAssessment.UNKNOWN,
                project,
                dependency,
                "Dependency licence is not reliably known.",
                "dependency-unknown",
            )
        try:
            project_node = self._licensing.parse(normalise_proprietary_licence(project))
            dependency_node = self._licensing.parse(normalise_proprietary_licence(dependency))
            if project_node is None or dependency_node is None:
                raise ValueError("Empty SPDX expression")
        except Exception:
            return self._result(
                LicenceAssessment.UNKNOWN,
                project,
                dependency,
                "Licence expression cannot be parsed.",
                "expression-invalid",
            )
        project_name = str(project_node)
        project_category = self._category_for(project_node.key) if isinstance(project_node, LicenseSymbol) else None
        if project_category is LicenceCategory.UNKNOWN:
            return self._result(
                LicenceAssessment.UNKNOWN,
                project,
                dependency,
                "The project licence is classified as unknown.",
                "project-unknown",
            )
        return self._assess_node(project, dependency, project_name, project_category, dependency_node)

    def _matching_rule(
        self,
        project: str,
        dependency: str,
        project_category: Optional[LicenceCategory],
        dependency_category: Optional[LicenceCategory],
    ) -> Optional[_Rule]:
        matching = [
            rule
            for rule in self.policy.rules.values()
            if rule.matches(
                project,
                dependency,
                project_category.value if project_category else None,
                dependency_category.value if dependency_category else "UNKNOWN",
            )
        ]
        if not matching:
            return None
        matching.sort(key=lambda rule: rule.specificity, reverse=True)
        if len(matching) > 1 and matching[0].specificity == matching[1].specificity:
            raise ValueError(f"Conflicting licence assessment rules: {matching[0].id} and {matching[1].id}")
        return matching[0]

    def _assess_node(
        self, project: str, dependency: str, project_name: str, project_category: Optional[LicenceCategory], node: Any
    ) -> LicenceAssessmentResult:
        name = str(node)
        category = self._category_for(node.key) if isinstance(node, LicenseSymbol) else None
        rule = self._matching_rule(project_name, name, project_category, category)
        if rule:
            return self._result(rule.status, project, dependency, rule.reason, rule.id, rule.source, name)
        if isinstance(node, LicenseSymbol) and category is not None:
            self._lookup_info(node.key)
        if category is LicenceCategory.UNKNOWN:
            return self._result(
                LicenceAssessment.UNKNOWN,
                project,
                dependency,
                f"The dependency licence {name} is classified as unknown.",
                "dependency-unknown",
                selected=name,
            )
        if not project_category:
            return self._result(
                LicenceAssessment.REVIEW,
                project,
                dependency,
                "Project licence is unclassified or offers multiple terms; "
                "determine the applicable terms before comparison.",
                "project-needs-review",
            )
        if isinstance(node, OR):
            branches = [
                self._assess_node(project, dependency, project_name, project_category, part) for part in node.args
            ]
            for status in (
                LicenceAssessment.ALLOW,
                LicenceAssessment.REVIEW,
                LicenceAssessment.UNKNOWN,
                LicenceAssessment.DENY,
            ):
                selected = next((branch for branch in branches if branch.status is status), None)
                if selected:
                    return self._result(
                        status,
                        project,
                        dependency,
                        f"An OR choice of {selected.selected_licence or name} is {status.value}: {selected.reason}",
                        "expression-or",
                        source=selected.source,
                        selected=selected.selected_licence,
                    )
        if isinstance(node, AND):
            branches = [
                self._assess_node(project, dependency, project_name, project_category, part) for part in node.args
            ]
            for status in (
                LicenceAssessment.DENY,
                LicenceAssessment.UNKNOWN,
                LicenceAssessment.REVIEW,
                LicenceAssessment.ALLOW,
            ):
                if any(branch.status is status for branch in branches):
                    source = "project" if any(branch.source == "project" for branch in branches) else "built-in"
                    return self._result(
                        status,
                        project,
                        dependency,
                        "All AND obligations apply: "
                        + "; ".join(f"{branch.selected_licence}: {branch.reason}" for branch in branches),
                        "expression-and",
                        source=source,
                        selected=name,
                    )
        if isinstance(node, LicenseWithExceptionSymbol):
            self._lookup_info(node.license_symbol.key)
            return self._result(
                LicenceAssessment.REVIEW,
                project,
                dependency,
                f"The exception in {name} has not been reviewed in the configured policy.",
                "exception-needs-review",
                selected=name,
            )
        if isinstance(node, LicenseSymbol):
            if name.startswith("LicenseRef-"):
                reason = f"The terms of the project-defined licence reference {name} need human review."
                status = LicenceAssessment.REVIEW
                identifier = "licence-reference-needs-review"
            elif category is not None:
                reason = f"No directional assessment rule is defined for {name}; review its obligations."
                status = LicenceAssessment.REVIEW
                identifier = "directional-rule-missing"
            else:
                reason = f"No classification or directional rule is available for {name}."
                status = LicenceAssessment.UNKNOWN
                identifier = "licence-unclassified"
            return self._result(status, project, dependency, reason, identifier, selected=name)
        return self._result(
            LicenceAssessment.UNKNOWN, project, dependency, "Unsupported SPDX expression.", "expression-unsupported"
        )

Screen an SPDX dependency expression against a project licence.

Use the same policy for reports and optional CI gating.

Methods

def assess(self,
project_licence: str,
dependency_licence: str,
dependency_name: str = '',
dependency_version: str = '',
project_unknown: bool = False,
dependency_unknown: bool = False,
verified_licence: str | None = None) ‑> LicenceAssessmentResult
Expand source code
def assess(
    self,
    project_licence: str,
    dependency_licence: str,
    dependency_name: str = "",
    dependency_version: str = "",
    project_unknown: bool = False,
    dependency_unknown: bool = False,
    verified_licence: Optional[str] = None,
) -> LicenceAssessmentResult:
    """Keep the automatic finding while recording a matching manual review."""
    manual_licence = verified_spdx_licence_expression(verified_licence)
    assessed_licence = dependency_licence
    automatic = self._assess_automatic(
        project_licence,
        dependency_licence,
        dependency_name,
        dependency_version,
        project_unknown,
        dependency_unknown,
    )
    if automatic.status is LicenceAssessment.UNKNOWN and automatic.rule != "project-unknown" and manual_licence:
        assessed_licence = manual_licence
        automatic = self._assess_automatic(
            project_licence, assessed_licence, dependency_name, dependency_version, project_unknown, False
        )
        source_details = (
            "the discovered licence was unknown"
            if dependency_unknown
            else (
                "the discovered licence could not be assessed reliably; "
                "verify any obligations omitted by the manual value"
            )
        )
        automatic = replace(
            automatic,
            discovered_licence=dependency_licence,
            assessed_licence_source="manual licence review",
            reason=(
                f"Assessed using manually verified {manual_licence} because {source_details}. " + automatic.reason
            ),
        )
    elif automatic.status is LicenceAssessment.UNKNOWN and verified_licence and not manual_licence:
        automatic = replace(
            automatic,
            reason=automatic.reason + " The manual review record is not a recognised SPDX licence expression.",
        )
    automatic = self._attach_lookup_references(automatic, project_licence, assessed_licence)
    review = self.manual_reviews.get(dependency_name)
    if (
        automatic.status is LicenceAssessment.REVIEW
        and review
        and review.matches(assessed_licence, dependency_version)
    ):
        return replace(
            automatic,
            status=LicenceAssessment.MANUALLY_REVIEWED,
            automatic_status=LicenceAssessment.REVIEW,
            manual_review_reason=review.reason,
        )
    return automatic

Keep the automatic finding while recording a matching manual review.

class LicenceCategory (*values)
Expand source code
class LicenceCategory(Enum):
    """Broad screening categories, not a substitute for individual licence terms."""

    PERMISSIVE = "PERMISSIVE"
    WEAK_COPYLEFT = "WEAK_COPYLEFT"
    STRONG_COPYLEFT = "STRONG_COPYLEFT"
    NETWORK_COPYLEFT = "NETWORK_COPYLEFT"
    PUBLIC_DOMAIN = "PUBLIC_DOMAIN"
    PROPRIETARY = "PROPRIETARY"
    UNKNOWN = "UNKNOWN"

Broad screening categories, not a substitute for individual licence terms.

Ancestors

  • enum.Enum

Class variables

var NETWORK_COPYLEFT

The type of the None singleton.

var PERMISSIVE

The type of the None singleton.

var PROPRIETARY

The type of the None singleton.

var PUBLIC_DOMAIN

The type of the None singleton.

var STRONG_COPYLEFT

The type of the None singleton.

var UNKNOWN

The type of the None singleton.

var WEAK_COPYLEFT

The type of the None singleton.