Source code for rocrate_validator.models.severity

# Copyright (c) 2024-2026 CRS4
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

import enum
import inspect
from dataclasses import dataclass
from functools import total_ordering

from enum_tools.documentation import document_enum


[docs] @enum.unique @document_enum @total_ordering class Severity(enum.Enum): """ Enum ordering "strength" of conditions to be verified """ #: the condition is not mandatory OPTIONAL = 0 #: the condition is recommended RECOMMENDED = 2 #: the condition is mandatory REQUIRED = 4 def __lt__(self, other: object) -> bool: if isinstance(other, Severity): return self.value < other.value raise TypeError(f"Comparison not supported between instances of {type(self)} and {type(other)}") @staticmethod def get(name: str) -> Severity: return getattr(Severity, name.upper())
[docs] @total_ordering @dataclass class RequirementLevel: """ Represents a requirement level. A requirement has a name and a severity level of type :class:`.Severity`. It implements the comparison operators to allow ordering of the requirement levels. """ name: str severity: Severity def __eq__(self, other: object) -> bool: if not isinstance(other, RequirementLevel): return False return self.name == other.name and self.severity == other.severity def __lt__(self, other: object) -> bool: # NOTE: this ordering is not totally coherent, since for two objects a and b # with equal Severity but different names you would have # not a < b, which implies a >= b # and also a != b and not a > b, which is incoherent with a >= b if not isinstance(other, RequirementLevel): raise TypeError(f"Cannot compare {type(self)} with {type(other)}") return self.severity < other.severity def __hash__(self) -> int: return hash((self.name, self.severity)) def __repr__(self) -> str: return f"RequirementLevel(name={self.name}, severity={self.severity})" def __str__(self) -> str: return self.name def __int__(self) -> int: return self.severity.value def __index__(self) -> int: return self.severity.value
[docs] class LevelCollection: """ Collection of :class:`.RequirementLevel` instances. Provides a set of predefined RequirementLevel instances that can be used to define the severity of a requirement. They map the keywords defined in **RFC 2119** to the corresponding severity levels. .. note:: The keywords **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY**, and **OPTIONAL** in this document are to be interpreted as described in **RFC 2119**. """ #: The requirement level OPTIONAL is mapped to the OPTIONAL severity level OPTIONAL = RequirementLevel("OPTIONAL", Severity.OPTIONAL) #: The requirement level MAY is mapped to the OPTIONAL severity level MAY = RequirementLevel("MAY", Severity.OPTIONAL) #: The requirement level REQUIRED is mapped to the REQUIRED severity level REQUIRED = RequirementLevel("REQUIRED", Severity.REQUIRED) #: The requirement level SHOULD is mapped to the RECOMMENDED severity level SHOULD = RequirementLevel("SHOULD", Severity.RECOMMENDED) #: The requirement level SHOULD NOT is mapped to the RECOMMENDED severity level SHOULD_NOT = RequirementLevel("SHOULD_NOT", Severity.RECOMMENDED) #: The requirement level RECOMMENDED is mapped to the RECOMMENDED severity level RECOMMENDED = RequirementLevel("RECOMMENDED", Severity.RECOMMENDED) #: The requirement level MUST is mapped to the REQUIRED severity level MUST = RequirementLevel("MUST", Severity.REQUIRED) #: The requirement level MUST_NOT is mapped to the REQUIRED severity level MUST_NOT = RequirementLevel("MUST_NOT", Severity.REQUIRED) #: The requirement level SHALL is mapped to the REQUIRED severity level SHALL = RequirementLevel("SHALL", Severity.REQUIRED) #: The requirement level SHALL_NOT is mapped to the REQUIRED severity level SHALL_NOT = RequirementLevel("SHALL_NOT", Severity.REQUIRED) def __init__(self): raise NotImplementedError(f"{type(self)} can't be instantiated") @staticmethod def all() -> list[RequirementLevel]: return [ level for name, level in inspect.getmembers(LevelCollection) if not inspect.isroutine(level) and not inspect.isdatadescriptor(level) and not name.startswith("__") ] @staticmethod def get(name: str) -> RequirementLevel: try: return getattr(LevelCollection, name.upper()) except AttributeError: raise ValueError(f"Invalid RequirementLevel: {name}") from None