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.
- async_orm_kit/__init__.py +151 -0
- async_orm_kit/_logging.py +58 -0
- async_orm_kit/_version.py +1 -0
- async_orm_kit/config.py +31 -0
- async_orm_kit/exceptions.py +353 -0
- async_orm_kit/integrations/__init__.py +15 -0
- async_orm_kit/integrations/alembic.py +56 -0
- async_orm_kit/integrations/fastapi.py +134 -0
- async_orm_kit/integrations/storage.py +213 -0
- async_orm_kit/manager/__init__.py +3 -0
- async_orm_kit/manager/manager.py +568 -0
- async_orm_kit/models/__init__.py +3 -0
- async_orm_kit/models/base.py +568 -0
- async_orm_kit/models/fields/__init__.py +23 -0
- async_orm_kit/models/fields/exceptions.py +10 -0
- async_orm_kit/models/fields/typed/__init__.py +11 -0
- async_orm_kit/models/fields/typed/base.py +155 -0
- async_orm_kit/models/fields/typed/file.py +697 -0
- async_orm_kit/models/fields/typed/string.py +38 -0
- async_orm_kit/models/fields/validators.py +110 -0
- async_orm_kit/models/meta.py +181 -0
- async_orm_kit/models/mixins.py +19 -0
- async_orm_kit/observers/__init__.py +4 -0
- async_orm_kit/observers/mixin.py +26 -0
- async_orm_kit/observers/model_observer.py +20 -0
- async_orm_kit/observers/queryset_observer.py +38 -0
- async_orm_kit/py.typed +0 -0
- async_orm_kit/query/__init__.py +0 -0
- async_orm_kit/query/aggregations/__init__.py +8 -0
- async_orm_kit/query/aggregations/avg.py +71 -0
- async_orm_kit/query/aggregations/base.py +317 -0
- async_orm_kit/query/aggregations/count.py +115 -0
- async_orm_kit/query/aggregations/max.py +76 -0
- async_orm_kit/query/aggregations/min.py +76 -0
- async_orm_kit/query/aggregations/sum.py +73 -0
- async_orm_kit/query/compiler.py +574 -0
- async_orm_kit/query/debugger.py +269 -0
- async_orm_kit/query/expressions/__init__.py +62 -0
- async_orm_kit/query/expressions/analysis.py +56 -0
- async_orm_kit/query/expressions/case.py +321 -0
- async_orm_kit/query/expressions/collections.py +251 -0
- async_orm_kit/query/expressions/f.py +362 -0
- async_orm_kit/query/expressions/functions.py +202 -0
- async_orm_kit/query/expressions/subquery.py +206 -0
- async_orm_kit/query/expressions/value.py +101 -0
- async_orm_kit/query/expressions/window.py +95 -0
- async_orm_kit/query/lookups/__init__.py +121 -0
- async_orm_kit/query/lookups/conditions.py +315 -0
- async_orm_kit/query/lookups/correlated.py +86 -0
- async_orm_kit/query/lookups/json_paths.py +167 -0
- async_orm_kit/query/lookups/parser.py +276 -0
- async_orm_kit/query/lookups/resolver/__init__.py +5 -0
- async_orm_kit/query/lookups/resolver/context.py +150 -0
- async_orm_kit/query/lookups/resolver/resolver.py +572 -0
- async_orm_kit/query/lookups/resolver/types.py +150 -0
- async_orm_kit/query/mixins/__init__.py +0 -0
- async_orm_kit/query/mixins/combinable.py +376 -0
- async_orm_kit/query/mixins/orderable.py +208 -0
- async_orm_kit/query/observability.py +346 -0
- async_orm_kit/query/planning/__init__.py +0 -0
- async_orm_kit/query/planning/directives.py +117 -0
- async_orm_kit/query/planning/factory.py +40 -0
- async_orm_kit/query/planning/passes/__init__.py +15 -0
- async_orm_kit/query/planning/passes/base.py +47 -0
- async_orm_kit/query/planning/passes/cartesian_detection.py +74 -0
- async_orm_kit/query/planning/passes/depth_splitting.py +78 -0
- async_orm_kit/query/planning/passes/join_promotion.py +81 -0
- async_orm_kit/query/planning/passes/path_conflicts.py +103 -0
- async_orm_kit/query/planning/passes/redundant_path.py +67 -0
- async_orm_kit/query/planning/passes/strategy_selection.py +49 -0
- async_orm_kit/query/planning/planner.py +160 -0
- async_orm_kit/query/planning/protocols.py +78 -0
- async_orm_kit/query/postgres/__init__.py +0 -0
- async_orm_kit/query/postgres/optimizer.py +189 -0
- async_orm_kit/query/q.py +596 -0
- async_orm_kit/query/relations/__init__.py +0 -0
- async_orm_kit/query/relations/enums.py +16 -0
- async_orm_kit/query/relations/graph.py +908 -0
- async_orm_kit/query/relations/node.py +421 -0
- async_orm_kit/queryset/__init__.py +4 -0
- async_orm_kit/queryset/builder.py +1259 -0
- async_orm_kit/queryset/compound.py +379 -0
- async_orm_kit/queryset/executor.py +718 -0
- async_orm_kit/queryset/queryset.py +1726 -0
- async_orm_kit/queryset/state.py +164 -0
- async_orm_kit/queryset/validators.py +361 -0
- async_orm_kit/queryset/writer.py +755 -0
- async_orm_kit/types.py +23 -0
- async_orm_kit-1.0.0.dist-info/METADATA +318 -0
- async_orm_kit-1.0.0.dist-info/RECORD +92 -0
- async_orm_kit-1.0.0.dist-info/WHEEL +4 -0
- 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
|