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]