GeneralManager 0.5.2__py3-none-any.whl → 0.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,11 +2,9 @@ from __future__ import annotations
2
2
  from abc import ABC, abstractmethod
3
3
  from typing import (
4
4
  Type,
5
- Generator,
6
5
  TYPE_CHECKING,
7
6
  Any,
8
7
  TypeVar,
9
- Generic,
10
8
  Iterable,
11
9
  ClassVar,
12
10
  Callable,
@@ -21,7 +19,7 @@ if TYPE_CHECKING:
21
19
  from general_manager.manager.input import Input
22
20
  from general_manager.manager.generalManager import GeneralManager
23
21
  from general_manager.manager.meta import GeneralManagerMeta
24
- from general_manager.manager.groupManager import GroupManager, GroupBucket
22
+ from general_manager.bucket.baseBucket import Bucket
25
23
 
26
24
 
27
25
  GeneralManagerType = TypeVar("GeneralManagerType", bound="GeneralManager")
@@ -73,7 +71,7 @@ class InterfaceBase(ABC):
73
71
  """
74
72
  Parses and validates input arguments into a structured identification dictionary.
75
73
 
76
- Converts positional and keyword arguments into a dictionary keyed by input field names, handling normalization of argument names and checking for unexpected or missing arguments. Processes input fields in dependency order, casting and validating each value. Raises a `TypeError` for unexpected or missing arguments and a `ValueError` if circular dependencies among input fields are detected.
74
+ Converts positional and keyword arguments into a dictionary keyed by input field names, normalizing argument names and ensuring all required fields are present. Processes input fields in dependency order, casting and validating each value. Raises a `TypeError` for unexpected or missing arguments and a `ValueError` if circular dependencies among input fields are detected.
77
75
 
78
76
  Returns:
79
77
  A dictionary mapping input field names to their validated and cast values.
@@ -134,7 +132,7 @@ class InterfaceBase(ABC):
134
132
  """
135
133
  Validates the type and allowed values of an input field.
136
134
 
137
- Checks that the provided value matches the expected type for the input field and, in debug mode, verifies that the value is among the allowed possible values if specified. Raises a TypeError for invalid types or possible value definitions, and a ValueError if the value is not permitted.
135
+ Ensures that the provided value matches the expected type for the specified input field. In debug mode, also checks that the value is among the allowed possible values if defined, supporting both callables and iterables. Raises a TypeError for invalid types or possible value definitions, and a ValueError if the value is not permitted.
138
136
  """
139
137
  input_field = self.input_fields[name]
140
138
  if not isinstance(value, input_field.type):
@@ -219,103 +217,15 @@ class InterfaceBase(ABC):
219
217
  @abstractmethod
220
218
  def getFieldType(cls, field_name: str) -> type:
221
219
  """
222
- Returns the type of the field with the given name.
220
+ Returns the type of the specified input field.
221
+
222
+ Args:
223
+ field_name: The name of the input field.
224
+
225
+ Returns:
226
+ The Python type associated with the given field name.
227
+
228
+ Raises:
229
+ NotImplementedError: This method must be implemented by subclasses.
223
230
  """
224
231
  raise NotImplementedError
225
-
226
-
227
- class Bucket(ABC, Generic[GeneralManagerType]):
228
-
229
- def __init__(self, manager_class: Type[GeneralManagerType]):
230
- self._manager_class = manager_class
231
- self._data = None
232
- self.excludes = {}
233
- self.filters = {}
234
-
235
- def __eq__(self, other: object) -> bool:
236
- if not isinstance(other, self.__class__):
237
- return False
238
- return self._data == other._data and self._manager_class == other._manager_class
239
-
240
- def __reduce__(self) -> str | tuple[Any, ...]:
241
- return (
242
- self.__class__,
243
- (None, self._manager_class, self.filters, self.excludes),
244
- )
245
-
246
- @abstractmethod
247
- def __or__(
248
- self, other: Bucket[GeneralManagerType] | GeneralManager[GeneralManagerType]
249
- ) -> Bucket[GeneralManagerType]:
250
- raise NotImplementedError
251
-
252
- @abstractmethod
253
- def __iter__(
254
- self,
255
- ) -> Generator[GeneralManagerType | GroupManager[GeneralManagerType]]:
256
- raise NotImplementedError
257
-
258
- @abstractmethod
259
- def filter(self, **kwargs: Any) -> Bucket[GeneralManagerType]:
260
- raise NotImplementedError
261
-
262
- @abstractmethod
263
- def exclude(self, **kwargs: Any) -> Bucket[GeneralManagerType]:
264
- raise NotImplementedError
265
-
266
- @abstractmethod
267
- def first(self) -> GeneralManagerType | GroupManager[GeneralManagerType] | None:
268
- raise NotImplementedError
269
-
270
- @abstractmethod
271
- def last(self) -> GeneralManagerType | GroupManager[GeneralManagerType] | None:
272
- raise NotImplementedError
273
-
274
- @abstractmethod
275
- def count(self) -> int:
276
- raise NotImplementedError
277
-
278
- @abstractmethod
279
- def all(self) -> Bucket[GeneralManagerType]:
280
- raise NotImplementedError
281
-
282
- @abstractmethod
283
- def get(
284
- self, **kwargs: Any
285
- ) -> GeneralManagerType | GroupManager[GeneralManagerType]:
286
- raise NotImplementedError
287
-
288
- @abstractmethod
289
- def __getitem__(
290
- self, item: int | slice
291
- ) -> (
292
- GeneralManagerType
293
- | GroupManager[GeneralManagerType]
294
- | Bucket[GeneralManagerType]
295
- ):
296
- raise NotImplementedError
297
-
298
- @abstractmethod
299
- def __len__(self) -> int:
300
- raise NotImplementedError
301
-
302
- @abstractmethod
303
- def __contains__(self, item: GeneralManagerType) -> bool:
304
- raise NotImplementedError
305
-
306
- @abstractmethod
307
- def sort(
308
- self,
309
- key: tuple[str] | str,
310
- reverse: bool = False,
311
- ) -> Bucket[GeneralManagerType]:
312
- raise NotImplementedError
313
-
314
- def group_by(self, *group_by_keys: str) -> GroupBucket[GeneralManagerType]:
315
- """
316
- This method groups the data by the given arguments.
317
- It returns a GroupBucket with the grouped data.
318
- """
319
- from general_manager.manager.groupManager import GroupBucket
320
-
321
- return GroupBucket(self._manager_class, group_by_keys, self)
@@ -1,19 +1,8 @@
1
1
  from __future__ import annotations
2
2
  from datetime import datetime
3
- from typing import (
4
- Any,
5
- Type,
6
- TYPE_CHECKING,
7
- Callable,
8
- Iterable,
9
- Union,
10
- Optional,
11
- Generator,
12
- List,
13
- )
3
+ from typing import Any
14
4
  from general_manager.interface.baseInterface import (
15
5
  InterfaceBase,
16
- Bucket,
17
6
  classPostCreationMethod,
18
7
  classPreCreationMethod,
19
8
  generalManagerClassName,
@@ -22,15 +11,10 @@ from general_manager.interface.baseInterface import (
22
11
  newlyCreatedGeneralManagerClass,
23
12
  newlyCreatedInterfaceClass,
24
13
  relatedClass,
25
- GeneralManagerType,
26
14
  AttributeTypedDict,
27
15
  )
28
16
  from general_manager.manager.input import Input
29
- from general_manager.auxiliary.filterParser import parse_filters
30
-
31
- if TYPE_CHECKING:
32
- from general_manager.manager.generalManager import GeneralManager
33
- from general_manager.manager.meta import GeneralManagerMeta
17
+ from general_manager.bucket.calculationBucket import CalculationBucket
34
18
 
35
19
 
36
20
  class CalculationInterface(InterfaceBase):
@@ -77,7 +61,15 @@ class CalculationInterface(InterfaceBase):
77
61
  def _preCreate(
78
62
  name: generalManagerClassName, attrs: attributes, interface: interfaceBaseClass
79
63
  ) -> tuple[attributes, interfaceBaseClass, None]:
80
- # Felder aus der Interface-Klasse sammeln
64
+
65
+ """
66
+ Prepares attributes and a new interface class before creating a GeneralManager class.
67
+
68
+ Collects all `Input` instances from the provided interface class, sets the interface type in the attributes, dynamically creates a new interface class with these input fields, and adds it to the attributes.
69
+
70
+ Returns:
71
+ A tuple containing the updated attributes dictionary, the new interface class, and None.
72
+ """
81
73
  input_fields: dict[str, Input[Any]] = {}
82
74
  for key, value in vars(interface).items():
83
75
  if key.startswith("__"):
@@ -85,7 +77,6 @@ class CalculationInterface(InterfaceBase):
85
77
  if isinstance(value, Input):
86
78
  input_fields[key] = value
87
79
 
88
- # Interface-Typ bestimmen
89
80
  attrs["_interface_type"] = interface._interface_type
90
81
  interface_cls = type(
91
82
  interface.__name__, (interface,), {"input_fields": input_fields}
@@ -118,289 +109,12 @@ class CalculationInterface(InterfaceBase):
118
109
  @classmethod
119
110
  def getFieldType(cls, field_name: str) -> type:
120
111
  """
121
- This method returns the field type for the given field name.
112
+ Returns the Python type of a specified input field.
113
+
114
+ Raises:
115
+ KeyError: If the field name does not exist in input_fields.
122
116
  """
123
117
  input = cls.input_fields.get(field_name)
124
118
  if input is None:
125
- raise ValueError(f"Field '{field_name}' not found in input fields.")
119
+ raise KeyError(f"Field '{field_name}' not found in input fields.")
126
120
  return input.type
127
-
128
-
129
- class CalculationBucket(Bucket[GeneralManagerType]):
130
- def __init__(
131
- self,
132
- manager_class: Type[GeneralManagerType],
133
- filter_definitions: Optional[dict[str, dict]] = None,
134
- exclude_definitions: Optional[dict[str, dict]] = None,
135
- sort_key: Optional[Union[str, tuple[str]]] = None,
136
- reverse: bool = False,
137
- ):
138
- from general_manager.interface.calculationInterface import (
139
- CalculationInterface,
140
- )
141
-
142
- super().__init__(manager_class)
143
-
144
- interface_class = manager_class.Interface
145
- if not issubclass(interface_class, CalculationInterface):
146
- raise TypeError(
147
- "CalculationBucket can only be used with CalculationInterface subclasses"
148
- )
149
- self.input_fields = interface_class.input_fields
150
- self.filters = {} if filter_definitions is None else filter_definitions
151
- self.excludes = {} if exclude_definitions is None else exclude_definitions
152
- self.__current_combinations = None
153
- self.sort_key = sort_key
154
- self.reverse = reverse
155
-
156
- def __reduce__(self) -> generalManagerClassName | tuple[Any, ...]:
157
- return (
158
- self.__class__,
159
- (
160
- self._manager_class,
161
- self.filters,
162
- self.excludes,
163
- self.sort_key,
164
- self.reverse,
165
- ),
166
- )
167
-
168
- def __or__(
169
- self, other: Bucket[GeneralManagerType] | GeneralManager[GeneralManagerType]
170
- ) -> CalculationBucket[GeneralManagerType]:
171
- from general_manager.manager.generalManager import GeneralManager
172
-
173
- if isinstance(other, GeneralManager) and other.__class__ == self._manager_class:
174
- return self.__or__(self.filter(id__in=[other.identification]))
175
- if not isinstance(other, self.__class__):
176
- raise ValueError("Cannot combine different bucket types")
177
- if self._manager_class != other._manager_class:
178
- raise ValueError("Cannot combine different manager classes")
179
-
180
- combined_filters = {
181
- key: value
182
- for key, value in self.filters.items()
183
- if key in other.filters and value == other.filters[key]
184
- }
185
-
186
- combined_excludes = {
187
- key: value
188
- for key, value in self.excludes.items()
189
- if key in other.excludes and value == other.excludes[key]
190
- }
191
-
192
- return CalculationBucket(
193
- self._manager_class,
194
- combined_filters,
195
- combined_excludes,
196
- )
197
-
198
- def __str__(self) -> str:
199
- PRINT_MAX = 5
200
- combinations = self.generate_combinations()
201
- prefix = f"CalculationBucket ({len(combinations)})["
202
- main = ",".join(
203
- [
204
- f"{self._manager_class.__name__}(**{comb})"
205
- for comb in combinations[:PRINT_MAX]
206
- ]
207
- )
208
- sufix = f"]"
209
- if len(combinations) > PRINT_MAX:
210
- sufix = f", ...]"
211
-
212
- return f"{prefix}{main}{sufix} "
213
-
214
- def __repr__(self) -> str:
215
- return self.__str__()
216
-
217
- def filter(self, **kwargs: Any) -> CalculationBucket:
218
- filters = self.filters.copy()
219
- excludes = self.excludes.copy()
220
- filters.update(parse_filters(kwargs, self.input_fields))
221
- return CalculationBucket(self._manager_class, filters, excludes)
222
-
223
- def exclude(self, **kwargs: Any) -> CalculationBucket:
224
- filters = self.filters.copy()
225
- excludes = self.excludes.copy()
226
- excludes.update(parse_filters(kwargs, self.input_fields))
227
- return CalculationBucket(self._manager_class, filters, excludes)
228
-
229
- def all(self) -> CalculationBucket:
230
- return self
231
-
232
- def __iter__(self) -> Generator[GeneralManagerType]:
233
- combinations = self.generate_combinations()
234
- for combo in combinations:
235
- yield self._manager_class(**combo)
236
-
237
- def generate_combinations(self) -> List[dict[str, Any]]:
238
- if self.__current_combinations is None:
239
- # Implementierung ähnlich wie im InputManager
240
- sorted_inputs = self.topological_sort_inputs()
241
- current_combinations = self._generate_combinations(
242
- sorted_inputs, self.filters, self.excludes
243
- )
244
- if self.sort_key is not None:
245
- sort_key = self.sort_key
246
- if isinstance(sort_key, str):
247
- sort_key = (sort_key,)
248
- key_func = lambda x: (tuple(x[key] for key in sort_key))
249
- current_combinations = sorted(
250
- current_combinations,
251
- key=key_func,
252
- )
253
- if self.reverse:
254
- current_combinations.reverse()
255
- self.__current_combinations = current_combinations
256
-
257
- return self.__current_combinations
258
-
259
- def topological_sort_inputs(self) -> List[str]:
260
- from collections import defaultdict
261
-
262
- dependencies = {
263
- name: field.depends_on for name, field in self.input_fields.items()
264
- }
265
- graph = defaultdict(set)
266
- for key, deps in dependencies.items():
267
- for dep in deps:
268
- graph[dep].add(key)
269
-
270
- visited = set()
271
- sorted_inputs = []
272
-
273
- def visit(node, temp_mark):
274
- if node in visited:
275
- return
276
- if node in temp_mark:
277
- raise ValueError(f"Cyclic dependency detected: {node}")
278
- temp_mark.add(node)
279
- for m in graph.get(node, []):
280
- visit(m, temp_mark)
281
- temp_mark.remove(node)
282
- visited.add(node)
283
- sorted_inputs.append(node)
284
-
285
- for node in self.input_fields.keys():
286
- if node not in visited:
287
- visit(node, set())
288
-
289
- sorted_inputs.reverse()
290
- return sorted_inputs
291
-
292
- def get_possible_values(
293
- self, key_name: str, input_field: Input, current_combo: dict
294
- ) -> Union[Iterable[Any], Bucket[Any]]:
295
- # Hole mögliche Werte
296
- if callable(input_field.possible_values):
297
- depends_on = input_field.depends_on
298
- dep_values = {dep_name: current_combo[dep_name] for dep_name in depends_on}
299
- possible_values = input_field.possible_values(**dep_values)
300
- elif isinstance(input_field.possible_values, (Iterable, Bucket)):
301
- possible_values = input_field.possible_values
302
- else:
303
- raise TypeError(f"Invalid possible_values for input '{key_name}'")
304
- return possible_values
305
-
306
- def _generate_combinations(
307
- self,
308
- sorted_inputs: List[str],
309
- filters: dict[str, dict],
310
- excludes: dict[str, dict],
311
- ) -> List[dict[str, Any]]:
312
- def helper(index, current_combo):
313
- if index == len(sorted_inputs):
314
- yield current_combo.copy()
315
- return
316
- input_name: str = sorted_inputs[index]
317
- input_field = self.input_fields[input_name]
318
-
319
- # Hole mögliche Werte
320
- possible_values = self.get_possible_values(
321
- input_name, input_field, current_combo
322
- )
323
-
324
- # Wende die Filter an
325
- field_filters = filters.get(input_name, {})
326
- field_excludes = excludes.get(input_name, {})
327
-
328
- if isinstance(possible_values, Bucket):
329
- # Wende die Filter- und Exklusionsargumente direkt an
330
- filter_kwargs = field_filters.get("filter_kwargs", {})
331
- exclude_kwargs = field_excludes.get("filter_kwargs", {})
332
- possible_values = possible_values.filter(**filter_kwargs).exclude(
333
- **exclude_kwargs
334
- )
335
- else:
336
- # Wende die Filterfunktionen an
337
- filter_funcs = field_filters.get("filter_funcs", [])
338
- for filter_func in filter_funcs:
339
- possible_values = filter(filter_func, possible_values)
340
-
341
- exclude_funcs = field_excludes.get("filter_funcs", [])
342
- for exclude_func in exclude_funcs:
343
- possible_values = filter(
344
- lambda x: not exclude_func(x), possible_values
345
- )
346
-
347
- possible_values = list(possible_values)
348
-
349
- for value in possible_values:
350
- if not isinstance(value, input_field.type):
351
- continue
352
- current_combo[input_name] = value
353
- yield from helper(index + 1, current_combo)
354
- del current_combo[input_name]
355
-
356
- return list(helper(0, {}))
357
-
358
- def first(self) -> GeneralManagerType | None:
359
- try:
360
- return next(iter(self))
361
- except StopIteration:
362
- return None
363
-
364
- def last(self) -> GeneralManagerType | None:
365
- items = list(self)
366
- if items:
367
- return items[-1]
368
- return None
369
-
370
- def count(self) -> int:
371
- return sum(1 for _ in self)
372
-
373
- def __len__(self) -> int:
374
- return self.count()
375
-
376
- def __getitem__(
377
- self, item: int | slice
378
- ) -> GeneralManagerType | CalculationBucket[GeneralManagerType]:
379
- items = self.generate_combinations()
380
- result = items[item]
381
- if isinstance(result, list):
382
- new_bucket = CalculationBucket(self._manager_class)
383
- new_bucket.filters = self.filters.copy()
384
- new_bucket.excludes = self.excludes.copy()
385
- return new_bucket
386
- return self._manager_class(**result)
387
-
388
- def __contains__(self, item: GeneralManagerType) -> bool:
389
- return item in list(self)
390
-
391
- def get(self, **kwargs: Any) -> GeneralManagerType:
392
- filtered_bucket = self.filter(**kwargs)
393
- items = list(filtered_bucket)
394
- if len(items) == 1:
395
- return items[0]
396
- elif len(items) == 0:
397
- raise ValueError("No matching calculation found.")
398
- else:
399
- raise ValueError("Multiple matching calculations found.")
400
-
401
- def sort(
402
- self, key: str | tuple[str], reverse: bool = False
403
- ) -> CalculationBucket[GeneralManagerType]:
404
- return CalculationBucket(
405
- self._manager_class, self.filters, self.excludes, key, reverse
406
- )