Source code for colander_data_converter.base.common

  1from enum import Enum
  2from typing import Dict, Any
  3
  4from pydantic import UUID4, BaseModel, model_serializer, GetCoreSchemaHandler, ValidationError
  5from pydantic_core import core_schema
  6
  7type ObjectReference = UUID4
  8"""ObjectReference is an alias for UUID4, representing a unique object identifier."""
  9
 10
[docs] 11class BasePydanticEnum(Enum): 12 """Base class for creating Pydantic-compatible enums with flexible member resolution. 13 14 This class extends Python's Enum to provide seamless integration with Pydantic models. 15 It allows enum members to be resolved from various input types including codes, 16 enum members, member values, or dictionary representations. 17 18 The enum members are expected to have a `code` attribute for string-based lookup 19 and support Pydantic model validation for dictionary inputs. 20 """ 21
[docs] 22 @classmethod 23 def __get_pydantic_core_schema__(cls, _source: Any, _handler: GetCoreSchemaHandler): 24 """Define the Pydantic core schema for enum validation and serialization. 25 26 This method configures how Pydantic should validate and serialize enum instances. 27 It sets up flexible input validation that accepts multiple input formats and 28 serializes enum members using their code attribute. 29 30 Args: 31 _source (Any): The source type being processed (unused) 32 _handler (GetCoreSchemaHandler): Core schema handler from Pydantic (unused) 33 34 Returns: 35 core_schema: A Pydantic core schema that handles JSON and Python validation 36 with custom serialization to the member's code attribute 37 """ 38 # Get the member from the enum no matter what we have as input. 39 # If we fail to find a matching member, it will fail. 40 # It accepts: code, enum member and enum member value as input. 41 get_member_schema = core_schema.no_info_plain_validator_function(cls._get_member) 42 43 return core_schema.json_or_python_schema( 44 json_schema=get_member_schema, 45 python_schema=get_member_schema, 46 serialization=core_schema.plain_serializer_function_ser_schema(lambda member: member.value.code), 47 )
48 49 @classmethod 50 def _get_member(cls, input_value: Any): 51 """Resolve an enum member from various input formats. 52 53 This method provides flexible member resolution supporting multiple input types: 54 - Direct enum member instances 55 - Enum member values (the objects stored in enum members) 56 - String codes matching the member's value.code attribute 57 - Dictionary representations that can be validated as member values 58 59 Args: 60 input_value (Any): The input to resolve to an enum member. Can be: 61 - An enum member instance 62 - A member value object 63 - A string code 64 - A dictionary representation of a member value 65 66 Returns: 67 Enum member: The resolved enum member 68 69 Raises: 70 ValueError: If the input_value cannot be resolved to any enum member 71 72 Note: 73 Dictionary validation is performed for each member during iteration, 74 which may impact performance for large enums. Consider implementing 75 a _get_value_type class method in subclasses for optimization. 76 """ 77 for member in cls: 78 # If the input is already a member or is a member value, let's use it. 79 if input_value == member or input_value == member.value: 80 return member 81 82 # If not, search for the member with input_value as code. 83 if member.value.code == input_value: 84 return member 85 86 # Try to validate the input as a model, in case the user supplied a dict 87 # representing a member. Validating during each loop is suboptimal, 88 # improve this if you care about this feature. 89 # Not easy since you can't know easily the type of you member values by 90 # default. Forcing the child to implement a _get_value_type class method 91 # would solve this. 92 try: 93 model = type(member.value).model_validate(input_value) 94 except ValidationError: 95 continue 96 else: 97 # Validated successfully and matches the current member. 98 if member.value == model: 99 return member 100 101 # Raise a ValueError if our search fails for Pydantic to create its proper 102 # ValidationError. 103 raise ValueError(f"Failed to convert {input_value} to a member of {cls}")
104 105
[docs] 106class Level(BaseModel): 107 """A Pydantic model representing a hierarchical level with ordering capabilities. 108 109 This class defines a level with a code, name, ordering value, and optional description. 110 It provides comparison operators based on the ordering_value field, allowing levels 111 to be sorted and compared in a meaningful hierarchy. 112 113 Example: 114 >>> level1 = Level(code="LOW", name="Low Priority", ordering_value=10) 115 >>> level2 = Level(code="HIGH", name="High Priority", ordering_value=20) 116 >>> level1 < level2 117 True 118 >>> str(level1) 119 'Low Priority' 120 """ 121 122 code: str 123 name: str 124 ordering_value: int 125 description: str | None = None 126
[docs] 127 @model_serializer 128 def ser_model(self) -> str: 129 return self.name
130 131 def __str__(self) -> str: 132 return self.name 133 134 def __le__(self, other: Any) -> bool: 135 if isinstance(other, Level): 136 return self.ordering_value <= other.ordering_value 137 return False 138 139 def __ge__(self, other: Any) -> bool: 140 if isinstance(other, Level): 141 return self.ordering_value >= other.ordering_value 142 return False 143 144 def __lt__(self, other: Any) -> bool: 145 if isinstance(other, Level): 146 return self.ordering_value < other.ordering_value 147 return False 148 149 def __gt__(self, other: Any) -> bool: 150 if isinstance(other, Level): 151 return self.ordering_value > other.ordering_value 152 return False 153 154 def __eq__(self, other: Any) -> bool: 155 if isinstance(other, Level): 156 return self.ordering_value == other.ordering_value and self.name == other.name 157 return False
158 159
[docs] 160class TlpPapLevel(BasePydanticEnum): 161 """Traffic Light Protocol (TLP) and Permissible Actions Protocol (PAP) classification levels. 162 163 The TLP is a set of designations used to ensure that sensitive information is shared 164 with the appropriate audience. PAP complements TLP by providing guidance on what 165 actions can be taken with the information. 166 167 Note: 168 See `FIRST TLP Standard <https://www.first.org/tlp/>`_ for complete specification. 169 170 Example: 171 >>> level = TlpPapLevel.RED 172 >>> print(level) 173 RED 174 >>> str(level) == "RED" 175 True 176 """ 177 178 RED = Level(name="RED", code="RED", ordering_value=40) 179 """Highly sensitive information, restricted to specific recipients.""" 180 181 AMBER = Level(name="AMBER", code="AMBER", ordering_value=30) 182 """Sensitive information, limited to a defined group.""" 183 184 GREEN = Level(name="GREEN", code="GREEN", ordering_value=20) 185 """Information that can be shared within the community.""" 186 187 WHITE = Level(name="WHITE", code="WHITE", ordering_value=10) 188 """Information that can be shared publicly.""" 189
[docs] 190 @classmethod 191 def by_name(cls, name: str) -> "TlpPapLevel": 192 """Retrieve a TLP/PAP level enum member by its name. 193 194 This method provides a convenient way to access enum members using their 195 string names (e.g., "RED", "AMBER", "GREEN", "WHITE"). 196 197 Args: 198 name (str): The name of the TLP/PAP level to retrieve. Must match 199 exactly one of the enum member names: "RED", "AMBER", 200 "GREEN", or "WHITE". 201 202 Returns: 203 Level: The Level object associated with the specified enum member 204 205 Raises: 206 AttributeError: If the provided name does not correspond to any 207 enum member 208 209 Example: 210 >>> level = TlpPapLevel.by_name("RED") 211 >>> print(level.name) 212 RED 213 >>> print(level.value.ordering_value) 214 40 215 """ 216 return getattr(cls, name)
217
[docs] 218 def __str__(self): 219 """Return the string representation of the TLP level. 220 221 Returns: 222 str: The TLP level value as a string 223 """ 224 return self.value.name
225 226 def __repr__(self): 227 return self.name
228 229
[docs] 230class Singleton(type): 231 """Metaclass implementation of the Singleton design pattern. 232 233 This metaclass ensures that only one instance of a class can exist at any time. 234 Subsequent instantiation attempts will return the existing instance rather than 235 creating a new one. 236 237 Note: 238 The singleton instance is created lazily on first instantiation and persists 239 for the lifetime of the Python process. 240 241 Classes using this metaclass should be designed to handle reinitialization 242 gracefully, as ``__init__`` may be called multiple times on the same instance. 243 244 Example: 245 >>> class Configuration(metaclass=Singleton): 246 ... def __init__(self, value=None): 247 ... if not hasattr(self, 'initialized'): 248 ... self.value = value 249 ... self.initialized = True 250 ... 251 >>> config1 = Configuration(value=42) 252 >>> config2 = Configuration(value=99) 253 >>> print(config1 is config2) # Both variables point to the same instance 254 True 255 >>> print(config1.value) # The value from first initialization 256 42 257 """ 258 259 _instances: Dict[type, type] = {} 260
[docs] 261 def __call__(cls, *args, **kwargs): 262 """Control instance creation to ensure singleton behavior. 263 264 Args: 265 cls (type): The class being instantiated 266 *args: Positional arguments for class initialization 267 **kwargs: Keyword arguments for class initialization 268 269 Returns: 270 type: The singleton instance of the class 271 272 Note: 273 If an instance already exists, ``__init__`` will still be called with 274 the provided arguments, but no new instance is created. 275 """ 276 if cls not in cls._instances: 277 cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) 278 return cls._instances[cls]