__init__.py
1 """Plugins submodule.""" 2 3 from __future__ import annotations 4 5 from collections import namedtuple 6 from typing import TYPE_CHECKING, Any, Sequence 7 8 from archan.enums import ResultCode 9 from archan.logging import Logger 10 from archan.printing import PrintableArgumentMixin, PrintableNameMixin, PrintablePluginMixin 11 12 if TYPE_CHECKING: 13 from archan.dsm import DesignStructureMatrix, DomainMappingMatrix, MultipleDomainMatrix 14 15 logger = Logger.get_logger(__name__) 16 17 18 class Argument(PrintableArgumentMixin): 19 """Placeholder for name, class, description and default value.""" 20 21 def __init__(self, name: str, cls: type, description: str, default: Any | None = None): 22 """Initialization method. 23 24 Parameters: 25 name: Name of the argument. 26 cls: Type of the argument. 27 description: Description of the argument. 28 default: Default value for the argument. 29 """ 30 self.name = name 31 self.cls = cls 32 self.description = description 33 self.default = default 34 35 def __str__(self): 36 return f" {self.name} ({self.cls}, default {self.default}): {self.description}" 37 38 39 # TODO: also add some "expect" attribute to describe the expected data format 40 class Checker(PrintableNameMixin, PrintablePluginMixin): 41 """Checker class. 42 43 An instance of Checker implements a check method that analyzes an instance 44 of DSM/DMM/MDM and return a true or false value, with optional message. 45 """ 46 47 identifier = "" 48 name = "" 49 description = "" 50 hint = "" 51 argument_list: Sequence[Argument] = () 52 53 Code = ResultCode 54 55 def __init__( 56 self, 57 name: str | None = None, 58 description: str | None = None, 59 hint: str | None = None, 60 allow_failure: bool = False, # noqa: FBT001, FBT002 61 passes: Any | None = None, 62 arguments: dict | None = None, 63 ): 64 """Initialization method. 65 66 Parameters: 67 name: The checker name. 68 description: The checker description. 69 hint: Hint provided for failures. 70 allow_failure: Still pass if failed or not. 71 passes: Boolean. 72 arguments: Arguments passed to the check method when run. 73 """ 74 if name: 75 self.name = name 76 if description: 77 self.description = description 78 if hint: 79 self.hint = hint 80 81 self.allow_failure = allow_failure 82 self.passes = passes 83 self.arguments = arguments or {} 84 self.result = None 85 86 def check( 87 self, 88 dsm: DesignStructureMatrix | MultipleDomainMatrix | DomainMappingMatrix, 89 **kwargs: Any, 90 ) -> tuple[Any, str]: 91 """Check the data and return a result. 92 93 Parameters: 94 dsm: DSM/DMM/MDM instance to check. 95 **kwargs: Additional arguments. 96 97 Returns: 98 result: Checker constant or object with a ``__bool__`` method. 99 message: Optional messages. 100 """ 101 raise NotImplementedError 102 103 def run(self, data: DesignStructureMatrix | MultipleDomainMatrix | DomainMappingMatrix) -> None: 104 """Run the check method and format the result for analysis. 105 106 Parameters: 107 data: DSM/DMM/MDM instance to check. 108 """ 109 result_type = namedtuple("Result", "code messages") # type: ignore[name-match] # noqa: PYI024 110 111 if self.passes is True: 112 result = result_type(Checker.Code.PASSED, "") 113 elif self.passes is False: 114 result = ( 115 result_type(Checker.Code.IGNORED, "") if self.allow_failure else result_type(Checker.Code.FAILED, "") 116 ) 117 else: 118 try: 119 result = self.check(data, **self.arguments) # type: ignore[assignment] 120 except NotImplementedError: 121 result = result_type(Checker.Code.NOT_IMPLEMENTED, "") 122 else: 123 messages = "" 124 if isinstance(result, tuple): 125 result, messages = result 126 127 if result not in Checker.Code: 128 result = Checker.Code.PASSED if bool(result) else Checker.Code.FAILED # type: ignore[assignment] 129 130 if result == Checker.Code.FAILED and self.allow_failure: 131 result = Checker.Code.IGNORED # type: ignore[assignment] 132 133 result = result_type(result, messages) 134 self.result = result # type: ignore[assignment] 135 136 137 class Provider(PrintableNameMixin, PrintablePluginMixin): 138 """Provider class. 139 140 An instance of provider implements a get_data method that returns an 141 instance of DSM/DMM/MDM to be checked by an instance of Checker. 142 """ 143 144 identifier = "" 145 name = "" 146 description = "" 147 argument_list: tuple[Argument, ...] = () 148 149 def __init__( 150 self, 151 name: str | None = None, 152 description: str | None = None, 153 arguments: dict | None = None, 154 ) -> None: 155 """Initialization method. 156 157 Parameters: 158 name: The provider name. 159 description: The provider description. 160 arguments: Arguments that will be used for `get_data` method. 161 """ 162 if name: 163 self.name = name 164 if description: 165 self.description = description 166 167 self.arguments = arguments or {} 168 self.data = None 169 170 def get_data(self, **kwargs: Any) -> Any: 171 """Abstract method. Return instance of DSM/DMM/MDM. 172 173 Parameters: 174 **kwargs: Keyword arguments. 175 176 Raises: 177 NotImplementedError: This method must be implemented in subclasses. 178 """ 179 raise NotImplementedError 180 181 def run(self) -> None: 182 """Run the get_data method with run arguments, store the result.""" 183 self.data = self.get_data(**self.arguments)