GeneralManager 0.5.1__py3-none-any.whl → 0.6.0__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")
@@ -70,9 +68,17 @@ class InterfaceBase(ABC):
70
68
  *args: Any,
71
69
  **kwargs: dict[str, Any],
72
70
  ) -> dict[str, Any]:
71
+ """
72
+ Parses and validates input arguments into a structured identification dictionary.
73
+
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.
75
+
76
+ Returns:
77
+ A dictionary mapping input field names to their validated and cast values.
78
+ """
73
79
  identification = {}
74
80
  kwargs = args_to_kwargs(args, self.input_fields.keys(), kwargs)
75
- # Prüfe auf fehlende oder unerwartete Argumente
81
+ # Check for extra arguments
76
82
  extra_args = set(kwargs.keys()) - set(self.input_fields.keys())
77
83
  if extra_args:
78
84
  for extra_arg in extra_args:
@@ -85,7 +91,7 @@ class InterfaceBase(ABC):
85
91
  if missing_args:
86
92
  raise TypeError(f"Missing required arguments: {', '.join(missing_args)}")
87
93
 
88
- # Verarbeite Felder unter Berücksichtigung von Abhängigkeiten
94
+ # process input fields with dependencies
89
95
  processed = set()
90
96
  while len(processed) < len(self.input_fields):
91
97
  progress_made = False
@@ -100,7 +106,7 @@ class InterfaceBase(ABC):
100
106
  processed.add(name)
101
107
  progress_made = True
102
108
  if not progress_made:
103
- # Zirkuläre Abhängigkeit erkannt
109
+ # detect circular dependencies
104
110
  unresolved = set(self.input_fields.keys()) - processed
105
111
  raise ValueError(
106
112
  f"Circular dependency detected among inputs: {', '.join(unresolved)}"
@@ -123,13 +129,18 @@ class InterfaceBase(ABC):
123
129
  def _process_input(
124
130
  self, name: str, value: Any, identification: dict[str, Any]
125
131
  ) -> None:
132
+ """
133
+ Validates the type and allowed values of an input field.
134
+
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.
136
+ """
126
137
  input_field = self.input_fields[name]
127
138
  if not isinstance(value, input_field.type):
128
139
  raise TypeError(
129
140
  f"Invalid type for {name}: {type(value)}, expected: {input_field.type}"
130
141
  )
131
142
  if settings.DEBUG:
132
- # Prüfe mögliche Werte
143
+ # `possible_values` can be a callable or an iterable
133
144
  possible_values = input_field.possible_values
134
145
  if possible_values is not None:
135
146
  if callable(possible_values):
@@ -206,103 +217,15 @@ class InterfaceBase(ABC):
206
217
  @abstractmethod
207
218
  def getFieldType(cls, field_name: str) -> type:
208
219
  """
209
- 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.
210
230
  """
211
231
  raise NotImplementedError
212
-
213
-
214
- class Bucket(ABC, Generic[GeneralManagerType]):
215
-
216
- def __init__(self, manager_class: Type[GeneralManagerType]):
217
- self._manager_class = manager_class
218
- self._data = None
219
- self.excludes = {}
220
- self.filters = {}
221
-
222
- def __eq__(self, other: object) -> bool:
223
- if not isinstance(other, self.__class__):
224
- return False
225
- return self._data == other._data and self._manager_class == other._manager_class
226
-
227
- def __reduce__(self) -> str | tuple[Any, ...]:
228
- return (
229
- self.__class__,
230
- (None, self._manager_class, self.filters, self.excludes),
231
- )
232
-
233
- @abstractmethod
234
- def __or__(
235
- self, other: Bucket[GeneralManagerType] | GeneralManager[GeneralManagerType]
236
- ) -> Bucket[GeneralManagerType]:
237
- raise NotImplementedError
238
-
239
- @abstractmethod
240
- def __iter__(
241
- self,
242
- ) -> Generator[GeneralManagerType | GroupManager[GeneralManagerType]]:
243
- raise NotImplementedError
244
-
245
- @abstractmethod
246
- def filter(self, **kwargs: Any) -> Bucket[GeneralManagerType]:
247
- raise NotImplementedError
248
-
249
- @abstractmethod
250
- def exclude(self, **kwargs: Any) -> Bucket[GeneralManagerType]:
251
- raise NotImplementedError
252
-
253
- @abstractmethod
254
- def first(self) -> GeneralManagerType | GroupManager[GeneralManagerType] | None:
255
- raise NotImplementedError
256
-
257
- @abstractmethod
258
- def last(self) -> GeneralManagerType | GroupManager[GeneralManagerType] | None:
259
- raise NotImplementedError
260
-
261
- @abstractmethod
262
- def count(self) -> int:
263
- raise NotImplementedError
264
-
265
- @abstractmethod
266
- def all(self) -> Bucket[GeneralManagerType]:
267
- raise NotImplementedError
268
-
269
- @abstractmethod
270
- def get(
271
- self, **kwargs: Any
272
- ) -> GeneralManagerType | GroupManager[GeneralManagerType]:
273
- raise NotImplementedError
274
-
275
- @abstractmethod
276
- def __getitem__(
277
- self, item: int | slice
278
- ) -> (
279
- GeneralManagerType
280
- | GroupManager[GeneralManagerType]
281
- | Bucket[GeneralManagerType]
282
- ):
283
- raise NotImplementedError
284
-
285
- @abstractmethod
286
- def __len__(self) -> int:
287
- raise NotImplementedError
288
-
289
- @abstractmethod
290
- def __contains__(self, item: GeneralManagerType) -> bool:
291
- raise NotImplementedError
292
-
293
- @abstractmethod
294
- def sort(
295
- self,
296
- key: tuple[str] | str,
297
- reverse: bool = False,
298
- ) -> Bucket[GeneralManagerType]:
299
- raise NotImplementedError
300
-
301
- def group_by(self, *group_by_keys: str) -> GroupBucket[GeneralManagerType]:
302
- """
303
- This method groups the data by the given arguments.
304
- It returns a GroupBucket with the grouped data.
305
- """
306
- from general_manager.manager.groupManager import GroupBucket
307
-
308
- 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):
@@ -118,289 +102,18 @@ class CalculationInterface(InterfaceBase):
118
102
  @classmethod
119
103
  def getFieldType(cls, field_name: str) -> type:
120
104
  """
121
- This method returns the field type for the given field name.
105
+ Returns the type of the specified input field.
106
+
107
+ Args:
108
+ field_name: The name of the input field.
109
+
110
+ Returns:
111
+ The Python type associated with the input field.
112
+
113
+ Raises:
114
+ ValueError: If the specified field name does not exist in input_fields.
122
115
  """
123
116
  input = cls.input_fields.get(field_name)
124
117
  if input is None:
125
118
  raise ValueError(f"Field '{field_name}' not found in input fields.")
126
119
  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
- )