"""
Provide an API that lets code express arbitrary requirements.
"""
from __future__ import annotations
from abc import abstractmethod
from textwrap import indent
from typing import cast, Any, TYPE_CHECKING, final
from typing_extensions import override
from betty.error import UserFacingError
from betty.locale.localizable import _, Localizable
from betty.locale.localized import LocalizedStr
if TYPE_CHECKING:
from collections.abc import Sequence, MutableSequence
from betty.locale.localizer import Localizer
[docs]
class Requirement(Localizable):
"""
Express a requirement.
"""
[docs]
@abstractmethod
def is_met(self) -> bool:
"""
Check if the requirement is met.
"""
pass
[docs]
def assert_met(self) -> None:
"""
Assert that the requirement is met.
"""
if not self.is_met():
raise RequirementError(self)
return None
[docs]
@abstractmethod
def summary(self) -> Localizable:
"""
Get the requirement's human-readable summary.
"""
pass
[docs]
def details(self) -> Localizable | None:
"""
Get the requirement's human-readable additional details.
"""
return None
[docs]
def reduce(self) -> Requirement | None:
"""
Remove unnecessary components of this requirement.
- Collections can flatten unnecessary hierarchies.
- Empty decorators or collections can 'dissolve' themselves and return None.
This function MUST NOT modify self.
"""
return self
[docs]
@final
class RequirementError(UserFacingError, RuntimeError):
"""
Raised when a requirement is not met.
"""
[docs]
@override
def reduce(self) -> Requirement | None:
reduced_requirements: MutableSequence[Requirement] = []
for requirement in self._requirements:
reduced_requirement = requirement.reduce()
if reduced_requirement:
if type(reduced_requirement) is type(self):
reduced_requirements.extend(
cast(RequirementCollection, reduced_requirement)._requirements
)
else:
reduced_requirements.append(reduced_requirement)
if len(reduced_requirements) == 1:
return reduced_requirements[0]
if reduced_requirements:
return type(self)(*reduced_requirements)
return None
[docs]
@final
class AnyRequirement(RequirementCollection):
"""
A requirement that is met if any of the given requirements are met.
"""
[docs]
def __init__(self, *requirements: Requirement | None):
super().__init__(*requirements)
self._summary = _("One or more of these requirements must be met")
[docs]
@override
def is_met(self) -> bool:
return any(requirement.is_met() for requirement in self._requirements)
[docs]
class AllRequirements(RequirementCollection):
"""
A requirement that is met if all of the given requirements are met.
"""
[docs]
def __init__(self, *requirements: Requirement | None):
super().__init__(*requirements)
self._summary = _("All of these requirements must be met")
[docs]
@override
def is_met(self) -> bool:
return all(requirement.is_met() for requirement in self._requirements)