snake-orm 0.1.0b1__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.
- snake_orm-0.1.0b1.dist-info/METADATA +366 -0
- snake_orm-0.1.0b1.dist-info/RECORD +198 -0
- snake_orm-0.1.0b1.dist-info/WHEEL +4 -0
- snake_orm-0.1.0b1.dist-info/entry_points.txt +2 -0
- snake_orm-0.1.0b1.dist-info/licenses/LICENSE +21 -0
- snakeorm/__init__.py +214 -0
- snakeorm/advisor.py +109 -0
- snakeorm/cli/__init__.py +3 -0
- snakeorm/cli/app.py +992 -0
- snakeorm/cli/discovery.py +179 -0
- snakeorm/cli/hooks.py +86 -0
- snakeorm/compiler/__init__.py +3 -0
- snakeorm/compiler/compiler.py +330 -0
- snakeorm/connection.py +228 -0
- snakeorm/contrib/__init__.py +17 -0
- snakeorm/contrib/asgi.py +192 -0
- snakeorm/contrib/config.py +264 -0
- snakeorm/contrib/deliver.py +256 -0
- snakeorm/contrib/django.py +245 -0
- snakeorm/contrib/sidecar.py +44 -0
- snakeorm/contrib/wsgi.py +176 -0
- snakeorm/core/__init__.py +1 -0
- snakeorm/core/config.py +173 -0
- snakeorm/core/converters.py +172 -0
- snakeorm/core/exceptions.py +175 -0
- snakeorm/core/placement.py +15 -0
- snakeorm/core/sentinels.py +37 -0
- snakeorm/core/signals.py +114 -0
- snakeorm/debug/__init__.py +42 -0
- snakeorm/debug/assets/css/panel.css +616 -0
- snakeorm/debug/assets/css/snake_orm_app.css +293 -0
- snakeorm/debug/assets/img/icon.svg +5 -0
- snakeorm/debug/assets/js/history.js +425 -0
- snakeorm/debug/assets/js/language.js +283 -0
- snakeorm/debug/assets/js/panel.js +241 -0
- snakeorm/debug/assets/js/snake_orm_app.js +82 -0
- snakeorm/debug/assets/pages/cli.html +28 -0
- snakeorm/debug/assets/pages/config.html +34 -0
- snakeorm/debug/assets/pages/database_first.html +29 -0
- snakeorm/debug/assets/pages/help.html +21 -0
- snakeorm/debug/assets/pages/history.html +27 -0
- snakeorm/debug/capture.py +234 -0
- snakeorm/debug/channel.py +131 -0
- snakeorm/debug/collector.py +144 -0
- snakeorm/debug/config.py +149 -0
- snakeorm/debug/html.py +578 -0
- snakeorm/debug/matrix.py +60 -0
- snakeorm/debug/origin.py +42 -0
- snakeorm/debug/otel/__init__.py +68 -0
- snakeorm/debug/otel/context.py +43 -0
- snakeorm/debug/otel/exporter.py +277 -0
- snakeorm/debug/otel/payload.py +97 -0
- snakeorm/debug/otel/report.py +29 -0
- snakeorm/debug/otel/spans.py +393 -0
- snakeorm/debug/otel/summary.py +59 -0
- snakeorm/debug/record.py +54 -0
- snakeorm/debug/report.py +311 -0
- snakeorm/debug/testing.py +23 -0
- snakeorm/decorators/__init__.py +13 -0
- snakeorm/decorators/abstract.py +26 -0
- snakeorm/decorators/db_first.py +104 -0
- snakeorm/decorators/function.py +28 -0
- snakeorm/decorators/model.py +268 -0
- snakeorm/decorators/polymorphic.py +140 -0
- snakeorm/decorators/result.py +167 -0
- snakeorm/decorators/row.py +90 -0
- snakeorm/decorators/trigger.py +47 -0
- snakeorm/decorators/view.py +149 -0
- snakeorm/dialects/__init__.py +6 -0
- snakeorm/dialects/base.py +373 -0
- snakeorm/dialects/capabilities.py +571 -0
- snakeorm/dialects/literals.py +46 -0
- snakeorm/dialects/matrix.py +384 -0
- snakeorm/dialects/mysql.py +519 -0
- snakeorm/dialects/postgres.py +504 -0
- snakeorm/dialects/sqlite.py +417 -0
- snakeorm/drivers/__init__.py +18 -0
- snakeorm/drivers/asyncbase.py +73 -0
- snakeorm/drivers/asyncdecorators.py +244 -0
- snakeorm/drivers/asyncpool.py +228 -0
- snakeorm/drivers/asyncpsycopg.py +124 -0
- snakeorm/drivers/asyncpymysql.py +50 -0
- snakeorm/drivers/asyncsqlite.py +30 -0
- snakeorm/drivers/base.py +76 -0
- snakeorm/drivers/failures.py +181 -0
- snakeorm/drivers/logging.py +167 -0
- snakeorm/drivers/pool.py +352 -0
- snakeorm/drivers/psycopg.py +188 -0
- snakeorm/drivers/pymysql.py +176 -0
- snakeorm/drivers/savepoints.py +46 -0
- snakeorm/drivers/sqlite.py +170 -0
- snakeorm/drivers/threaded.py +143 -0
- snakeorm/drivers/timeout.py +87 -0
- snakeorm/dto/__init__.py +55 -0
- snakeorm/dto/read.py +231 -0
- snakeorm/dto/region.py +326 -0
- snakeorm/dto/resolve.py +354 -0
- snakeorm/dto/spec.py +201 -0
- snakeorm/expressions/__init__.py +88 -0
- snakeorm/expressions/conditional.py +178 -0
- snakeorm/expressions/expression.py +810 -0
- snakeorm/expressions/functions.py +71 -0
- snakeorm/expressions/keys.py +299 -0
- snakeorm/expressions/paths.py +43 -0
- snakeorm/expressions/scalar.py +293 -0
- snakeorm/expressions/window.py +248 -0
- snakeorm/fields/__init__.py +68 -0
- snakeorm/fields/check.py +79 -0
- snakeorm/fields/column.py +316 -0
- snakeorm/fields/enum.py +64 -0
- snakeorm/fields/index.py +46 -0
- snakeorm/fields/relationship.py +738 -0
- snakeorm/fields/typed.py +736 -0
- snakeorm/helpers/__init__.py +1 -0
- snakeorm/helpers/annotations.py +21 -0
- snakeorm/helpers/inheritance.py +41 -0
- snakeorm/helpers/pyliteral.py +52 -0
- snakeorm/helpers/pytype.py +50 -0
- snakeorm/introspection/__init__.py +13 -0
- snakeorm/introspection/base.py +30 -0
- snakeorm/introspection/drift.py +78 -0
- snakeorm/introspection/models.py +633 -0
- snakeorm/introspection/mysql.py +367 -0
- snakeorm/introspection/postgres.py +397 -0
- snakeorm/introspection/sqlite.py +217 -0
- snakeorm/introspection/unsupported.py +94 -0
- snakeorm/linker/__init__.py +3 -0
- snakeorm/linker/linker.py +521 -0
- snakeorm/metadata/__init__.py +41 -0
- snakeorm/metadata/check.py +37 -0
- snakeorm/metadata/column.py +201 -0
- snakeorm/metadata/enum_storage.py +33 -0
- snakeorm/metadata/fk_action.py +19 -0
- snakeorm/metadata/foreign_key.py +26 -0
- snakeorm/metadata/index.py +43 -0
- snakeorm/metadata/index_method.py +19 -0
- snakeorm/metadata/int_size.py +20 -0
- snakeorm/metadata/json_storage.py +21 -0
- snakeorm/metadata/polymorphic.py +30 -0
- snakeorm/metadata/primary_key.py +23 -0
- snakeorm/metadata/relationship.py +60 -0
- snakeorm/metadata/relationship_kind.py +43 -0
- snakeorm/metadata/routine.py +26 -0
- snakeorm/metadata/server_default.py +20 -0
- snakeorm/metadata/table.py +97 -0
- snakeorm/metadata/table_kind.py +23 -0
- snakeorm/metadata/trigger.py +62 -0
- snakeorm/metadata/type_params.py +348 -0
- snakeorm/migration/__init__.py +88 -0
- snakeorm/migration/asyncrunner.py +168 -0
- snakeorm/migration/autodetect.py +96 -0
- snakeorm/migration/ddl.py +1172 -0
- snakeorm/migration/diff.py +701 -0
- snakeorm/migration/loader.py +77 -0
- snakeorm/migration/operations.py +952 -0
- snakeorm/migration/planner.py +78 -0
- snakeorm/migration/realize.py +420 -0
- snakeorm/migration/renames.py +153 -0
- snakeorm/migration/render.py +1095 -0
- snakeorm/migration/runner.py +314 -0
- snakeorm/migration/squash.py +60 -0
- snakeorm/migration/state.py +107 -0
- snakeorm/model.py +162 -0
- snakeorm/py.typed +0 -0
- snakeorm/query/__init__.py +9 -0
- snakeorm/query/compound.py +288 -0
- snakeorm/query/join_kind.py +19 -0
- snakeorm/query/joined.py +143 -0
- snakeorm/query/query.py +1001 -0
- snakeorm/query/recursive.py +244 -0
- snakeorm/registry/__init__.py +5 -0
- snakeorm/registry/by_module.py +163 -0
- snakeorm/registry/registry.py +166 -0
- snakeorm/session/__init__.py +8 -0
- snakeorm/session/asyncsession.py +638 -0
- snakeorm/session/coercion.py +306 -0
- snakeorm/session/factory.py +29 -0
- snakeorm/session/guards.py +269 -0
- snakeorm/session/isolation.py +23 -0
- snakeorm/session/mapper.py +213 -0
- snakeorm/session/planning.py +815 -0
- snakeorm/session/retry.py +96 -0
- snakeorm/session/session.py +871 -0
- snakeorm/session/shared.py +355 -0
- snakeorm/sql/__init__.py +18 -0
- snakeorm/sql/adapt.py +104 -0
- snakeorm/sql/aggregate.py +232 -0
- snakeorm/sql/condition.py +359 -0
- snakeorm/sql/delete.py +46 -0
- snakeorm/sql/insert.py +149 -0
- snakeorm/sql/joins.py +158 -0
- snakeorm/sql/pk_subquery.py +54 -0
- snakeorm/sql/refs.py +19 -0
- snakeorm/sql/resolver.py +28 -0
- snakeorm/sql/select.py +175 -0
- snakeorm/sql/update.py +82 -0
- snakeorm/sql/value.py +552 -0
- snakeorm/times.py +276 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: snake-orm
|
|
3
|
+
Version: 0.1.0b1
|
|
4
|
+
Summary: Fully typed deep relationship navigation in Python. No codegen, no type-checker plugin.
|
|
5
|
+
Project-URL: Homepage, https://github.com/velezanthony/snake-orm
|
|
6
|
+
Project-URL: Documentation, https://velezanthony.github.io/snake-orm/
|
|
7
|
+
Project-URL: Source, https://github.com/velezanthony/snake-orm
|
|
8
|
+
Project-URL: Issues, https://github.com/velezanthony/snake-orm/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/velezanthony/snake-orm/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Anthony Velez Tapia <velezanthony2000@gmail.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: dataclasses,mysql,orm,postgresql,sql,sqlite,typing
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
24
|
+
Classifier: Topic :: Database
|
|
25
|
+
Classifier: Topic :: Database :: Front-Ends
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.11
|
|
29
|
+
Requires-Dist: psycopg2-binary>=2.9.9
|
|
30
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
31
|
+
Provides-Extra: async
|
|
32
|
+
Requires-Dist: psycopg[binary]>=3.2; extra == 'async'
|
|
33
|
+
Provides-Extra: mysql
|
|
34
|
+
Requires-Dist: pymysql>=1.1; extra == 'mysql'
|
|
35
|
+
Provides-Extra: otel
|
|
36
|
+
Requires-Dist: opentelemetry-api>=1.27; extra == 'otel'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# 🐍 SnakeORM
|
|
40
|
+
|
|
41
|
+
[](https://github.com/velezanthony/snake-orm/actions/workflows/ci.yml)
|
|
42
|
+

|
|
43
|
+

|
|
44
|
+

|
|
45
|
+
[](LICENSE)
|
|
46
|
+
|
|
47
|
+
**Fully typed deep relationship navigation in Python. No codegen. No type-checker plugin.**
|
|
48
|
+
|
|
49
|
+
Docs (English and Spanish): <https://velezanthony.github.io/snake-orm/>
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
Truck.maker.nation.name == "España" # SnakeExpr[str] -> SnakeCondition
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```sql
|
|
56
|
+
SELECT t0."id", t0."model", t0."maker_id" FROM "public"."trucks" AS t0
|
|
57
|
+
JOIN "public"."makers" AS t1 ON t0."maker_id" = t1."id"
|
|
58
|
+
JOIN "public"."nations" AS t2 ON t1."nation_id" = t2."id"
|
|
59
|
+
WHERE t2."name" = %s
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Mypy checks it. Pyright checks it. Pylance autocompletes it. The Django equivalent,
|
|
63
|
+
`filter(maker__nation__name="España")`, is a string: no autocomplete, no check, and renaming
|
|
64
|
+
`nation` fails in production.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Install
|
|
69
|
+
|
|
70
|
+
Requires Python 3.11+. SQLite ships with the standard library, so nothing needs to be running to
|
|
71
|
+
start.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install snake-orm==0.1.0b1 # or: pip install --pre snake-orm
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The distribution is `snake-orm` and the package is `snakeorm`: `import snakeorm`.
|
|
78
|
+
|
|
79
|
+
The version is a **beta**, and the pin is the point: a preliminary is not picked up by a plain
|
|
80
|
+
`pip install snake-orm`, so nobody upgrades into it by accident while the API is still moving.
|
|
81
|
+
|
|
82
|
+
From a checkout, to work on the ORM itself:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
uv sync --all-extras --all-groups
|
|
86
|
+
uv run pytest # suite
|
|
87
|
+
uv run mypy . # must pass
|
|
88
|
+
uv run ruff check . # must pass
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
[Installation](docs/users/getting-started/installation.md) →
|
|
92
|
+
[first model](docs/users/getting-started/first-model.md) →
|
|
93
|
+
[migrations](docs/users/getting-started/migrations.md).
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
@snake_model(table="makers")
|
|
97
|
+
class Maker(SnakeModel):
|
|
98
|
+
id: SnakeColumn[int] = snake_auto()
|
|
99
|
+
name: SnakeColumn[str] = snake_str(unique=True)
|
|
100
|
+
nation_id: SnakeColumn[int] = snake_int()
|
|
101
|
+
nation: SnakeToOne[Nation] = snake_to_one(nation_id)
|
|
102
|
+
trucks: SnakeToMany[Truck] = snake_to_many("maker")
|
|
103
|
+
|
|
104
|
+
snake_link() # once, after importing every model
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The type always comes from the annotation. `snake_column()` only adds SQL information.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
uv run snakeorm makemigrations --models myapp.models --name initial
|
|
111
|
+
uv run snakeorm migrate --models myapp.models --dsn "host=... dbname=..."
|
|
112
|
+
uv run snakeorm rollback --models myapp.models --dsn "..."
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## The mechanism
|
|
118
|
+
|
|
119
|
+
**Recursive descriptors** whose `__get__` returns a different type depending on the access:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
user.car.name # instance -> the value -> str
|
|
123
|
+
User.car.brand.name == "x" # class -> a SQL expression -> SnakeCondition
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`User.car` returns `type[Car]`, so `.brand.name` re-triggers the class overload of `Car`'s
|
|
127
|
+
descriptors. `@dataclass_transform` on the decorator types `__init__`. It is the manual equivalent
|
|
128
|
+
of TypeScript's mapped types.
|
|
129
|
+
|
|
130
|
+
The type system is the single source of truth: the class is compiled **once** into an immutable
|
|
131
|
+
metadata graph, and the runtime never reflects on it again.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Why Django returns `Any`
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
User.objects.annotate(num_posts=Count("posts"))
|
|
139
|
+
user.num_posts # -> Any
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`annotate` returns "a `User` **plus** a `num_posts: int`". That is an intersection type. TypeScript
|
|
143
|
+
has it; Python does not. So there are three paths, and only three:
|
|
144
|
+
|
|
145
|
+
| Path | Dynamic names | Real typing | IntelliSense |
|
|
146
|
+
|---|---|---|---|
|
|
147
|
+
| `__getattr__ -> Any` | ✅ | ❌ | ❌ |
|
|
148
|
+
| Declared names | ❌ | ✅ | ✅ |
|
|
149
|
+
| Type-checker plugin | ✅ | ✅ | partial |
|
|
150
|
+
|
|
151
|
+
Django took the first and patched it with the third (`django-stubs`). SnakeORM forbids the third by
|
|
152
|
+
thesis and takes the second, with a typed escape hatch.
|
|
153
|
+
|
|
154
|
+
`Any` is not typing, it is switching the checker off:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
def __getattr__(self, name: str) -> Any: ...
|
|
158
|
+
u.agg.count_children * 2 # mypy: 0 errors
|
|
159
|
+
other: str = u.agg.count_children # mypy: 0 errors <- same value, as a str
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`object` keeps the dynamic name and wakes the checker up:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
def __getattr__(self, name: str) -> object: ...
|
|
166
|
+
u.agg.count_children * 2 # error: unsupported operand types for *
|
|
167
|
+
count: int = cast("int", u.agg.count_children) # explicit, and signed with your name
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Illegal states you cannot write
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
SnakeQuery(Nation).filter(Nation.makers.name == "SEAT")
|
|
176
|
+
# error: "SnakeCollection[Maker]" has no attribute "name" [attr-defined]
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
In Django that compiles, runs and silently duplicates rows — which is what `.distinct()` is for. A
|
|
180
|
+
to-many exposes **collection operations**, not the child's columns:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
q.filter(Nation.makers.any(Maker.name == "SEAT")) # any? -> correlated EXISTS
|
|
184
|
+
q.filter(~Nation.makers.any()) # none? -> NOT EXISTS
|
|
185
|
+
q.filter(Nation.makers.count() > 3) # how many? -> scalar subquery
|
|
186
|
+
q.include(Nation.makers) # load them -> select-in, 2 queries
|
|
187
|
+
|
|
188
|
+
q.include(SnakePrefetch(Nation.makers).then(Maker.trucks))
|
|
189
|
+
# one query per LEVEL (root + makers + trucks = 3), never one per parent
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
One row per parent. `DISTINCT` is never needed.
|
|
193
|
+
|
|
194
|
+
```sql
|
|
195
|
+
-- Nation.makers.any(Maker.trucks.any(Truck.model == "Ibiza"))
|
|
196
|
+
SELECT "id", "name" FROM "public"."nations" WHERE EXISTS (
|
|
197
|
+
SELECT 1 FROM "public"."makers" AS e0 WHERE e0."nation_id" = "nations"."id" AND EXISTS (
|
|
198
|
+
SELECT 1 FROM "public"."trucks" AS e1 WHERE e1."maker_id" = e0."id" AND e1."model" = %s))
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
> **Implicit when the answer is unique. Explicit when there is more than one correct answer.**
|
|
202
|
+
|
|
203
|
+
A to-one never changes the row count, so the `JOIN` is inferred. A to-many does, so the ORM does not
|
|
204
|
+
guess.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## What the thesis gives for free
|
|
209
|
+
|
|
210
|
+
**No silent N+1.** An unloaded relation raises instead of querying:
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
truck.maker
|
|
214
|
+
# SnakeRelationshipNotLoaded: Relation 'maker' was not loaded.
|
|
215
|
+
# Use .include(Truck.maker) in the query.
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**No `F()`.** Class access already is an expression:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
session.update_where(query, [(Counter.views, Counter.views + 1)])
|
|
222
|
+
# UPDATE "counters" SET "views" = ("views" + %s) WHERE ...
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Pairs, not a dict: `SnakeExpr` is unhashable because its `__eq__` returns a `SnakeCondition`.
|
|
226
|
+
|
|
227
|
+
**Many-to-many crosses a real model**, never an implicit table:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
tags: SnakeToMany["Tag"] = snake_to_many_through(through="PostTag", via="post", to="tag")
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The bridge is an ordinary model, so extra columns on it are ordinary fields from day one.
|
|
234
|
+
|
|
235
|
+
**A bulk `UPDATE`/`DELETE` uses the filter and nothing else.** With no `WHERE` it is refused, and
|
|
236
|
+
so is any other knob you set on the same query — `limit()`, `order_by()`, `only()`. Dropping what
|
|
237
|
+
you asked for would answer a different question without saying so: select the rows first, then
|
|
238
|
+
write by primary key.
|
|
239
|
+
|
|
240
|
+
**`annotate()` counts with a correlated subquery, not a `LEFT JOIN`** — a parent with no children
|
|
241
|
+
comes out `0` instead of disappearing.
|
|
242
|
+
|
|
243
|
+
**A view is a read-only model, and navigable.** `@snake_view` maps a `VIEW` with typed columns,
|
|
244
|
+
navigable both ways; `session.add/update/delete` reject it in the **type**, because writing requires
|
|
245
|
+
a `SnakeModel`. `CreateView`/`AlterView`/`DropView` live in the migrations.
|
|
246
|
+
|
|
247
|
+
**Migration history is `.py`**, because `python_type` is a Python `type`. JSON would need a
|
|
248
|
+
name↔type registry: a second type system, parallel to Python's and worse.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Typed annotations
|
|
253
|
+
|
|
254
|
+
Declare the result class — you choose the name, Python chooses the type:
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
@snake_result
|
|
258
|
+
class RealmStats(SnakeResult[Realm]):
|
|
259
|
+
realm: Realm
|
|
260
|
+
forge_count: int
|
|
261
|
+
|
|
262
|
+
rows = session.annotate(query, RealmStats, forge_count=Realm.forges.count())
|
|
263
|
+
rows[0].forge_count # int, with IntelliSense
|
|
264
|
+
rows[0].realm.name # str, navigation intact
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
For genuinely dynamic names, the escape hatch is explicit:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
count = cast("int", realm.aggregate.forge_count) # object -> the cast is mandatory
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Without the cast it does not compile. Without annotating, it raises `SnakeAggregateNotLoaded` naming
|
|
274
|
+
the aggregates it does have.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## What is inside
|
|
279
|
+
|
|
280
|
+
| | |
|
|
281
|
+
|---|---|
|
|
282
|
+
| **Queries** | filter · order/limit/offset · group by/having · aggregates · `annotate` · explicit joins · `include` (to-one and to-many) · deep navigation · `.any()` · correlated subqueries · composite `IN` · `only`/`defer` · `iterate` (server cursor) · `for_update` · `raw` |
|
|
283
|
+
| **SQL** | window functions with frame · `UNION`/`INTERSECT`/`EXCEPT` · `WITH RECURSIVE` · `CASE`/`COALESCE`/`NULLIF` · text, date and math functions · `json_get` · `ILIKE` |
|
|
284
|
+
| **Writes** | insert/update/delete · upsert · bulk · `RETURNING` · savepoints · isolation levels · retry on transient conflict · `refresh` |
|
|
285
|
+
| **Schema** | composite PK and FK · polymorphic inheritance · views · triggers · indexes (partial, functional, `GIN`/`GIST`/`BRIN`) · checks · comments · enums · custom converters |
|
|
286
|
+
| **Engines** | PostgreSQL · MySQL/MariaDB · SQLite, all first class · `Cap` catalogue (`Full`/`Degraded`/`Nope`) · sync and async drivers · pool with `pre_ping`/`recycle`/timeout · statement timeout · `EXPLAIN` |
|
|
287
|
+
| **Migrations** | autodetected diff · atomic runner · `RebuildTable` for SQLite · `RunPython` with reverse · squash · cross-app dependencies · drift detection against the live database |
|
|
288
|
+
| **Tooling** | introspection and scaffold for the three engines · debug panel (`ssr`, `envelope`, `timing`, `sidecar`, `otel`) · index advisor · WSGI/ASGI/Django contrib · CLI |
|
|
289
|
+
|
|
290
|
+
Row by row, with links to the code, the test and the page: [feature index](docs/features.md).
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Architecture
|
|
295
|
+
|
|
296
|
+
```
|
|
297
|
+
Python class → Model Compiler → immutable metadata graph
|
|
298
|
+
↓
|
|
299
|
+
SQL · migrations · query · session · CLI
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
```
|
|
303
|
+
decorators/ metadata/ compiler/ registry/ linker/
|
|
304
|
+
query/ expressions/ sql/ dialects/ drivers/ session/ migration/ cli/
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Two axes that never mix: the **dialect** decides how SQL is *written* (placeholders, quoting,
|
|
308
|
+
`RETURNING`, `ON CONFLICT`); the **driver** decides how it is *executed*. Models and graph are
|
|
309
|
+
engine-agnostic — anything Postgres-specific reaching the model is a bug.
|
|
310
|
+
|
|
311
|
+
SQL is always parameterised: emission returns `(sql, params)` and values never enter the string.
|
|
312
|
+
That kills injection, and it is what makes multi-engine possible.
|
|
313
|
+
|
|
314
|
+
Async reuses the whole core: SQL generation does not execute, so it has no colour.
|
|
315
|
+
|
|
316
|
+
Details in [architecture](docs/contributors/architecture.md); how to work here in
|
|
317
|
+
[CONTRIBUTING](CONTRIBUTING.md).
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## The type contract is a test
|
|
322
|
+
|
|
323
|
+
In `test/typing/`:
|
|
324
|
+
|
|
325
|
+
- `cases_positive.py` — what **must** type, with `assert_type`.
|
|
326
|
+
- `cases_negative.py` — what **must not compile**, each line carrying its `# EXPECT: <code>`.
|
|
327
|
+
- The runner requires mypy to report exactly those errors on exactly those lines, and pyright to
|
|
328
|
+
reject the same ones.
|
|
329
|
+
|
|
330
|
+
Break `Truck.maker.nation.name` and the suite fails.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Deliberately not built
|
|
335
|
+
|
|
336
|
+
- **Identity map and unit of work.** Two queries to the same row return two objects. Writes are
|
|
337
|
+
explicit; nothing is flushed behind your back.
|
|
338
|
+
- **Lazy loading.** Touching an unloaded relation raises. This is what makes N+1 impossible by
|
|
339
|
+
default.
|
|
340
|
+
- **Joined-table inheritance.** Single table with a discriminator covers the polymorphism, and its
|
|
341
|
+
price is one rule: a child's own columns must allow `NULL`.
|
|
342
|
+
- **Model default ordering.** A hidden `ORDER BY` you did not write.
|
|
343
|
+
|
|
344
|
+
## Known limits
|
|
345
|
+
|
|
346
|
+
- `storage=NATIVE` in `snake_enum` (Postgres `CREATE TYPE`) is not built: `ALTER TYPE ... ADD VALUE`
|
|
347
|
+
has no inverse, so its `down_sql` would be a lie. The default `CHECK` is reversible.
|
|
348
|
+
- CHECKs are declared outside the class body (`snake_checks(User, ...)`). Inside it, `__set_name__`
|
|
349
|
+
has not run and the column does not know its own name.
|
|
350
|
+
- An expression does not carry its owning model in the type: `Maker.id` and `Truck.id` are twins to
|
|
351
|
+
the checker. Encoding the owner would break deep navigation and condition composition; where it
|
|
352
|
+
matters (aggregates, `.any()`) it is validated at runtime.
|
|
353
|
+
- No lazy loading, full-text search, JSON containment operators or array operators with a typed API.
|
|
354
|
+
|
|
355
|
+
The complete, current list is [known limits](docs/users/reference/limits.md) — part of the contract,
|
|
356
|
+
not a list of apologies.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Status
|
|
361
|
+
|
|
362
|
+
Not published on PyPI. The distribution is named `snake-orm`, the import name is
|
|
363
|
+
`snakeorm`; both are explained in [release](docs/contributors/release.md).
|
|
364
|
+
|
|
365
|
+
Everything above is implemented and tested against real PostgreSQL, MySQL/MariaDB and SQLite. It has
|
|
366
|
+
not run in production yet, and that is the one thing a repository cannot give itself.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
snakeorm/__init__.py,sha256=VCNI0yKRf0deHAoUKQqYsNG3h0_c9dGuDsiq5ivMoGo,12263
|
|
2
|
+
snakeorm/advisor.py,sha256=vOJH4tkqt7tkTPKnkDDy_JJmX1SqiupOYqbMbJbaO2k,4879
|
|
3
|
+
snakeorm/connection.py,sha256=jQeT7oUPGVagwZewX9AKeWtkSI1yKlHRbeCzSVV_5Y4,11396
|
|
4
|
+
snakeorm/model.py,sha256=cas8xzBxs1TukqlKbU3d-_hEd80oLkp2nhmOHdRBk1U,6166
|
|
5
|
+
snakeorm/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
snakeorm/times.py,sha256=JhQmAhX4QwUIwWUS6juxnIeeZM32okSMgPOR4nng_2Y,12421
|
|
7
|
+
snakeorm/cli/__init__.py,sha256=qHOfkNqMYdYhLamwFgr-gvvr-HvR6gdcIfI1LZiE87E,200
|
|
8
|
+
snakeorm/cli/app.py,sha256=WYy-8u4LThLakUpaFXtpZYrCpWTa8yDaOodESu53HiU,42046
|
|
9
|
+
snakeorm/cli/discovery.py,sha256=67lRKaBRe8MNF11_o2aRXELlu8TnOYxA84g48a95jvE,7796
|
|
10
|
+
snakeorm/cli/hooks.py,sha256=WaYZfxHAKYoafRtUBm3cZTrBnssf-4SWon-dr8gGP-Q,3847
|
|
11
|
+
snakeorm/compiler/__init__.py,sha256=nw5qKO4xdSFNwNse6lgnCKoOIKUGnonq-3-97Ytfuxc,140
|
|
12
|
+
snakeorm/compiler/compiler.py,sha256=T-MqoF6uOjS9JSzbMuhYJu54VW-302xkNRS-yfRAua4,15238
|
|
13
|
+
snakeorm/contrib/__init__.py,sha256=aCuxfOM9wkRVZC8VKGYvw8sasm_z-XLjeD1tl_5mgf4,986
|
|
14
|
+
snakeorm/contrib/asgi.py,sha256=S5Awsdd5C7wjkk3gJOgfNUGk5dnIZxsi1q0CBbopJwQ,7321
|
|
15
|
+
snakeorm/contrib/config.py,sha256=5lG_7Ld8uheH8nBwad2WDbkMtTz9IuEPytco9qMXS9U,11962
|
|
16
|
+
snakeorm/contrib/deliver.py,sha256=0wivvwUkqBeJcXtCCTm8mn_PhLgLmbWEmuJYzMu9DR0,10998
|
|
17
|
+
snakeorm/contrib/django.py,sha256=YX52bLi_Pf2GK637e8p018S0Nv_6TWOrz4IWjHIttOg,10262
|
|
18
|
+
snakeorm/contrib/sidecar.py,sha256=W4TgjDrNBrhwreJOhVV8Ag-miQL269iKmCs1old7wWU,1589
|
|
19
|
+
snakeorm/contrib/wsgi.py,sha256=Rq6qz5dR9bZ2tucza6dT-3i3MgFF0db43StBx2lwb5s,6828
|
|
20
|
+
snakeorm/core/__init__.py,sha256=lbXm3QM16-tfI8sifIrpf5LqiCDV6p8NBX5jnznUQSY,92
|
|
21
|
+
snakeorm/core/config.py,sha256=70E-avkF_jbjDBslpGV9WXuTl-9hW1ajxNWNw3rY03Q,7416
|
|
22
|
+
snakeorm/core/converters.py,sha256=7CxxfB-QwwiOHYC5lZJtB37cWQIOYgMWSLC6YNkH_aM,8409
|
|
23
|
+
snakeorm/core/exceptions.py,sha256=RAanQWdrIn5-x_usmUcR6x8MGRvAGnfhggFITucqtGI,7308
|
|
24
|
+
snakeorm/core/placement.py,sha256=iMddxsgiRk8yQXRZ4PsdO-N0GjUFByOKlZf2URQnUnI,637
|
|
25
|
+
snakeorm/core/sentinels.py,sha256=dNdylIDWEKH5UnPxWRaCy34N2CET3vondYR7Wt1b2VE,1427
|
|
26
|
+
snakeorm/core/signals.py,sha256=imfSa8jXds6afmL7NR6nxjdOK6Zu2RCEgIv3KxAIh80,4164
|
|
27
|
+
snakeorm/debug/__init__.py,sha256=Psh3Al0cjY--saRBsl4xhHmAUtjILJpugrVkvNWed8o,2716
|
|
28
|
+
snakeorm/debug/capture.py,sha256=PAc3ZCjIichv3pjLycMfKkDa8K41ihOansX3IM_fxew,8720
|
|
29
|
+
snakeorm/debug/channel.py,sha256=q2P0IFj_ziLOzi0zW4WcAXO3oZU7Eq23Fj8RzmUX4cY,6151
|
|
30
|
+
snakeorm/debug/collector.py,sha256=SCyUGd3n9M-NU7l0OYRlB_HtghpAi3w-ogKaHeK9RCo,5608
|
|
31
|
+
snakeorm/debug/config.py,sha256=LBkUfOBTaa6S2KI99PxSWdFLTt2WVcpLz42yeLQOOpc,6853
|
|
32
|
+
snakeorm/debug/html.py,sha256=S-YyE9sJChUvnDN8ff3vozjAyBxv6yrx1vp_oKJVc3c,23941
|
|
33
|
+
snakeorm/debug/matrix.py,sha256=gmhBT5LlAVos_Zb3qMZZ7N3KQG1chz2mCMo_o1HCCcY,2294
|
|
34
|
+
snakeorm/debug/origin.py,sha256=3JpPNl0InMHKEnMUBEM3edl_Sk1rJuUyL9D-YuVE3Wg,1870
|
|
35
|
+
snakeorm/debug/record.py,sha256=FDmxwN8MdStwshCWYpOXXzt57LhyG51hc80E2AnZ-ts,2263
|
|
36
|
+
snakeorm/debug/report.py,sha256=_ZbIxIxlRvQnY4-yu1SpYXRRCId1Ogta7URW8NMZqL0,12990
|
|
37
|
+
snakeorm/debug/testing.py,sha256=mnM2DqPfcK7srriPDk12Agj5DgjHSPvo735CiTv6HIQ,809
|
|
38
|
+
snakeorm/debug/assets/css/panel.css,sha256=9qR1U9kBrUxqtyP-vYpCPRtF9js2XSIXdbS8B23FnXc,11719
|
|
39
|
+
snakeorm/debug/assets/css/snake_orm_app.css,sha256=SHNdOhb0Xgn4f2O3Pw-AZInj5qhDYH9L1VZyfbu1M3I,5594
|
|
40
|
+
snakeorm/debug/assets/img/icon.svg,sha256=Z91qpA0khEkUKDCkgNzPjMU9Fl507oAkE9AU2WF947w,368
|
|
41
|
+
snakeorm/debug/assets/js/history.js,sha256=7stu6cXXKeLDA_u3r_Jn1XYy5m0_DCLiEbbLJiMpYwY,17549
|
|
42
|
+
snakeorm/debug/assets/js/language.js,sha256=FkzSb1jkUWra0e0e8ccH0gELQGyL4jLiMuGO5dfGHrU,17773
|
|
43
|
+
snakeorm/debug/assets/js/panel.js,sha256=Dwh3raH5TpJHXsMrXHLy2yMyXfWrfWbVu4HRvdAorCk,9436
|
|
44
|
+
snakeorm/debug/assets/js/snake_orm_app.js,sha256=0X_o2rmrK0mKrgncPSpl0o9wYM4IX9L9P3LYQFgTRmc,3430
|
|
45
|
+
snakeorm/debug/assets/pages/cli.html,sha256=PI2S_J5zb0u-ZxyScc49J7UvoHk8OKtgoyYAg3CKXDM,1809
|
|
46
|
+
snakeorm/debug/assets/pages/config.html,sha256=mPQgpnz9UbHiuj8WxU4bygE1qDbzpFougy1jYqjXfbs,2905
|
|
47
|
+
snakeorm/debug/assets/pages/database_first.html,sha256=5kBwsqxEL1bhlYbDHRjTDoAviqKRecc2UPr6oGFlFZ8,2334
|
|
48
|
+
snakeorm/debug/assets/pages/help.html,sha256=0GJgfTk0Zdcl1W2YkaZa6ollYwkiiZeWm51Fh2UVp_o,1193
|
|
49
|
+
snakeorm/debug/assets/pages/history.html,sha256=azGLQd4kf3h3kAmaw7KG7e5akVLKkFKjYpD-gISn_Bk,3088
|
|
50
|
+
snakeorm/debug/otel/__init__.py,sha256=r5VCFowlqMWn1uPP7Lhg7GOhhSa8Kne6iZADRRBzXhM,4263
|
|
51
|
+
snakeorm/debug/otel/context.py,sha256=3yWFEUNvaP8txliQaVxgpNLWLYIwdahQ4Vkb354A620,1576
|
|
52
|
+
snakeorm/debug/otel/exporter.py,sha256=2r0oAMy7vFCf1cz12BXhgRlBRr-_p5NopcW7GWL1Qk8,10599
|
|
53
|
+
snakeorm/debug/otel/payload.py,sha256=El0Z-S9Nn2MT34ZL8kvB2u-5M5mfZtn3Rq9177s6CKg,3558
|
|
54
|
+
snakeorm/debug/otel/report.py,sha256=ZnH4vggM4lZm5diYdQiEjFFTlXiskoJMRXVoktan1Bk,1301
|
|
55
|
+
snakeorm/debug/otel/spans.py,sha256=WRl40__Rt3cO7ScRAtHZyK6VRmQGeUOAN7FvmxAGDv0,15230
|
|
56
|
+
snakeorm/debug/otel/summary.py,sha256=MQ_I4o6KXju1yq9GqFYW4MkB1XbBD6UoUze4EhO48m4,2473
|
|
57
|
+
snakeorm/decorators/__init__.py,sha256=5btwYRaemuxOllc23Y32en69Tbq5XCpojzlBB7SfbKU,770
|
|
58
|
+
snakeorm/decorators/abstract.py,sha256=q0w7r_mSZ_NfuuQtq9jFSidxXQNx4uUr6aKdDBnEWlA,968
|
|
59
|
+
snakeorm/decorators/db_first.py,sha256=NjIpYI35wgV2NyqH2UrLJ7YSSN_5QDNvAOkSMTamNA8,3736
|
|
60
|
+
snakeorm/decorators/function.py,sha256=LgjnMpxIeFvhp_RKwSQMsahcCaVJc9dNDfKgll-O9Uk,1224
|
|
61
|
+
snakeorm/decorators/model.py,sha256=hJk4YdfSHHQPK23L7_fX3VFRbEGk4iZwXlxuorNAdgo,10806
|
|
62
|
+
snakeorm/decorators/polymorphic.py,sha256=l6Nmsf0dxW2dFZTnL5cNj_udFsXDtWDcZ6JuOnrznds,5874
|
|
63
|
+
snakeorm/decorators/result.py,sha256=3UKU0mn5RpMJdmCjVnUjzCHRB_x3XnKOb15pENIRgGU,6926
|
|
64
|
+
snakeorm/decorators/row.py,sha256=iSgwDYGNVh9o0RW2flOa2Wi6OM_hYn9JgtbaGhXYFnE,3158
|
|
65
|
+
snakeorm/decorators/trigger.py,sha256=RMfDJcljVZdZWZ8xLc2OnsoO2-UsN_e0G95oeqoIGFA,1658
|
|
66
|
+
snakeorm/decorators/view.py,sha256=xX7M1DwNuZNL3ClNjV5_Xdbvqcf__5E1QyZ71MXw7MM,5579
|
|
67
|
+
snakeorm/dialects/__init__.py,sha256=AzkKHZjLFB04rrOhoi5rhz24_BnIM7WlWMDB2_7Mixg,343
|
|
68
|
+
snakeorm/dialects/base.py,sha256=YLpulEJo31mRXFLBgjkSFL069aUx8ZvRASchesBjH_Y,17603
|
|
69
|
+
snakeorm/dialects/capabilities.py,sha256=Vh426O-8cjJOJ95OewGwjTXprZ7n_Z0kWStLM9ve9Ks,27715
|
|
70
|
+
snakeorm/dialects/literals.py,sha256=WzYNiWXajmzxBAdYLRA-fNFHn3BJEhx0J4FW3oYhfBA,2349
|
|
71
|
+
snakeorm/dialects/matrix.py,sha256=OSZMpnF9bP5JETNp34scaPMtlLA1gu0wJUKyykhAVBI,18429
|
|
72
|
+
snakeorm/dialects/mysql.py,sha256=gRbn7t8uye3QCv1O6FTZNdhPkXiG3lod-1f4zeETjRM,25782
|
|
73
|
+
snakeorm/dialects/postgres.py,sha256=EZ3IXPS8aHEhNovNH3XXjK8f3lQBy9DAFEXayAnYxAk,23384
|
|
74
|
+
snakeorm/dialects/sqlite.py,sha256=dRkZx6RNbf49wU6nTiYbmpqgQ9t7J8Xv2bfgSg_Y1D4,19777
|
|
75
|
+
snakeorm/drivers/__init__.py,sha256=5Z3MuxwrgJWbcd4hNnYHclmT5vA44glTPcCcup6nmeM,1221
|
|
76
|
+
snakeorm/drivers/asyncbase.py,sha256=yHLM2QZpeJrLeiYBBmi0f57OXR7-E7gyNgpt3KJ1gBw,2786
|
|
77
|
+
snakeorm/drivers/asyncdecorators.py,sha256=cuQ5OdEUlpLEw5r717v7cDKufH3epEBoF3ywrlPLkCo,10094
|
|
78
|
+
snakeorm/drivers/asyncpool.py,sha256=Z-ASwurX1PCRaf0B-D0OOT9FS1ziSqrw1MAcpVwU0U4,9976
|
|
79
|
+
snakeorm/drivers/asyncpsycopg.py,sha256=K0FrxStyPO9k7Z3Pa2HdlaYjYsThlC62YlLsrNKqT9o,5042
|
|
80
|
+
snakeorm/drivers/asyncpymysql.py,sha256=dc879K0ZIqQzuLf6A-XY4ZkkOmIYN65LUBK54csXSJ0,2408
|
|
81
|
+
snakeorm/drivers/asyncsqlite.py,sha256=oVWqUaJgQyy2Xu_URBiOVDpDmTm-9jlsuj8DjQ4p7zU,1426
|
|
82
|
+
snakeorm/drivers/base.py,sha256=zRO9Q9yNp-PkbYbIyZCEYpdqCIWUq2DesOZ5uolWQU4,3033
|
|
83
|
+
snakeorm/drivers/failures.py,sha256=JWLBdsEfY82l40DD6vAaiwCEU38y-TQSSyGW96ftAl4,8156
|
|
84
|
+
snakeorm/drivers/logging.py,sha256=MUpL_7LVjhAudswAwNop6cM_DwCMyMik9bRrLv6AyUc,7217
|
|
85
|
+
snakeorm/drivers/pool.py,sha256=6gtfssbDJX13_dTf5lvq3Mgk2EH-x2chMxWtK3wGNAI,16643
|
|
86
|
+
snakeorm/drivers/psycopg.py,sha256=BgCfBIg10KdS5aqKJ3tvd7hpLsbF2uM80JMsX-LHG4s,7705
|
|
87
|
+
snakeorm/drivers/pymysql.py,sha256=zdulMiJE-OjGzC_FLxDEN-ZhGuAtqt3vpG0W0daw0Jw,7292
|
|
88
|
+
snakeorm/drivers/savepoints.py,sha256=Q0flSUCALbm0TwfEt5rc7yDsfIKbvy41nGJkNYAHrc0,2353
|
|
89
|
+
snakeorm/drivers/sqlite.py,sha256=NEVH-GYQlCrwqhdyY2vC7xaCax-HG8FHivYvrgvT0bk,7730
|
|
90
|
+
snakeorm/drivers/threaded.py,sha256=bieXQWZWlOPC7MIsA61Reczn0fz283zZOSxmU-lhH9c,6877
|
|
91
|
+
snakeorm/drivers/timeout.py,sha256=_6f5Bd2ktrWcEdhcjf98_G5alZw7UZlof-kmsIVvzYg,3808
|
|
92
|
+
snakeorm/dto/__init__.py,sha256=kazOsbp0VR8rxNaP1ezQSXj22yh3rK67SPIQpwXM5l8,2126
|
|
93
|
+
snakeorm/dto/read.py,sha256=ZdPo92JpvYY24-GgY32YqmJrLSH-x8ZFYmS-awbpmlE,10174
|
|
94
|
+
snakeorm/dto/region.py,sha256=Sdo9A6iew3uyodOqQ5CXcMV5BviQozqTWV7o4Y1P3yw,13903
|
|
95
|
+
snakeorm/dto/resolve.py,sha256=SUwBKGRS8oiIIGtUCPrnCHdslT3d-fWPn8XUS1s8ehs,15615
|
|
96
|
+
snakeorm/dto/spec.py,sha256=EcSVNlowe5hJfDahfYyL3bGA5kjgv0z3hFx-dtHH6gw,8982
|
|
97
|
+
snakeorm/expressions/__init__.py,sha256=iDwRIs8ZnfMVvz08k8WnbQNfJdkQBNXZrvUdNW7IRGo,5854
|
|
98
|
+
snakeorm/expressions/conditional.py,sha256=rhVeFJ17yKygFDB_nirOgB8_tPEs2tZvNNjZjo2kICA,7267
|
|
99
|
+
snakeorm/expressions/expression.py,sha256=_QsOcEB7-1Vuv8Y0mmEb8HzIZBielPbiHDkup8ridY8,33781
|
|
100
|
+
snakeorm/expressions/functions.py,sha256=9WzTksjfCntr5F0bgxiHm5loVPvhi7KZw9BfntpEm84,2843
|
|
101
|
+
snakeorm/expressions/keys.py,sha256=DXfvhW46soeuFF3eo0vNNrRu3snhkQtaARceJT-XTGU,14503
|
|
102
|
+
snakeorm/expressions/paths.py,sha256=vvfKMlzslBiHHMIuRORwkbzc24iF5e_MJDfKkpSRV8o,1550
|
|
103
|
+
snakeorm/expressions/scalar.py,sha256=eJJ54zMbcVwkvoyhuDpAQphjijoZ5Pp9df1EcZkk0RY,12362
|
|
104
|
+
snakeorm/expressions/window.py,sha256=gWcIBwB--1HFTncMbWtERzeiprEuTe6e8mvZ6GM9Dns,10253
|
|
105
|
+
snakeorm/fields/__init__.py,sha256=EoSx4O8z0QnpHKm2qw0UWRzeOMo0Rd7qCjaJGtp-jD0,3531
|
|
106
|
+
snakeorm/fields/check.py,sha256=eL_i2nkw-hkci6msivTgoypRJu7xh6ypodZiif0-d9s,3238
|
|
107
|
+
snakeorm/fields/column.py,sha256=uOvUrSMJ9kgcZ63l-NyEO8VUMsfPpU8jrRxJ7dOmbJo,13098
|
|
108
|
+
snakeorm/fields/enum.py,sha256=OP7zSQk8AMaDGUwugs0v0H5cBJ_shCYjnRnsbpMn78A,2599
|
|
109
|
+
snakeorm/fields/index.py,sha256=GzsbwNXUGW9GjIPoChY7JQhjwHsj9_NytV5aSpH87Xg,1837
|
|
110
|
+
snakeorm/fields/relationship.py,sha256=VjzsrxtSAmEwj29W3NSFRbhl_at90b7jG1KSDedlAc0,33503
|
|
111
|
+
snakeorm/fields/typed.py,sha256=MdjAdWPskaaI4jQ0ZqrOtfoJbI5YF65NuY8GjP6nDGw,24083
|
|
112
|
+
snakeorm/helpers/__init__.py,sha256=oTAk4BaEG7b_6vFomwBiYuW0ZyjQco-2hcMUsAadOHI,90
|
|
113
|
+
snakeorm/helpers/annotations.py,sha256=u3VlsJeT5VoXelQYRYkg27kTlmvrlmCoX4kl5fabipI,822
|
|
114
|
+
snakeorm/helpers/inheritance.py,sha256=sAOway-_q_lbA6lO2LJECTjc4vxwfU2fMj51-Krd3nM,1603
|
|
115
|
+
snakeorm/helpers/pyliteral.py,sha256=dnjCUibXSR4fQmIVqdc3N2s65xdJMNc-2QRVvlsPawU,2034
|
|
116
|
+
snakeorm/helpers/pytype.py,sha256=oSRxns3aqFzh3o3AJ845ca2-bd5MfEatN_GXzqkftzM,2354
|
|
117
|
+
snakeorm/introspection/__init__.py,sha256=3UsiXVTAWjZ1d9uby9ZegI8RFs8UDoJoNsat_uHiWa0,844
|
|
118
|
+
snakeorm/introspection/base.py,sha256=7MvpJtf-au_23wPrz2yI_h3Qvkg87crRZJqDAzl-7zI,1368
|
|
119
|
+
snakeorm/introspection/drift.py,sha256=HqyFIYwigSXbUH8VhI5Xy4Z5KZPh3Idj7gCrFfyTes4,3898
|
|
120
|
+
snakeorm/introspection/models.py,sha256=iCYOqaXasOxDDSAFg7WNAOHoMDju4dJHGoccS__fyMI,30538
|
|
121
|
+
snakeorm/introspection/mysql.py,sha256=R3mOWCQDWXInFQ5JyMO3eLdSBOhR7dgsbttBlkg7AME,15869
|
|
122
|
+
snakeorm/introspection/postgres.py,sha256=lYIi-vlUBjwHqckDnnTjwBwFg7s7XzA1o8sYRLSzokg,18469
|
|
123
|
+
snakeorm/introspection/sqlite.py,sha256=NmCByKel-lXAz224P8DF0CO2_akgzbV6qNG0VbDNgos,8512
|
|
124
|
+
snakeorm/introspection/unsupported.py,sha256=ZqnsHsIuUxMqzcF1Lp6C3XLySFrfbBn8MIfj_QBo06Q,4358
|
|
125
|
+
snakeorm/linker/__init__.py,sha256=h50PEU2-uwHVWIFHkS-qJhjOeNSy7S5H32adgw5qB-I,135
|
|
126
|
+
snakeorm/linker/linker.py,sha256=tEgtKKnamfA01nobCqY22fO9fjKz_03iHg7kkuka7Ek,24715
|
|
127
|
+
snakeorm/metadata/__init__.py,sha256=d93E2t3BCQEZp4BS1W6kAv7x4jczba2PxiTy0BgJWmE,2616
|
|
128
|
+
snakeorm/metadata/check.py,sha256=30cEN9AzQfbqp-KkDW74zjVJfceDCYgOXhtXeyPuAXo,1401
|
|
129
|
+
snakeorm/metadata/column.py,sha256=oMxH-aHQ4TSvoscMw25V6-1DFbIjj2XjaNpjUKSzYSY,9142
|
|
130
|
+
snakeorm/metadata/enum_storage.py,sha256=KftQksmdoPc9cySVG0w39zIQ-mDzDtuDzLLJzDkbjcs,1600
|
|
131
|
+
snakeorm/metadata/fk_action.py,sha256=V9oI2hc0WbXvUtH8wzNUUJb2fjkNiDJCysXIEfbi0fI,500
|
|
132
|
+
snakeorm/metadata/foreign_key.py,sha256=gF-klSkb4YXICvckvq1aqHrvh8X5K9bQz9f3qXgm3Gk,850
|
|
133
|
+
snakeorm/metadata/index.py,sha256=64lAVN8MlMwFOfb5HXqXtGWLsngNR4FIfUFukip8VFM,1772
|
|
134
|
+
snakeorm/metadata/index_method.py,sha256=neFbGeeFFesy-4r0KoVzLTYCz31L9N4gHwFeF2ZyPv0,636
|
|
135
|
+
snakeorm/metadata/int_size.py,sha256=Rot4E1kbaACdfq43KRfXpeY8ka0Ehm1qAQQ5cdSVkFc,864
|
|
136
|
+
snakeorm/metadata/json_storage.py,sha256=PvounEiA_4fJ8wvPcm4vj4E8pww3Adcy0QLFb9ueC4I,739
|
|
137
|
+
snakeorm/metadata/polymorphic.py,sha256=8yAGVHSoy7bxcyxcTKAVAAuLklCBSf2MvV5tj_Vz3aM,1223
|
|
138
|
+
snakeorm/metadata/primary_key.py,sha256=Tm7n74kIB4o-bZN1BK1_l09lV13cmKvtRgXGfTTV08Y,676
|
|
139
|
+
snakeorm/metadata/relationship.py,sha256=4uEb5rKWA0cCrnBZhZ5EE-0b6hP-1OWveZI5ikQLeDY,2578
|
|
140
|
+
snakeorm/metadata/relationship_kind.py,sha256=xXI3ME4kbh1ZOXDI_uudD_H2l2VHahqrnHUX3Uauq-M,1734
|
|
141
|
+
snakeorm/metadata/routine.py,sha256=GTILeiDBJWWiA8PQyoOv0-_BXg71mKnjb3mtioOGjXw,844
|
|
142
|
+
snakeorm/metadata/server_default.py,sha256=p744uQW0ee3-QOui9TE2X1FYaDKE4uqMolQR4FzH_6o,757
|
|
143
|
+
snakeorm/metadata/table.py,sha256=ZT5Ul3cs4kvsBcTadbEe-mlQt4yo-YBoCMU_zzPwX0c,4490
|
|
144
|
+
snakeorm/metadata/table_kind.py,sha256=55-mRkDsbja23F73MkI23sal6thNo4FZxVNEMtoQCW8,743
|
|
145
|
+
snakeorm/metadata/trigger.py,sha256=4Gh4k9ZV0KUT5tVP_igvm8Xq1Bc-AVHeWOMxHS1ZkvU,2057
|
|
146
|
+
snakeorm/metadata/type_params.py,sha256=X2OmrNq-HFZb9db5MHs9nfTfBfLRjCIbUbIVG-PBNNU,15918
|
|
147
|
+
snakeorm/migration/__init__.py,sha256=XHXCZLz6WAlFKE9cCBSdqOcjgNaOLvysRuKjWVxIf9o,6099
|
|
148
|
+
snakeorm/migration/asyncrunner.py,sha256=GkRsvbfr4e0mbHjyYlqXsMRQZiPibDBiB7UpltxMDkg,7505
|
|
149
|
+
snakeorm/migration/autodetect.py,sha256=nRaTjRVnqdZM0Uu5UzbqGYCPGwcUTUKDRGuX4315aeY,4389
|
|
150
|
+
snakeorm/migration/ddl.py,sha256=RbkshLX-gq69IqHhNwHwKaSnQpphJ1oC3u__5_a-1bw,55389
|
|
151
|
+
snakeorm/migration/diff.py,sha256=-TcVdJkQGRfcYKeMbOWZNsD3lBKwN3duZW6kbqqxyyM,33387
|
|
152
|
+
snakeorm/migration/loader.py,sha256=1d_8lR-D4pPArgSR81tLV7c2ufthEbZg66gXW39JcVQ,2836
|
|
153
|
+
snakeorm/migration/operations.py,sha256=a7i8XeC7GdiEpEnJxw5TE52yBxt6bbfbwfuI_Kr9CJM,38440
|
|
154
|
+
snakeorm/migration/planner.py,sha256=lkf5KPN2TrUheXKV6PGWlDJ6asSp9sWTVoL3qiGcXno,3302
|
|
155
|
+
snakeorm/migration/realize.py,sha256=HiA9T7I5QVQDkEqAckKHyBxd14tc4c-by6kdEAVH01s,23427
|
|
156
|
+
snakeorm/migration/renames.py,sha256=74t40eWSiS_C-dj7f6f8nl9SfLDl55I1AlAeM-3COCg,7381
|
|
157
|
+
snakeorm/migration/render.py,sha256=YL5mDBDrETU1uzfnBgtdaMMhFAFR-ITvupHPiikEIYo,45045
|
|
158
|
+
snakeorm/migration/runner.py,sha256=0UnT2G85-B_w0zgozx8uWsYEs12ZQCu0xrmhz1-a12A,15383
|
|
159
|
+
snakeorm/migration/squash.py,sha256=MLmf_DCyLfWwnTyULIYwl3-jx6ghM6fhlLbBqzkyrTI,3159
|
|
160
|
+
snakeorm/migration/state.py,sha256=aVfl0iCnec_wzKctnKTdTUTtJVTiJN4IQ_mFmQ2KCSk,4716
|
|
161
|
+
snakeorm/query/__init__.py,sha256=kEKbmgVRQ4YOiPQYLcgAAz87iMHAIgJTqdCQTL3156w,560
|
|
162
|
+
snakeorm/query/compound.py,sha256=AUmwZ1Vu4CrE6V7Tv-AjaV9Di0totl3hiXXBDVQ0PXQ,13672
|
|
163
|
+
snakeorm/query/join_kind.py,sha256=nXHFOo7gFtCPohf-REgPP-Q3eKK35CLaBf2Lpb0OgRA,685
|
|
164
|
+
snakeorm/query/joined.py,sha256=fjSctbgUSrebeo_yoR459vBq_UxvTFAQOU2SpoEQ6OE,6204
|
|
165
|
+
snakeorm/query/query.py,sha256=h-32vd4SehNdG_Ro_4NP3v3nGQXNebCEl-u5rGmVGSQ,45193
|
|
166
|
+
snakeorm/query/recursive.py,sha256=0L3u9h33Ysm6U7mhj5IHZYG1XgEPqtnFt44GybgLB2c,10826
|
|
167
|
+
snakeorm/registry/__init__.py,sha256=o1e7NmEwRNPPiJRU84tcjncp8u0PeUYw3jkvsiUVB9A,243
|
|
168
|
+
snakeorm/registry/by_module.py,sha256=KMEcSLErvMW5RwzHIc_twM1mjU2GJNCrV8oTJnbJi2E,7096
|
|
169
|
+
snakeorm/registry/registry.py,sha256=jhXNGixjCMc1LilHLSc2_9dhJBc86sm3ka3TCbB-fbg,7773
|
|
170
|
+
snakeorm/session/__init__.py,sha256=Ut_zFwbn3fxRdoMDrjD82RkeCGUuD1kOVXpxUe5a7d4,468
|
|
171
|
+
snakeorm/session/asyncsession.py,sha256=zhOmEL1yu7irD6i8i0-pBw4KWi8Pzz1UP3yVxgmmPcU,28100
|
|
172
|
+
snakeorm/session/coercion.py,sha256=_VbS6dje0tEILVtNfuSIYAykq5uqZiiBe-IXUyc6A3w,13782
|
|
173
|
+
snakeorm/session/factory.py,sha256=Cr2V1hriAOjUfbTSiHzBR_0CZ7175Sj_Uzj6GmgIH_8,1519
|
|
174
|
+
snakeorm/session/guards.py,sha256=yk8TVZ1dhxw9DXRpNIy5n0Lm9UPdcdC-qLsSrx3PLXA,13495
|
|
175
|
+
snakeorm/session/isolation.py,sha256=bRl2WZeY41LZcsycpS3pSJorE9qHtuky5w_lf7O8Nbc,946
|
|
176
|
+
snakeorm/session/mapper.py,sha256=Hs58azawWilfiit3lDYy1U3SzHyY4m1r2VqMLij-ltw,9219
|
|
177
|
+
snakeorm/session/planning.py,sha256=TAyVpWkxFWK0fW96PnblNTBw3HDloXFGMmbQ8-63dBE,34959
|
|
178
|
+
snakeorm/session/retry.py,sha256=PwIxs6MZW-SkRsKOYBS0oHf9_KCZEa0iXGCjV1jGTH0,4336
|
|
179
|
+
snakeorm/session/session.py,sha256=ddXsLOKVh0wG1PmF80SVX44MMJQSgnOXYESWJ_XQ7cI,40015
|
|
180
|
+
snakeorm/session/shared.py,sha256=DS0cvmW8ww5mYikwoe4Ls6NoJfSa2Kbp08YiRCvUZVg,16165
|
|
181
|
+
snakeorm/sql/__init__.py,sha256=qa4MkP3h55e-2fC-ZZciK3ChGLUG-doigIel9QOfP8E,1124
|
|
182
|
+
snakeorm/sql/adapt.py,sha256=0-bFPAkniub5u6ZDE-WtLVeFBwkr42WlDDsJipJ5gPQ,5632
|
|
183
|
+
snakeorm/sql/aggregate.py,sha256=3m1CUtLQwWNbOmDfi56dgsPjhrqhEgZuCdZDd5bojEM,9440
|
|
184
|
+
snakeorm/sql/condition.py,sha256=dGKJG8efnBiO8W-t_NQTAX-Qpv0TGXrOIcn3RbxJk9c,15267
|
|
185
|
+
snakeorm/sql/delete.py,sha256=u41Nz-dQAV3i1QQ73nPfJ89kZ6Orq_6ZWXjE5qe9Vp4,1770
|
|
186
|
+
snakeorm/sql/insert.py,sha256=XMukIKlxYjcvf_GjBVtjhXHFaxYSpVu3NcdQ18GqGDM,6045
|
|
187
|
+
snakeorm/sql/joins.py,sha256=rh7yHexrj3J8vZu6rh0iblaGeyugBLbLKY3fY7zxPk8,7526
|
|
188
|
+
snakeorm/sql/pk_subquery.py,sha256=58vd0ZZ8qDd-ae278zppzNDVRuZXTM5aAM2AgTQU_lk,2310
|
|
189
|
+
snakeorm/sql/refs.py,sha256=HY68tKVegwULk7-t13ljIzs5Hl5omOYkBqhGGTEimIs,728
|
|
190
|
+
snakeorm/sql/resolver.py,sha256=yMTDBSouuxHSzSC_7J-OlWqrb2nCLrw0gG2PVWVggZE,1216
|
|
191
|
+
snakeorm/sql/select.py,sha256=utTv5qauHDKHeROxWb89TzgK2CF-9jHozbHC9sDu8RI,7028
|
|
192
|
+
snakeorm/sql/update.py,sha256=vjVLhDhFVpeIPfbEgoeRO1lOGWAD5gQQepDEcTnc3eY,3169
|
|
193
|
+
snakeorm/sql/value.py,sha256=uRkJqrxWXimNEqlr-aH3ZRXppdlww_MW82zH9wW7bes,22920
|
|
194
|
+
snake_orm-0.1.0b1.dist-info/METADATA,sha256=08rQZl7tpnhQjzmD3UbRt3cln-fGGf4bELmBR-1t0Es,14688
|
|
195
|
+
snake_orm-0.1.0b1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
196
|
+
snake_orm-0.1.0b1.dist-info/entry_points.txt,sha256=5U4-q754fJzSepwYrVP1noJO887yafx8sCTKnhM5cvk,47
|
|
197
|
+
snake_orm-0.1.0b1.dist-info/licenses/LICENSE,sha256=bUCxdUV3QMfnRb-mnSglz35O7RvBFiHyOpDHOB-DvPY,1076
|
|
198
|
+
snake_orm-0.1.0b1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anthony Velez Tapia
|
|
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.
|