async-orm-kit 1.0.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.
Files changed (92) hide show
  1. async_orm_kit/__init__.py +151 -0
  2. async_orm_kit/_logging.py +58 -0
  3. async_orm_kit/_version.py +1 -0
  4. async_orm_kit/config.py +31 -0
  5. async_orm_kit/exceptions.py +353 -0
  6. async_orm_kit/integrations/__init__.py +15 -0
  7. async_orm_kit/integrations/alembic.py +56 -0
  8. async_orm_kit/integrations/fastapi.py +134 -0
  9. async_orm_kit/integrations/storage.py +213 -0
  10. async_orm_kit/manager/__init__.py +3 -0
  11. async_orm_kit/manager/manager.py +568 -0
  12. async_orm_kit/models/__init__.py +3 -0
  13. async_orm_kit/models/base.py +568 -0
  14. async_orm_kit/models/fields/__init__.py +23 -0
  15. async_orm_kit/models/fields/exceptions.py +10 -0
  16. async_orm_kit/models/fields/typed/__init__.py +11 -0
  17. async_orm_kit/models/fields/typed/base.py +155 -0
  18. async_orm_kit/models/fields/typed/file.py +697 -0
  19. async_orm_kit/models/fields/typed/string.py +38 -0
  20. async_orm_kit/models/fields/validators.py +110 -0
  21. async_orm_kit/models/meta.py +181 -0
  22. async_orm_kit/models/mixins.py +19 -0
  23. async_orm_kit/observers/__init__.py +4 -0
  24. async_orm_kit/observers/mixin.py +26 -0
  25. async_orm_kit/observers/model_observer.py +20 -0
  26. async_orm_kit/observers/queryset_observer.py +38 -0
  27. async_orm_kit/py.typed +0 -0
  28. async_orm_kit/query/__init__.py +0 -0
  29. async_orm_kit/query/aggregations/__init__.py +8 -0
  30. async_orm_kit/query/aggregations/avg.py +71 -0
  31. async_orm_kit/query/aggregations/base.py +317 -0
  32. async_orm_kit/query/aggregations/count.py +115 -0
  33. async_orm_kit/query/aggregations/max.py +76 -0
  34. async_orm_kit/query/aggregations/min.py +76 -0
  35. async_orm_kit/query/aggregations/sum.py +73 -0
  36. async_orm_kit/query/compiler.py +574 -0
  37. async_orm_kit/query/debugger.py +269 -0
  38. async_orm_kit/query/expressions/__init__.py +62 -0
  39. async_orm_kit/query/expressions/analysis.py +56 -0
  40. async_orm_kit/query/expressions/case.py +321 -0
  41. async_orm_kit/query/expressions/collections.py +251 -0
  42. async_orm_kit/query/expressions/f.py +362 -0
  43. async_orm_kit/query/expressions/functions.py +202 -0
  44. async_orm_kit/query/expressions/subquery.py +206 -0
  45. async_orm_kit/query/expressions/value.py +101 -0
  46. async_orm_kit/query/expressions/window.py +95 -0
  47. async_orm_kit/query/lookups/__init__.py +121 -0
  48. async_orm_kit/query/lookups/conditions.py +315 -0
  49. async_orm_kit/query/lookups/correlated.py +86 -0
  50. async_orm_kit/query/lookups/json_paths.py +167 -0
  51. async_orm_kit/query/lookups/parser.py +276 -0
  52. async_orm_kit/query/lookups/resolver/__init__.py +5 -0
  53. async_orm_kit/query/lookups/resolver/context.py +150 -0
  54. async_orm_kit/query/lookups/resolver/resolver.py +572 -0
  55. async_orm_kit/query/lookups/resolver/types.py +150 -0
  56. async_orm_kit/query/mixins/__init__.py +0 -0
  57. async_orm_kit/query/mixins/combinable.py +376 -0
  58. async_orm_kit/query/mixins/orderable.py +208 -0
  59. async_orm_kit/query/observability.py +346 -0
  60. async_orm_kit/query/planning/__init__.py +0 -0
  61. async_orm_kit/query/planning/directives.py +117 -0
  62. async_orm_kit/query/planning/factory.py +40 -0
  63. async_orm_kit/query/planning/passes/__init__.py +15 -0
  64. async_orm_kit/query/planning/passes/base.py +47 -0
  65. async_orm_kit/query/planning/passes/cartesian_detection.py +74 -0
  66. async_orm_kit/query/planning/passes/depth_splitting.py +78 -0
  67. async_orm_kit/query/planning/passes/join_promotion.py +81 -0
  68. async_orm_kit/query/planning/passes/path_conflicts.py +103 -0
  69. async_orm_kit/query/planning/passes/redundant_path.py +67 -0
  70. async_orm_kit/query/planning/passes/strategy_selection.py +49 -0
  71. async_orm_kit/query/planning/planner.py +160 -0
  72. async_orm_kit/query/planning/protocols.py +78 -0
  73. async_orm_kit/query/postgres/__init__.py +0 -0
  74. async_orm_kit/query/postgres/optimizer.py +189 -0
  75. async_orm_kit/query/q.py +596 -0
  76. async_orm_kit/query/relations/__init__.py +0 -0
  77. async_orm_kit/query/relations/enums.py +16 -0
  78. async_orm_kit/query/relations/graph.py +908 -0
  79. async_orm_kit/query/relations/node.py +421 -0
  80. async_orm_kit/queryset/__init__.py +4 -0
  81. async_orm_kit/queryset/builder.py +1259 -0
  82. async_orm_kit/queryset/compound.py +379 -0
  83. async_orm_kit/queryset/executor.py +718 -0
  84. async_orm_kit/queryset/queryset.py +1726 -0
  85. async_orm_kit/queryset/state.py +164 -0
  86. async_orm_kit/queryset/validators.py +361 -0
  87. async_orm_kit/queryset/writer.py +755 -0
  88. async_orm_kit/types.py +23 -0
  89. async_orm_kit-1.0.0.dist-info/METADATA +318 -0
  90. async_orm_kit-1.0.0.dist-info/RECORD +92 -0
  91. async_orm_kit-1.0.0.dist-info/WHEEL +4 -0
  92. async_orm_kit-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,155 @@
1
+ import inspect
2
+ import logging
3
+ from collections.abc import Awaitable, Callable
4
+ from typing import Any
5
+
6
+ from sqlalchemy.types import TypeDecorator
7
+
8
+ from async_orm_kit.models.fields.exceptions import FieldValidationError
9
+ from async_orm_kit.models.fields.validators import (
10
+ FieldValidator,
11
+ )
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ ValidatorType = (
17
+ FieldValidator | Callable[[object], None] | Callable[[object], Awaitable[None]]
18
+ )
19
+
20
+
21
+ class BaseValidatedType(TypeDecorator):
22
+ """Production-grade validated type with monitoring and bulk optimization."""
23
+
24
+ cache_ok = True
25
+ impl: Any = None
26
+
27
+ def __init__(
28
+ self,
29
+ *,
30
+ validators: list[ValidatorType] | None = None,
31
+ **kwargs,
32
+ ):
33
+ super().__init__(**kwargs)
34
+ self.model_class = None # Will be set by contribute_to_class
35
+ self.attname: str | None = None
36
+ self._validators: list[ValidatorType] = validators or []
37
+ self._sync_validators, self._async_validators = self._split_validators()
38
+
39
+ # Validators are partitioned once at construction. The previous
40
+ # implementation attempted to call pipeline builders that were never
41
+ # defined; the split lists are the actual execution pipeline.
42
+
43
+ def _is_async_validator(self, v: ValidatorType) -> bool:
44
+ """Return True if validator is a coroutine function or has async __call__."""
45
+ if inspect.iscoroutinefunction(v):
46
+ return True
47
+ # Inspect the callable implementation to detect async validator objects.
48
+ call = getattr(v, "__call__", None) # noqa: B004
49
+ return bool(call and inspect.iscoroutinefunction(call))
50
+
51
+ def _split_validators(self):
52
+ sync_list: list[ValidatorType] = []
53
+ async_list: list[ValidatorType] = []
54
+ for v in self._validators:
55
+ if self._is_async_validator(v):
56
+ async_list.append(v)
57
+ else:
58
+ sync_list.append(v)
59
+ return sync_list, async_list
60
+
61
+ def run_validators(self, value):
62
+ """
63
+ Run synchronous validators.
64
+
65
+ Validators are expected to:
66
+ - take `value`
67
+ - raise FieldValidationError (or ValueError) if invalid
68
+ - return None (no transformation)
69
+ """
70
+ if value is None:
71
+ return
72
+
73
+ for validator in self._sync_validators:
74
+ try:
75
+ validator(value)
76
+ except FieldValidationError as e:
77
+ e.field_name = self.attname
78
+ raise
79
+ except Exception as e:
80
+ # Normalize to FieldValidationError
81
+ raise FieldValidationError(
82
+ message=str(e), field_name=self.attname
83
+ ) from e
84
+
85
+ async def arun_validators(self, value):
86
+ """
87
+ Run asynchronous validators.
88
+
89
+ Async validators have same contract as sync ones but may be coroutines.
90
+ """
91
+ if value is None:
92
+ return
93
+
94
+ for validator in self._async_validators:
95
+ try:
96
+ await validator(value)
97
+ except FieldValidationError as e:
98
+ e.field_name = self.attname
99
+ raise
100
+ except TypeError:
101
+ raise
102
+ except Exception as e:
103
+ raise FieldValidationError(
104
+ message=str(e), field_name=self.attname
105
+ ) from e
106
+
107
+ def contribute_to_class(self, cls, name, instrumented_attr):
108
+ """
109
+ Called after SQLAlchemy instrumentation.
110
+ Subclasses can override to add custom behavior.
111
+
112
+ Args:
113
+ cls: The model class
114
+ name: The attribute name
115
+ instrumented_attr: SQLAlchemy's InstrumentedAttribute
116
+ """
117
+ cls._model_field_types[name] = self
118
+ self.model_class = cls
119
+ self.attname = name
120
+
121
+ def validate(self, value, model_instance=None):
122
+ """
123
+ Internal field-level validation.
124
+ Override in subclasses to implement built-in constraints (like max_length, range, etc.).
125
+ Should raise FieldValidationError on failure.
126
+ """
127
+ # Default: accept all non-null values
128
+ return value
129
+
130
+ async def clean(self, value, model_instance):
131
+ """
132
+ Async validation method that runs all validators (sync + async).
133
+ Use this when you need explicit validation with async support.
134
+ """
135
+ self.run_validators(value)
136
+ await self.arun_validators(value)
137
+ return value
138
+
139
+ def process_bind_param(self, value, dialect):
140
+ """
141
+ Runs core validation automatically before DB write.
142
+ """
143
+ try:
144
+ value = self.validate(value)
145
+ return value
146
+ except FieldValidationError as e:
147
+ if not e.field_name:
148
+ e.field_name = self.attname
149
+ raise
150
+ except Exception as e:
151
+ raise FieldValidationError(message=str(e), field_name=self.attname) from e
152
+
153
+ def process_result_value(self, value, dialect):
154
+ """Zero-cost read path."""
155
+ return value