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
async_orm_kit/types.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from datetime import date, datetime, time, timedelta
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from typing import TYPE_CHECKING, TypeVar
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from async_orm_kit.models import Model
|
|
7
|
+
from async_orm_kit.queryset import QuerySet
|
|
8
|
+
|
|
9
|
+
ModelType = TypeVar("ModelType", bound="Model")
|
|
10
|
+
QS = TypeVar("QS", bound="QuerySet")
|
|
11
|
+
|
|
12
|
+
SAFE_SCALAR_TYPES = (
|
|
13
|
+
str,
|
|
14
|
+
int,
|
|
15
|
+
float,
|
|
16
|
+
bool,
|
|
17
|
+
type(None),
|
|
18
|
+
Decimal,
|
|
19
|
+
date,
|
|
20
|
+
datetime,
|
|
21
|
+
time,
|
|
22
|
+
timedelta,
|
|
23
|
+
)
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: async-orm-kit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Async-first Django-inspired QuerySets and relationship-aware query planning for SQLAlchemy
|
|
5
|
+
Keywords: async,asyncio,database,django-inspired,orm,queryset,sqlalchemy,query-planner,postgresql,mysql
|
|
6
|
+
Author: Aryan Niknezhad, Mehrshad Mirshekary
|
|
7
|
+
Author-email: Aryan Niknezhad <aryan513966@gmail.com>, Mehrshad Mirshekary <mehrshad_mirshekary@email.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Framework :: AsyncIO
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
20
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
|
+
Classifier: Topic :: Database
|
|
22
|
+
Classifier: Topic :: Database :: Front-Ends
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.40,<3.0
|
|
26
|
+
Requires-Dist: sphinx>=8.2,<10 ; extra == 'docs'
|
|
27
|
+
Requires-Dist: sphinx-rtd-theme>=3.0,<4 ; extra == 'docs'
|
|
28
|
+
Requires-Dist: fastapi>=0.110,<1.0 ; extra == 'fastapi'
|
|
29
|
+
Requires-Dist: alembic>=1.13,<2.0 ; extra == 'migrations'
|
|
30
|
+
Requires-Dist: asyncmy>=0.2,<1 ; extra == 'mysql'
|
|
31
|
+
Requires-Dist: cryptography>=44 ; extra == 'mysql'
|
|
32
|
+
Requires-Dist: asyncpg>=0.29,<1 ; extra == 'postgres'
|
|
33
|
+
Requires-Dist: aiosqlite>=0.20,<1 ; extra == 'sqlite'
|
|
34
|
+
Requires-Dist: async-storage-kit>=1.0.0,<2.0.0 ; extra == 'storage'
|
|
35
|
+
Requires-Dist: pytest>=8.3,<10 ; extra == 'test'
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.24,<2 ; extra == 'test'
|
|
37
|
+
Requires-Dist: pytest-cov>=6,<8 ; extra == 'test'
|
|
38
|
+
Requires-Dist: aiosqlite>=0.20,<1 ; extra == 'test'
|
|
39
|
+
Requires-Dist: async-storage-kit>=1.0.0,<2.0.0 ; extra == 'test'
|
|
40
|
+
Requires-Dist: alembic>=1.13,<2.0 ; extra == 'test'
|
|
41
|
+
Requires-Dist: fastapi>=0.110,<1.0 ; extra == 'test'
|
|
42
|
+
Requires-Dist: httpx>=0.27,<1 ; extra == 'test'
|
|
43
|
+
Requires-Python: >=3.11, <3.16
|
|
44
|
+
Project-URL: Homepage, https://github.com/Lazarus-org/async-orm-kit
|
|
45
|
+
Project-URL: Repository, https://github.com/Lazarus-org/async-orm-kit
|
|
46
|
+
Project-URL: Documentation, https://github.com/Lazarus-org/async-orm-kit/blob/main/docs/index.rst
|
|
47
|
+
Project-URL: Issues, https://github.com/Lazarus-org/async-orm-kit/issues
|
|
48
|
+
Project-URL: Changelog, https://github.com/Lazarus-org/async-orm-kit/blob/main/CHANGELOG.md
|
|
49
|
+
Provides-Extra: docs
|
|
50
|
+
Provides-Extra: fastapi
|
|
51
|
+
Provides-Extra: migrations
|
|
52
|
+
Provides-Extra: mysql
|
|
53
|
+
Provides-Extra: postgres
|
|
54
|
+
Provides-Extra: sqlite
|
|
55
|
+
Provides-Extra: storage
|
|
56
|
+
Provides-Extra: test
|
|
57
|
+
Description-Content-Type: text/markdown
|
|
58
|
+
|
|
59
|
+
# Async ORM Kit
|
|
60
|
+
|
|
61
|
+
[](https://pypi.org/project/async-orm-kit/)
|
|
62
|
+
[](https://pypi.org/project/async-orm-kit/)
|
|
63
|
+
[](LICENSE)
|
|
64
|
+
|
|
65
|
+
[](https://github.com/Lazarus-org/async-orm-kit/actions/workflows/ci.yml)
|
|
66
|
+
[](.pre-commit-config.yaml)
|
|
67
|
+
[](https://docs.astral.sh/ruff/)
|
|
68
|
+
[](https://codecov.io/gh/lazarus-org/async-orm-kit)
|
|
69
|
+
[](src/async_orm_kit/py.typed)
|
|
70
|
+
|
|
71
|
+
[](docs/reference/index.rst)
|
|
72
|
+
[](https://github.com/Lazarus-org/async-orm-kit/commits/main/)
|
|
73
|
+
[](https://github.com/Lazarus-org/async-orm-kit/issues)
|
|
74
|
+
|
|
75
|
+
[](https://www.sqlalchemy.org/)
|
|
76
|
+
|
|
77
|
+
**Django-inspired models, managers, expressions and QuerySets — powered by SQLAlchemy's async infrastructure.**
|
|
78
|
+
|
|
79
|
+
`async-orm-kit` gives you an expressive, chainable ORM query API while preserving SQLAlchemy 2.x mappings, drivers, sessions, transactions and composable SQL expressions. It's an **async-first library** for applications that want Django-style querying without adopting Django or losing SQLAlchemy control.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
users = await (
|
|
83
|
+
User.objects.filter(session, active=True, age__gte=18)
|
|
84
|
+
.exclude(email__isnull=True)
|
|
85
|
+
.order_by("-created_at", "id")
|
|
86
|
+
.limit(20)
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Features
|
|
91
|
+
|
|
92
|
+
| Area | Capabilities |
|
|
93
|
+
| --- | --- |
|
|
94
|
+
| **Models & managers** | SQLAlchemy declarative models, default/custom managers, model validation, serialization helpers, model-specific exceptions |
|
|
95
|
+
| **QuerySets** | Lazy immutable chaining, `filter`, `exclude`, `get`, `first`, `count`, `exists`, projections, ordering, pagination, async streaming |
|
|
96
|
+
| **Lookups & logic** | `Q` AND/OR/NOT, field and relationship traversal, NULL handling, nested JSON path lookups |
|
|
97
|
+
| **Expressions** | `F`, `Value`, `Case`/`When`, arithmetic and atomic SQL updates, typed SQL functions |
|
|
98
|
+
| **Aggregations** | `Count`, `Sum`, `Avg`, `Min`, `Max`, grouped annotations, `HAVING`, filtered and custom aggregates |
|
|
99
|
+
| **Advanced SQL** | `Subquery`, nested `OuterRef`, `Exists`, window functions, `UNION` / `INTERSECT` / `EXCEPT`, CTEs and recursive CTEs |
|
|
100
|
+
| **Relationships** | `select_related`, `prefetch_related`, relationship planning, correlated collection exclusions, related-row counts |
|
|
101
|
+
| **Database writes** | Create/update/delete, bulk operations, conflict handling, generated defaults, transaction-aware lifecycle hooks |
|
|
102
|
+
| **PostgreSQL** | JSONB and ARRAY operators, row-lock options and dialect-specific upserts |
|
|
103
|
+
| **Extensibility** | QuerySet-local custom lookups, trusted SQL function/aggregate factories, deferred fields, keyset pagination |
|
|
104
|
+
| **Observability** | Event-backed `QueryProfiler`, SQL timings, task-scoped captures, bounded history, heuristic N+1 detection |
|
|
105
|
+
| **Integrations** | Optional `async-storage-kit` / `FileField`, Alembic and FastAPI adapters |
|
|
106
|
+
|
|
107
|
+
The design is intentionally **session-explicit, transaction-neutral and SQLAlchemy-native**. QuerySet methods don't hide network connections, migrations, commits or rollbacks.
|
|
108
|
+
|
|
109
|
+
## Installation
|
|
110
|
+
|
|
111
|
+
**Requirements:** CPython 3.11–3.15, SQLAlchemy 2.x and a compatible async database driver. Only SQLAlchemy is a mandatory runtime dependency.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Install the source checkout with SQLite support
|
|
115
|
+
python -m pip install -e ".[sqlite]"
|
|
116
|
+
|
|
117
|
+
# Distribution extras, when a published build is available:
|
|
118
|
+
pip install "async-orm-kit[postgres]" # asyncpg
|
|
119
|
+
pip install "async-orm-kit[mysql]" # asyncmy
|
|
120
|
+
pip install "async-orm-kit[storage]" # async-storage-kit
|
|
121
|
+
pip install "async-orm-kit[migrations]" # Alembic
|
|
122
|
+
pip install "async-orm-kit[fastapi]" # FastAPI
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
SQLite uses `aiosqlite`; PostgreSQL uses `asyncpg`; MySQL uses `asyncmy`. The package does not start a database or configure a connection pool for you. See [Installation](docs/installation.rst) and the [database compatibility matrix](docs/compatibility.rst).
|
|
126
|
+
|
|
127
|
+
## Quick start
|
|
128
|
+
|
|
129
|
+
Define models with familiar SQLAlchemy mapping primitives:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from sqlalchemy import Boolean, ForeignKey, String
|
|
133
|
+
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
|
134
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
135
|
+
|
|
136
|
+
from async_orm_kit import Model
|
|
137
|
+
|
|
138
|
+
engine = create_async_engine("sqlite+aiosqlite:///./app.db")
|
|
139
|
+
SessionFactory = async_sessionmaker(engine, expire_on_commit=False)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class Author(Model):
|
|
143
|
+
__tablename__ = "authors"
|
|
144
|
+
|
|
145
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
146
|
+
name: Mapped[str] = mapped_column(String(120))
|
|
147
|
+
active: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
148
|
+
posts: Mapped[list["Post"]] = relationship(back_populates="author")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class Post(Model):
|
|
152
|
+
__tablename__ = "posts"
|
|
153
|
+
|
|
154
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
155
|
+
author_id: Mapped[int] = mapped_column(ForeignKey("authors.id"))
|
|
156
|
+
title: Mapped[str] = mapped_column(String(255))
|
|
157
|
+
view_count: Mapped[int] = mapped_column(default=0)
|
|
158
|
+
author: Mapped[Author] = relationship(back_populates="posts")
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
In a disposable development database you can create tables through SQLAlchemy. For deployed applications, use reviewed Alembic migrations:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
async with engine.begin() as connection:
|
|
165
|
+
await connection.run_sync(Model.metadata.create_all)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Write through the caller-owned transaction:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
async with SessionFactory() as session:
|
|
172
|
+
async with session.begin():
|
|
173
|
+
author = await Author.objects.create(session, name="Ada")
|
|
174
|
+
await Post.objects.create(session, author_id=author.id, title="Async SQL")
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Query with eager loading and normal `AsyncSession` ownership:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
async with SessionFactory() as session:
|
|
181
|
+
authors = await (
|
|
182
|
+
Author.objects.filter(session, active=True)
|
|
183
|
+
.prefetch_related("posts")
|
|
184
|
+
.order_by("name")
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
for author in authors:
|
|
188
|
+
print(author.name, len(author.posts))
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Important:** Manager methods accept `session` as their **first argument**. A QuerySet remembers that session; chaining methods such as `.filter(...)` and `.order_by(...)` do **not** take it again. Alternatively, `Author.objects.using(session)` returns a session-bound manager proxy.
|
|
192
|
+
|
|
193
|
+
## Querying and expressions
|
|
194
|
+
|
|
195
|
+
### Boolean logic, aggregation and atomic updates
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from async_orm_kit import Count, F, Q, Sum
|
|
199
|
+
|
|
200
|
+
# Boolean predicates and relationship traversal
|
|
201
|
+
query = Author.objects.filter(
|
|
202
|
+
session,
|
|
203
|
+
Q(name__icontains="ada") | Q(posts__title__icontains="sql"),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Count related rows, preserving parents with no related records
|
|
207
|
+
ranked = await (
|
|
208
|
+
Author.objects.annotate(session, post_count=Count("posts"))
|
|
209
|
+
.filter(post_count__gte=1)
|
|
210
|
+
.order_by("-post_count")
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Updates use SQL-side expressions and still require an application transaction.
|
|
214
|
+
async with SessionFactory() as write_session:
|
|
215
|
+
async with write_session.begin():
|
|
216
|
+
await Post.objects.filter(write_session, id=42).update(
|
|
217
|
+
view_count=F("view_count") + 1
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Use `values(...).annotate(...)` for grouped summaries and `aggregate(...)` for a terminal summary dictionary. Complex combinations of aggregate annotations and row predicates support `Q` OR/AND/NOT using correlated existence semantics. See [Aggregations](docs/advanced/aggregation.rst) and [Expressions](docs/advanced/expressions.rst).
|
|
222
|
+
|
|
223
|
+
### Subqueries, set operations and JSON
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
from async_orm_kit import Exists, OuterRef
|
|
227
|
+
|
|
228
|
+
matching = Post.objects.filter(
|
|
229
|
+
session, author_id=OuterRef("id"), title__icontains="async"
|
|
230
|
+
)
|
|
231
|
+
authors = await Author.objects.filter(session, Exists(matching))
|
|
232
|
+
|
|
233
|
+
# Set operations on compatible projections and the same session
|
|
234
|
+
first = Author.objects.filter(session, active=True).values("id", "name")
|
|
235
|
+
second = Author.objects.filter(session, name__startswith="A").values("id", "name")
|
|
236
|
+
selected = await first.union(second).filter(name__icontains="a").order_by("id")
|
|
237
|
+
|
|
238
|
+
# For a model with a mapped SQLAlchemy JSON column named payload:
|
|
239
|
+
# events = await Event.objects.filter(session, payload__profile__age__gte=18)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The package also supports typed `JSONPath`, PostgreSQL JSONB/ARRAY expressions, CTEs, window functions and SQLAlchemy-composable subqueries. Dialect-specific operators are not silently emulated with text matching. See [Advanced SQL](docs/advanced/sets-cte.rst), [JSON & ARRAY](docs/advanced/json-array.rst) and [Correlated subqueries](docs/advanced/subqueries.rst).
|
|
243
|
+
|
|
244
|
+
### Pagination and streaming
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
base = Author.objects.order_by(session, "id")
|
|
248
|
+
first_page = await base.limit(25)
|
|
249
|
+
|
|
250
|
+
if first_page:
|
|
251
|
+
next_page = await base.seek_after(first_page[-1].id).limit(25)
|
|
252
|
+
|
|
253
|
+
async for author in base.stream():
|
|
254
|
+
process(author)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Keyset pagination requires a deterministic non-nullable ordering that includes the primary key. It accepts **cursor values**, not an opaque signed token. Keep the session open throughout streaming. See [Pagination](docs/advanced/pagination.rst).
|
|
258
|
+
|
|
259
|
+
## Transactions and integrations
|
|
260
|
+
|
|
261
|
+
### You own the session and transaction
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
async with SessionFactory() as session:
|
|
265
|
+
async with session.begin():
|
|
266
|
+
await Author.objects.create(session, name="Grace")
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Model and QuerySet mutation methods flush or execute SQL **without committing or rolling back**. A failed flush requires rollback before session reuse. Sessions must not be shared across concurrent tasks. Model `post_save` and `post_delete` observers run after flush, **before commit**; they aren't after-commit hooks.
|
|
270
|
+
|
|
271
|
+
### Optional framework and storage integrations
|
|
272
|
+
|
|
273
|
+
- **[FastAPI](docs/integrations-rst/fastapi.rst):** request-scoped `AsyncSession` dependency, explicit application transactions and safe exception responses.
|
|
274
|
+
- **[Alembic](docs/integrations-rst/alembic.rst):** import model metadata for autogeneration; schema migrations remain application-owned.
|
|
275
|
+
- **[FileField / async-storage-kit](docs/integrations-rst/storage.rst):** asynchronous upload, streaming download, metadata and backend adapters. Object storage is **not** transactionally coupled to the database.
|
|
276
|
+
- **[Observability](docs/advanced/observability.rst):** SQLAlchemy-event query timing, bounded execution traces and suspected N+1 patterns.
|
|
277
|
+
|
|
278
|
+
## Documentation
|
|
279
|
+
|
|
280
|
+
The primary documentation is authored in **reStructuredText** and built with Sphinx. Start at [`docs/index.rst`](docs/index.rst), or open the sections directly:
|
|
281
|
+
|
|
282
|
+
| Getting started | ORM fundamentals | Advanced queries | Operations |
|
|
283
|
+
| --- | --- | --- | --- |
|
|
284
|
+
| [Install](docs/installation.rst) | [Models](docs/guide/models.rst) | [Expressions](docs/advanced/expressions.rst) | [Compatibility](docs/compatibility.rst) |
|
|
285
|
+
| [Quick start](docs/quickstart.rst) | [Managers](docs/guide/managers.rst) | [Aggregations](docs/advanced/aggregation.rst) | [Troubleshooting](docs/guide/troubleshooting.rst) |
|
|
286
|
+
| [Design](docs/introduction.rst) | [QuerySets](docs/guide/querysets.rst) | [Subqueries](docs/advanced/subqueries.rst) | [Development](docs/maintainers/development.rst) |
|
|
287
|
+
| [Sessions](docs/guide/sessions.rst) | [Relationships](docs/guide/relationships.rst) | [Set operations & CTEs](docs/advanced/sets-cte.rst) | [Release policy](docs/maintainers/release.rst) |
|
|
288
|
+
|
|
289
|
+
The [complete API reference](docs/reference/index.rst) documents exported classes, methods, functions, exceptions, integrations and custom expression types. Supporting architecture decisions are in `docs/architecture/ADR/`.
|
|
290
|
+
|
|
291
|
+
### Build locally
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
python -m pip install -e ".[docs]"
|
|
295
|
+
sphinx-build -b html -W --keep-going docs docs/_build/html
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The repository also supplies `.readthedocs.yaml` for hosted Sphinx builds.
|
|
299
|
+
|
|
300
|
+
## Development and quality
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
uv lock --check
|
|
304
|
+
uv sync --locked
|
|
305
|
+
uv run pytest -q
|
|
306
|
+
uv run ruff check src tests .github/scripts
|
|
307
|
+
uv run ruff format --check src tests .github/scripts
|
|
308
|
+
uv run mypy src/async_orm_kit
|
|
309
|
+
uv run bandit -c pyproject.toml -r src/async_orm_kit
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The source includes offline SQL correctness tests and native-driver integration suites for `aiosqlite`, `asyncpg` and `asyncmy`. A skipped provider test is **not** passing compatibility evidence. Before the first stable release, the supported Python/OS matrix, live database tests, security/type checks, distributions and documentation build must all be verified. See [Release readiness](docs/maintainers/release.rst).
|
|
313
|
+
|
|
314
|
+
## Contributing, security and licensing
|
|
315
|
+
|
|
316
|
+
Contributions and community conduct are governed by the existing detailed [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) documents. Report security issues using [SECURITY.md](SECURITY.md), rather than public issues. Release notes are in [CHANGELOG.md](CHANGELOG.md).
|
|
317
|
+
|
|
318
|
+
Async ORM Kit is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
async_orm_kit/__init__.py,sha256=bQR8PPs6FiBT9TCmGCQudhpdEFsLYC6DdP5WFq4k6fo,3273
|
|
2
|
+
async_orm_kit/_logging.py,sha256=3a18wzmZeg_JVEaubmtekw6d7o9WTEf1iTPF2u0UWi4,2027
|
|
3
|
+
async_orm_kit/_version.py,sha256=J-j-u0itpEFT6irdmWmixQqYMadNl1X91TxUmoiLHMI,22
|
|
4
|
+
async_orm_kit/config.py,sha256=6TTXkCF3vHqxTus5lVd4hse50SE5_2siciLIWCJfkgs,955
|
|
5
|
+
async_orm_kit/exceptions.py,sha256=ew2_hBv2sGlDhiL_nvmWhO1NfRqb8uBlOtdbVG58kcw,13487
|
|
6
|
+
async_orm_kit/integrations/__init__.py,sha256=QcJOG_xQFVvK2qlLX-PaNivWaYoHMQTSylwVEFzRO3g,446
|
|
7
|
+
async_orm_kit/integrations/alembic.py,sha256=I7ds1SxUKjR1m9Hegwjm47oMrEpNDAR4dIYg0IgXqWw,2028
|
|
8
|
+
async_orm_kit/integrations/fastapi.py,sha256=znQjiXcSZnuDQwrTjn06VW2fPDbPhhMBozdYBW_A4Ws,4822
|
|
9
|
+
async_orm_kit/integrations/storage.py,sha256=NnD31AxKk2oC66IdpoUokka_Uew7twSRk8aSXBvPF4I,7753
|
|
10
|
+
async_orm_kit/manager/__init__.py,sha256=xrUzRNQTTaW4tt4R8IsZQq382aU9GxSclCOwIZuQIoQ,52
|
|
11
|
+
async_orm_kit/manager/manager.py,sha256=4JoqvwHMFuDb7NNvHii-xkM7wk0ArnLDrPRhN6NCBxg,21884
|
|
12
|
+
async_orm_kit/models/__init__.py,sha256=F6gddCdrPE2KHkNmapH_Iv-GKeBCOfJfgBEo0G69e04,45
|
|
13
|
+
async_orm_kit/models/base.py,sha256=SHQ03fdFDPch3hxkVKUz-63Rv-lgaU7PwqpyIlDeYFY,20700
|
|
14
|
+
async_orm_kit/models/fields/__init__.py,sha256=saywUj22r9rnm8vl5HvxbdUJjTqSktqME8kT_8U90CM,510
|
|
15
|
+
async_orm_kit/models/fields/exceptions.py,sha256=U7HW-BGs_stu-qXWZPZ9f5aZioYnD6gT3nfc6RnsEmg,310
|
|
16
|
+
async_orm_kit/models/fields/typed/__init__.py,sha256=0jMFqTQ2TPzrqbeyVe5TNvih74p3RFPE1K2Imx59S50,236
|
|
17
|
+
async_orm_kit/models/fields/typed/base.py,sha256=8MeLJDMH1jwJ49sJ5207OGNhQrxZFpkewh59RK2wZJQ,5073
|
|
18
|
+
async_orm_kit/models/fields/typed/file.py,sha256=5WyfMOVnIDhxJkyXvXME4MgoX2XsQf_XuB_6szZsRlI,25602
|
|
19
|
+
async_orm_kit/models/fields/typed/string.py,sha256=cceV1z4IIFWFUgiR-OR_fiGCps96xpH9GsC_A-KGsLI,1114
|
|
20
|
+
async_orm_kit/models/fields/validators.py,sha256=PcPYYbCZx092NJR-6toCn3hPDNdDmA6GTBpWTSKq9hg,3442
|
|
21
|
+
async_orm_kit/models/meta.py,sha256=4ggjADPQuzgpN-Udqv2PTkodCd0-b1GB-h2aAcqGAvQ,7190
|
|
22
|
+
async_orm_kit/models/mixins.py,sha256=G6vgXZqoLagDj7MPXCBdn7ml35Zjc-Fi_9v3XqKzHa8,744
|
|
23
|
+
async_orm_kit/observers/__init__.py,sha256=qW2niOIUfGXDQ0NIPIQeJ14Wx8Arr3SK9S4_If0Sd1U,155
|
|
24
|
+
async_orm_kit/observers/mixin.py,sha256=diWQSkeHJBfpVejTT2rz78pY-Khq0sareWkNCaahrF8,855
|
|
25
|
+
async_orm_kit/observers/model_observer.py,sha256=H_WevHj8dHBMayAaVDqUa-Zmab22_DdHyBmXct8KjFk,527
|
|
26
|
+
async_orm_kit/observers/queryset_observer.py,sha256=PYDzxQzz-xtMjoavK-LP4vH0bb3-vQWvlklZMlkzmeA,1040
|
|
27
|
+
async_orm_kit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
+
async_orm_kit/query/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
async_orm_kit/query/aggregations/__init__.py,sha256=gi18bHwBjQk3wS1dXnbE7wqbu-QZx4lo6EIZ3_i_cYk,239
|
|
30
|
+
async_orm_kit/query/aggregations/avg.py,sha256=09lKi74JUGJO5GPMfZ4Db4Vfsf80Ef_Ykx9oYLTBw4g,2936
|
|
31
|
+
async_orm_kit/query/aggregations/base.py,sha256=fswtU07-CejU2rdn2wdha8bF08Kocw0QJ5RsNibP62Y,14977
|
|
32
|
+
async_orm_kit/query/aggregations/count.py,sha256=SVYObnXXX2kc9mg90VXBWkv1MfwAe7nYMaWsibyVNX8,4712
|
|
33
|
+
async_orm_kit/query/aggregations/max.py,sha256=8Jk_pY0C42C9XcV6Ia_WMbpq9FHX06Cm20TmUxzpcB4,3141
|
|
34
|
+
async_orm_kit/query/aggregations/min.py,sha256=woh9AyvfE6X1zFbvWtAM8Nf2da7o28ToFtUnq-xYrvs,3140
|
|
35
|
+
async_orm_kit/query/aggregations/sum.py,sha256=73y-vrQ0-1Eoi6zEIwPX5-6lesaAu1MHb0PdznPuY7A,3093
|
|
36
|
+
async_orm_kit/query/compiler.py,sha256=nF4LcjiLAZ11HEt5lhV9R4DNuptSSnKOG14fwTcBPq0,22674
|
|
37
|
+
async_orm_kit/query/debugger.py,sha256=bx4ERdnp5zHL-nrfJ1_VVXxg5F6cr7eGt3g1ppYJ4mM,9165
|
|
38
|
+
async_orm_kit/query/expressions/__init__.py,sha256=s7UjRe7fYWV3pgrAPhRSdnYF4g5bL4UdzLn8ALJ4RZs,986
|
|
39
|
+
async_orm_kit/query/expressions/analysis.py,sha256=IQHoip8mwkiURDdioCGj3s_XrTIRU4jgSB1Q7jduNQA,1975
|
|
40
|
+
async_orm_kit/query/expressions/case.py,sha256=XoLV8WDBvhKvwl9xJ15Rf_wdnLNuXSRzuMtv4xx4B4Y,10934
|
|
41
|
+
async_orm_kit/query/expressions/collections.py,sha256=tVrnLdBsAKZKDcNFyTvZ5caUnbCnW0nJVT34_mvjPzg,9142
|
|
42
|
+
async_orm_kit/query/expressions/f.py,sha256=9dUsT2k_RRFfSj-vb1SGbwpnFhx2e6Yecb0hp3FiJrI,13750
|
|
43
|
+
async_orm_kit/query/expressions/functions.py,sha256=-uiSSvPM2e7ga-RHjAs0f1w-AffJ4LfciMPvXDGeFA4,6783
|
|
44
|
+
async_orm_kit/query/expressions/subquery.py,sha256=FfjwVHK3qfzljDwjOYmnx0CJVb82kHTOF6qYdpfOK38,8787
|
|
45
|
+
async_orm_kit/query/expressions/value.py,sha256=-DCworcAQPja9IixPGkR3BttMIYCmOzd96Tem51OBYI,4042
|
|
46
|
+
async_orm_kit/query/expressions/window.py,sha256=7eLZPYVRpKYTjIb0_TVrdYTMWp8UTjCC-Hd6Dt_yorU,3604
|
|
47
|
+
async_orm_kit/query/lookups/__init__.py,sha256=DdQT_cq9aweAIEhRhs2qLqIK3qbz0vfwKgzjRPUfMwI,4044
|
|
48
|
+
async_orm_kit/query/lookups/conditions.py,sha256=_Y6r1OZE7T5g4Ip96jdOZRRnILo3ZAhTCUxcclnV6-Y,13826
|
|
49
|
+
async_orm_kit/query/lookups/correlated.py,sha256=KXTFBHOU1fKbO5fb41az34vxpseW6cNm-id2MNia7Ds,3283
|
|
50
|
+
async_orm_kit/query/lookups/json_paths.py,sha256=fJd9qEOlWvtAo_nlPMMeB9o36siXHeBCYeVqZZM6B0c,6690
|
|
51
|
+
async_orm_kit/query/lookups/parser.py,sha256=8wcMjC6Brto0con6FehkF0xKxsZ9T0qz-NAXQ_lR9Us,12590
|
|
52
|
+
async_orm_kit/query/lookups/resolver/__init__.py,sha256=0uBjLvzMRLK_IyB_XbjG7hkikY9elVb-Zx8Y4r0ftXA,209
|
|
53
|
+
async_orm_kit/query/lookups/resolver/context.py,sha256=rNdwXP9vxISylcS_Jx_RQlkHOGzgQjGp0HG1zD5yGCY,6880
|
|
54
|
+
async_orm_kit/query/lookups/resolver/resolver.py,sha256=ceM1H0vya4RDjuXWMODjBz-sgAZME_6n4T4tWlZLvz8,24910
|
|
55
|
+
async_orm_kit/query/lookups/resolver/types.py,sha256=zmsbIGFzYHsfDvVPhhpHDzH_JsfWsAnVDR9P6VwPY9E,6017
|
|
56
|
+
async_orm_kit/query/mixins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
|
+
async_orm_kit/query/mixins/combinable.py,sha256=rGOwxnJpwUytm0w9VORJSwVQ_bHatxyra5kWasZbIpc,14417
|
|
58
|
+
async_orm_kit/query/mixins/orderable.py,sha256=B3qXmBSaEXXaKJJRgdDXMm-ir8k-aJRc7z_TmoFThls,8895
|
|
59
|
+
async_orm_kit/query/observability.py,sha256=yWK4mU-hdXQMAEbPQkIz27aZ97nqVnT5er_tuQ_TgRw,12979
|
|
60
|
+
async_orm_kit/query/planning/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
61
|
+
async_orm_kit/query/planning/directives.py,sha256=d3Vc6jx9lQEuPp4WoLWU_KvO71HIZUKHHYa7gHbp8X0,4191
|
|
62
|
+
async_orm_kit/query/planning/factory.py,sha256=2FTg_VQVm5I7htHDR6YZTU-cvLFMkG5f_JapaskWQj4,1254
|
|
63
|
+
async_orm_kit/query/planning/passes/__init__.py,sha256=GScNSxzfV9AmapHZ9XsRkdvOOKHUPtBhyrygYdasujQ,485
|
|
64
|
+
async_orm_kit/query/planning/passes/base.py,sha256=phErPZpdvydcTWd2hebc2Vm7GxfRohOILygQjCMKmjQ,1237
|
|
65
|
+
async_orm_kit/query/planning/passes/cartesian_detection.py,sha256=e53On7uh3JXbqBebTTukprYttVl_ktZZiTXEoPVOa_M,2504
|
|
66
|
+
async_orm_kit/query/planning/passes/depth_splitting.py,sha256=j07eyt6l3JNe8PVoIWsrqouqR7qhmPifBeU-fMVidaQ,2737
|
|
67
|
+
async_orm_kit/query/planning/passes/join_promotion.py,sha256=r8Pbhk6fBM6TvcIjoAFJIRaMfiJA9DgOiSc2NWz16c8,2595
|
|
68
|
+
async_orm_kit/query/planning/passes/path_conflicts.py,sha256=pCpnCf8jedWDFAVKE5lKC3_cA-t0OoKApJnj7jBQzIA,3926
|
|
69
|
+
async_orm_kit/query/planning/passes/redundant_path.py,sha256=9yMMqNvzZ_dskplVKv92NOGq0r657aiUA_VUg85Burw,2322
|
|
70
|
+
async_orm_kit/query/planning/passes/strategy_selection.py,sha256=Qk9ZXWPfOxyvbC0ZW7ROhQ8udI21iFxBw_0iw0jRImE,1601
|
|
71
|
+
async_orm_kit/query/planning/planner.py,sha256=2W-34LOQL223Gq_zfo2JOxQbss3Jaq9UJAfqm3u0W5Y,5482
|
|
72
|
+
async_orm_kit/query/planning/protocols.py,sha256=nolSkVKcvNAp7WvjT2mevCyPT5rNPPn1C_XM3cCDWJQ,1811
|
|
73
|
+
async_orm_kit/query/postgres/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
74
|
+
async_orm_kit/query/postgres/optimizer.py,sha256=_-doCZOWY_A48t9GRz7K0t8DsmmbKGCgXZUc6-ggvBA,5955
|
|
75
|
+
async_orm_kit/query/q.py,sha256=UvHeLKzckbUuLjbJUgGW4wpA5-wQTSeNvmwhgpPpdbo,23011
|
|
76
|
+
async_orm_kit/query/relations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
77
|
+
async_orm_kit/query/relations/enums.py,sha256=T0KAe3w3Ffo7SEsKyPAUw-3T786Gd5QowamupTarkhw,302
|
|
78
|
+
async_orm_kit/query/relations/graph.py,sha256=PAwKScKpcwK-T9k8xaSqB-K_32DhvUtpKBaUa15gp14,33996
|
|
79
|
+
async_orm_kit/query/relations/node.py,sha256=wHGF8jIXj-b4YI3zeQN2ZRKo4SiQn0w_8nn8kw8oFNs,16371
|
|
80
|
+
async_orm_kit/queryset/__init__.py,sha256=MlxV5oJjSTuorDGTAN7VGCnrS5CsGG7W55MQhbGTCCM,114
|
|
81
|
+
async_orm_kit/queryset/builder.py,sha256=JuF8rodWe-p1woYPZjnxrfFG5JZxedGYG40izhDdfmo,51966
|
|
82
|
+
async_orm_kit/queryset/compound.py,sha256=vmoaeBq7G1oLz-jDno4yrmVK32cOQBBOt3wbmfxFe30,15212
|
|
83
|
+
async_orm_kit/queryset/executor.py,sha256=uN_CSSnmb_sdvbfZhxeoQ3ZcRapImgHS1nFkPTjHjk0,29926
|
|
84
|
+
async_orm_kit/queryset/queryset.py,sha256=FgzmwrIrQ4NU_SXzU0PGMtbNfoFAGT7QbwcQvtvKKuM,68575
|
|
85
|
+
async_orm_kit/queryset/state.py,sha256=_7rrKAyTo9mt41hpmoo-EoZM-yb0P-7nH-dHaQYirb0,7670
|
|
86
|
+
async_orm_kit/queryset/validators.py,sha256=aPO_tqZ7GYCMaGh2O47aucPJ-w9u-VlCueo-luydckw,17065
|
|
87
|
+
async_orm_kit/queryset/writer.py,sha256=rfGwF1CxqKGtQrjKx7Z8x0vjuwFytrLjOHMmP7Y1MbI,32434
|
|
88
|
+
async_orm_kit/types.py,sha256=juglsMGfUzah7_i5OuIx9WclRdiJ-xPlLikzbiQBVFQ,461
|
|
89
|
+
async_orm_kit-1.0.0.dist-info/licenses/LICENSE,sha256=3xVqY2F_FE9nMl8iT4dxe25AlpFVwmzr4dLZQJ-T67w,1104
|
|
90
|
+
async_orm_kit-1.0.0.dist-info/WHEEL,sha256=MWx7psoF7KNST9G72pGtiBYuaXRXQXBk9pw5R6FzYko,80
|
|
91
|
+
async_orm_kit-1.0.0.dist-info/METADATA,sha256=II2b2XSw0C8TR1L7haDHiUcJEDN17uNrM4zXOTDpy8s,16446
|
|
92
|
+
async_orm_kit-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Async ORM Kit contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|