oxyde-postgres 0.1.0__tar.gz
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.
- oxyde_postgres-0.1.0/LICENSE +21 -0
- oxyde_postgres-0.1.0/PKG-INFO +141 -0
- oxyde_postgres-0.1.0/README.md +72 -0
- oxyde_postgres-0.1.0/oxyde/__init__.py +151 -0
- oxyde_postgres-0.1.0/oxyde/_msgpack.py +3 -0
- oxyde_postgres-0.1.0/oxyde/cli/__init__.py +7 -0
- oxyde_postgres-0.1.0/oxyde/cli/app.py +107 -0
- oxyde_postgres-0.1.0/oxyde/cli/codegen.py +66 -0
- oxyde_postgres-0.1.0/oxyde/cli/config.py +84 -0
- oxyde_postgres-0.1.0/oxyde/cli/migrations.py +536 -0
- oxyde_postgres-0.1.0/oxyde/codegen/__init__.py +15 -0
- oxyde_postgres-0.1.0/oxyde/codegen/stub_generator.py +1229 -0
- oxyde_postgres-0.1.0/oxyde/core/__init__.py +90 -0
- oxyde_postgres-0.1.0/oxyde/core/column_types.py +367 -0
- oxyde_postgres-0.1.0/oxyde/core/ir.py +380 -0
- oxyde_postgres-0.1.0/oxyde/core/types.py +69 -0
- oxyde_postgres-0.1.0/oxyde/core/wrapper.py +242 -0
- oxyde_postgres-0.1.0/oxyde/db/__init__.py +230 -0
- oxyde_postgres-0.1.0/oxyde/db/pool.py +305 -0
- oxyde_postgres-0.1.0/oxyde/db/registry.py +113 -0
- oxyde_postgres-0.1.0/oxyde/db/schema.py +51 -0
- oxyde_postgres-0.1.0/oxyde/db/transaction.py +392 -0
- oxyde_postgres-0.1.0/oxyde/exceptions.py +89 -0
- oxyde_postgres-0.1.0/oxyde/migrations/__init__.py +49 -0
- oxyde_postgres-0.1.0/oxyde/migrations/config.py +187 -0
- oxyde_postgres-0.1.0/oxyde/migrations/context.py +598 -0
- oxyde_postgres-0.1.0/oxyde/migrations/executor.py +456 -0
- oxyde_postgres-0.1.0/oxyde/migrations/extract.py +316 -0
- oxyde_postgres-0.1.0/oxyde/migrations/generator.py +534 -0
- oxyde_postgres-0.1.0/oxyde/migrations/replay.py +367 -0
- oxyde_postgres-0.1.0/oxyde/migrations/squash.py +109 -0
- oxyde_postgres-0.1.0/oxyde/migrations/tracker.py +162 -0
- oxyde_postgres-0.1.0/oxyde/migrations/utils.py +145 -0
- oxyde_postgres-0.1.0/oxyde/models/__init__.py +83 -0
- oxyde_postgres-0.1.0/oxyde/models/base.py +871 -0
- oxyde_postgres-0.1.0/oxyde/models/decorators.py +111 -0
- oxyde_postgres-0.1.0/oxyde/models/field.py +204 -0
- oxyde_postgres-0.1.0/oxyde/models/lookups.py +667 -0
- oxyde_postgres-0.1.0/oxyde/models/metadata.py +174 -0
- oxyde_postgres-0.1.0/oxyde/models/registry.py +170 -0
- oxyde_postgres-0.1.0/oxyde/models/serializers.py +144 -0
- oxyde_postgres-0.1.0/oxyde/models/utils.py +116 -0
- oxyde_postgres-0.1.0/oxyde/py.typed +0 -0
- oxyde_postgres-0.1.0/oxyde/queries/__init__.py +79 -0
- oxyde_postgres-0.1.0/oxyde/queries/aggregates.py +145 -0
- oxyde_postgres-0.1.0/oxyde/queries/base.py +249 -0
- oxyde_postgres-0.1.0/oxyde/queries/conditions.py +92 -0
- oxyde_postgres-0.1.0/oxyde/queries/expressions.py +171 -0
- oxyde_postgres-0.1.0/oxyde/queries/insert.py +124 -0
- oxyde_postgres-0.1.0/oxyde/queries/joins.py +66 -0
- oxyde_postgres-0.1.0/oxyde/queries/manager.py +409 -0
- oxyde_postgres-0.1.0/oxyde/queries/mixins/__init__.py +68 -0
- oxyde_postgres-0.1.0/oxyde/queries/mixins/aggregation.py +335 -0
- oxyde_postgres-0.1.0/oxyde/queries/mixins/debug.py +154 -0
- oxyde_postgres-0.1.0/oxyde/queries/mixins/execution.py +701 -0
- oxyde_postgres-0.1.0/oxyde/queries/mixins/filtering.py +144 -0
- oxyde_postgres-0.1.0/oxyde/queries/mixins/joining.py +160 -0
- oxyde_postgres-0.1.0/oxyde/queries/mixins/mutation.py +508 -0
- oxyde_postgres-0.1.0/oxyde/queries/mixins/pagination.py +145 -0
- oxyde_postgres-0.1.0/oxyde/queries/q.py +250 -0
- oxyde_postgres-0.1.0/oxyde/queries/raw.py +114 -0
- oxyde_postgres-0.1.0/oxyde/queries/select.py +345 -0
- oxyde_postgres-0.1.0/oxyde/queries/typed.py +213 -0
- oxyde_postgres-0.1.0/oxyde_postgres.egg-info/PKG-INFO +141 -0
- oxyde_postgres-0.1.0/oxyde_postgres.egg-info/SOURCES.txt +69 -0
- oxyde_postgres-0.1.0/oxyde_postgres.egg-info/dependency_links.txt +1 -0
- oxyde_postgres-0.1.0/oxyde_postgres.egg-info/entry_points.txt +2 -0
- oxyde_postgres-0.1.0/oxyde_postgres.egg-info/requires.txt +20 -0
- oxyde_postgres-0.1.0/oxyde_postgres.egg-info/top_level.txt +1 -0
- oxyde_postgres-0.1.0/pyproject.toml +138 -0
- oxyde_postgres-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Nikita Ryzhenkov
|
|
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.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oxyde-postgres
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Type-safe asynchronous ORM for PostgreSQL 18+ with a Rust core
|
|
5
|
+
Author-email: Nikita Ryzhenkov <nikita.ryzhenkoff@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 Nikita Ryzhenkov
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/ebrake/oxyde-postgres
|
|
29
|
+
Project-URL: Documentation, https://github.com/ebrake/oxyde-postgres/tree/main/docs
|
|
30
|
+
Project-URL: Repository, https://github.com/ebrake/oxyde-postgres
|
|
31
|
+
Project-URL: Issues, https://github.com/ebrake/oxyde-postgres/issues
|
|
32
|
+
Keywords: orm,async,database,postgresql,pydantic
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
43
|
+
Classifier: Programming Language :: Rust
|
|
44
|
+
Classifier: Framework :: AsyncIO
|
|
45
|
+
Classifier: Topic :: Database
|
|
46
|
+
Classifier: Typing :: Typed
|
|
47
|
+
Requires-Python: >=3.10
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
License-File: LICENSE
|
|
50
|
+
Requires-Dist: pydantic>=2.0
|
|
51
|
+
Requires-Dist: msgpack>=1.0
|
|
52
|
+
Requires-Dist: typer>=0.24
|
|
53
|
+
Requires-Dist: oxyde-postgres-core<0.2.0,>=0.1.0
|
|
54
|
+
Provides-Extra: dev
|
|
55
|
+
Requires-Dist: pytest>=9.0; extra == "dev"
|
|
56
|
+
Requires-Dist: pytest-asyncio>=1.0; extra == "dev"
|
|
57
|
+
Requires-Dist: pytest-cov>=7.0; extra == "dev"
|
|
58
|
+
Requires-Dist: ruff>=0.14.7; extra == "dev"
|
|
59
|
+
Requires-Dist: pre-commit>=4.5.0; extra == "dev"
|
|
60
|
+
Requires-Dist: mypy==1.19.1; extra == "dev"
|
|
61
|
+
Requires-Dist: pyright[nodejs]==1.1.411; extra == "dev"
|
|
62
|
+
Requires-Dist: ty==0.0.59; extra == "dev"
|
|
63
|
+
Requires-Dist: testcontainers[postgres]>=4.0; extra == "dev"
|
|
64
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "dev"
|
|
65
|
+
Provides-Extra: docs
|
|
66
|
+
Requires-Dist: mkdocs<2,>=1.6; extra == "docs"
|
|
67
|
+
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
68
|
+
Dynamic: license-file
|
|
69
|
+
|
|
70
|
+
# Oxyde for PostgreSQL
|
|
71
|
+
|
|
72
|
+
Oxyde for PostgreSQL is a PostgreSQL-focused fork of [Oxyde ORM](https://github.com/mr-fatalyst/oxyde): a type-safe, Pydantic-centric asynchronous ORM with a high-performance Rust core designed for clarity, speed, and reliability. It builds on Oxyde's great work and optimizes it for PostgreSQL with bug fixes and PostgreSQL-specific features.
|
|
73
|
+
|
|
74
|
+
**Requires PostgreSQL 18+ and Python 3.10+.** The Rust core checks the server version when connecting. Both `postgres://` and `postgresql://` connection URLs are supported.
|
|
75
|
+
|
|
76
|
+
## Features
|
|
77
|
+
|
|
78
|
+
- Pydantic v2 models with validation, type hints, and serialization.
|
|
79
|
+
- Familiar `Model.objects.filter()` queries, joins, aggregates, and bulk operations.
|
|
80
|
+
- Native PostgreSQL arrays, JSONB, enums, UUIDs, decimals, `RETURNING`, and row locks.
|
|
81
|
+
- Array and JSON lookups: `contains`, `contained_by`, and `overlap`.
|
|
82
|
+
- Async transactions with nested savepoints and public transaction-depth reporting.
|
|
83
|
+
- Schema migrations with PostgreSQL advisory locking and transactional DDL.
|
|
84
|
+
- Rust SQL generation, connection pooling, and execution, with Python 3.15 wheel support.
|
|
85
|
+
- Cache-disabled unnamed statements for PostgreSQL/PgBouncer, including cold custom-type decoding without replaying queries.
|
|
86
|
+
|
|
87
|
+
The initial fork release is **0.1.0**. [First-release notes](docs/changelog.md) list every addition and its source.
|
|
88
|
+
|
|
89
|
+
## Installation
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pip install oxyde-postgres
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The distribution names are `oxyde-postgres` and `oxyde-postgres-core`. Python imports remain `oxyde`, the native module remains `_oxyde_core`, and the CLI remains `oxyde`. Use a separate virtual environment from upstream Oxyde because the packages share these import names.
|
|
96
|
+
|
|
97
|
+
To build this checkout:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv venv --python 3.13
|
|
101
|
+
source .venv/bin/activate
|
|
102
|
+
uv pip install maturin
|
|
103
|
+
maturin develop --manifest-path crates/oxyde-core-py/Cargo.toml
|
|
104
|
+
uv pip install -e './python[dev]'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Quick start
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import asyncio
|
|
111
|
+
from oxyde import Field, Model, db
|
|
112
|
+
from oxyde.db.schema import create_tables
|
|
113
|
+
|
|
114
|
+
class User(Model):
|
|
115
|
+
id: int | None = Field(default=None, db_pk=True)
|
|
116
|
+
name: str
|
|
117
|
+
email: str = Field(db_unique=True)
|
|
118
|
+
tags: list[str] = Field(default_factory=list)
|
|
119
|
+
|
|
120
|
+
class Meta:
|
|
121
|
+
is_table = True
|
|
122
|
+
|
|
123
|
+
async def main():
|
|
124
|
+
await db.init(default="postgresql://user:password@localhost:5432/app")
|
|
125
|
+
try:
|
|
126
|
+
connection = await db.get_connection()
|
|
127
|
+
await create_tables(connection)
|
|
128
|
+
await User.objects.create(name="Alice", email="alice@example.com", tags=["python"])
|
|
129
|
+
users = await User.objects.filter(tags__contains=["python"]).all()
|
|
130
|
+
print(users)
|
|
131
|
+
finally:
|
|
132
|
+
await db.close()
|
|
133
|
+
|
|
134
|
+
asyncio.run(main())
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
For managed schema changes, run `oxyde init`, `oxyde makemigrations`, and `oxyde migrate`. See the [documentation](docs/index.md), [connection guide](docs/guide/connections.md), and [contributor guide](.github/CONTRIBUTING.md).
|
|
138
|
+
|
|
139
|
+
## Credit and license
|
|
140
|
+
|
|
141
|
+
Oxyde for PostgreSQL is based on [Oxyde](https://github.com/mr-fatalyst/oxyde), created by Nikita Ryzhenkov and its contributors. The original MIT [license](LICENSE) is preserved. The vendored SQLx PostgreSQL driver retains its MIT and Apache-2.0 licenses.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Oxyde for PostgreSQL
|
|
2
|
+
|
|
3
|
+
Oxyde for PostgreSQL is a PostgreSQL-focused fork of [Oxyde ORM](https://github.com/mr-fatalyst/oxyde): a type-safe, Pydantic-centric asynchronous ORM with a high-performance Rust core designed for clarity, speed, and reliability. It builds on Oxyde's great work and optimizes it for PostgreSQL with bug fixes and PostgreSQL-specific features.
|
|
4
|
+
|
|
5
|
+
**Requires PostgreSQL 18+ and Python 3.10+.** The Rust core checks the server version when connecting. Both `postgres://` and `postgresql://` connection URLs are supported.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Pydantic v2 models with validation, type hints, and serialization.
|
|
10
|
+
- Familiar `Model.objects.filter()` queries, joins, aggregates, and bulk operations.
|
|
11
|
+
- Native PostgreSQL arrays, JSONB, enums, UUIDs, decimals, `RETURNING`, and row locks.
|
|
12
|
+
- Array and JSON lookups: `contains`, `contained_by`, and `overlap`.
|
|
13
|
+
- Async transactions with nested savepoints and public transaction-depth reporting.
|
|
14
|
+
- Schema migrations with PostgreSQL advisory locking and transactional DDL.
|
|
15
|
+
- Rust SQL generation, connection pooling, and execution, with Python 3.15 wheel support.
|
|
16
|
+
- Cache-disabled unnamed statements for PostgreSQL/PgBouncer, including cold custom-type decoding without replaying queries.
|
|
17
|
+
|
|
18
|
+
The initial fork release is **0.1.0**. [First-release notes](docs/changelog.md) list every addition and its source.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install oxyde-postgres
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The distribution names are `oxyde-postgres` and `oxyde-postgres-core`. Python imports remain `oxyde`, the native module remains `_oxyde_core`, and the CLI remains `oxyde`. Use a separate virtual environment from upstream Oxyde because the packages share these import names.
|
|
27
|
+
|
|
28
|
+
To build this checkout:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv venv --python 3.13
|
|
32
|
+
source .venv/bin/activate
|
|
33
|
+
uv pip install maturin
|
|
34
|
+
maturin develop --manifest-path crates/oxyde-core-py/Cargo.toml
|
|
35
|
+
uv pip install -e './python[dev]'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import asyncio
|
|
42
|
+
from oxyde import Field, Model, db
|
|
43
|
+
from oxyde.db.schema import create_tables
|
|
44
|
+
|
|
45
|
+
class User(Model):
|
|
46
|
+
id: int | None = Field(default=None, db_pk=True)
|
|
47
|
+
name: str
|
|
48
|
+
email: str = Field(db_unique=True)
|
|
49
|
+
tags: list[str] = Field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
class Meta:
|
|
52
|
+
is_table = True
|
|
53
|
+
|
|
54
|
+
async def main():
|
|
55
|
+
await db.init(default="postgresql://user:password@localhost:5432/app")
|
|
56
|
+
try:
|
|
57
|
+
connection = await db.get_connection()
|
|
58
|
+
await create_tables(connection)
|
|
59
|
+
await User.objects.create(name="Alice", email="alice@example.com", tags=["python"])
|
|
60
|
+
users = await User.objects.filter(tags__contains=["python"]).all()
|
|
61
|
+
print(users)
|
|
62
|
+
finally:
|
|
63
|
+
await db.close()
|
|
64
|
+
|
|
65
|
+
asyncio.run(main())
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
For managed schema changes, run `oxyde init`, `oxyde makemigrations`, and `oxyde migrate`. See the [documentation](docs/index.md), [connection guide](docs/guide/connections.md), and [contributor guide](.github/CONTRIBUTING.md).
|
|
69
|
+
|
|
70
|
+
## Credit and license
|
|
71
|
+
|
|
72
|
+
Oxyde for PostgreSQL is based on [Oxyde](https://github.com/mr-fatalyst/oxyde), created by Nikita Ryzhenkov and its contributors. The original MIT [license](LICENSE) is preserved. The vendored SQLx PostgreSQL driver retains its MIT and Apache-2.0 licenses.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Oxyde ORM - High-performance async Python ORM with Rust core.
|
|
2
|
+
|
|
3
|
+
This is the main entry point for the Oxyde ORM library. It re-exports
|
|
4
|
+
all public APIs from submodules for convenient access.
|
|
5
|
+
|
|
6
|
+
Public API:
|
|
7
|
+
Models:
|
|
8
|
+
Model: Base class for all ORM models (Pydantic v2 based).
|
|
9
|
+
Field: Field configuration with db_pk, db_index, db_default, etc.
|
|
10
|
+
Index: Composite index decorator for models.
|
|
11
|
+
Check: CHECK constraint decorator for models.
|
|
12
|
+
|
|
13
|
+
Queries:
|
|
14
|
+
Query: SELECT query builder with Django-like filter syntax.
|
|
15
|
+
QueryManager: Model.objects manager for CRUD operations.
|
|
16
|
+
Q: Boolean expressions for complex filters (AND/OR/NOT).
|
|
17
|
+
F: Database-side field references for expressions.
|
|
18
|
+
|
|
19
|
+
Aggregates:
|
|
20
|
+
Count, Sum, Avg, Max, Min: SQL aggregate functions.
|
|
21
|
+
Concat, Coalesce: SQL scalar functions.
|
|
22
|
+
RawSQL: Raw SQL expression wrapper.
|
|
23
|
+
|
|
24
|
+
Database:
|
|
25
|
+
db: Database module with init(), close(), connect(), lifespan().
|
|
26
|
+
AsyncDatabase: Connection pool wrapper.
|
|
27
|
+
PoolSettings: Pool configuration (max_connections, timeouts).
|
|
28
|
+
atomic: Transaction context manager (decorator or async with).
|
|
29
|
+
|
|
30
|
+
Exceptions:
|
|
31
|
+
OxydeError: Base exception for all Oxyde errors.
|
|
32
|
+
NotFoundError: Raised when get() finds no results.
|
|
33
|
+
MultipleObjectsReturned: Raised when get() finds multiple results.
|
|
34
|
+
IntegrityError: Raised on constraint violations.
|
|
35
|
+
FieldError, FieldLookupError, ManagerError: Validation errors.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
from oxyde import Model, Field, db
|
|
39
|
+
|
|
40
|
+
class User(Model):
|
|
41
|
+
id: int | None = Field(default=None, db_pk=True)
|
|
42
|
+
name: str
|
|
43
|
+
|
|
44
|
+
class Meta:
|
|
45
|
+
is_table = True
|
|
46
|
+
|
|
47
|
+
async with db.connect("postgresql://localhost/app"):
|
|
48
|
+
user = await User.objects.create(name="Alice")
|
|
49
|
+
users = await User.objects.filter(name__startswith="A").all()
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
import warnings
|
|
53
|
+
|
|
54
|
+
from oxyde import db
|
|
55
|
+
from oxyde.db import (
|
|
56
|
+
AsyncDatabase,
|
|
57
|
+
PoolSettings,
|
|
58
|
+
TransactionTimeoutError,
|
|
59
|
+
atomic,
|
|
60
|
+
create_tables,
|
|
61
|
+
disconnect_all,
|
|
62
|
+
drop_tables,
|
|
63
|
+
get_connection,
|
|
64
|
+
register_connection,
|
|
65
|
+
)
|
|
66
|
+
from oxyde.exceptions import (
|
|
67
|
+
CheckViolationError,
|
|
68
|
+
FieldError,
|
|
69
|
+
FieldLookupError,
|
|
70
|
+
FieldLookupValueError,
|
|
71
|
+
ForeignKeyViolationError,
|
|
72
|
+
IntegrityError,
|
|
73
|
+
ManagerError,
|
|
74
|
+
MultipleObjectsReturned,
|
|
75
|
+
NotFoundError,
|
|
76
|
+
NotNullViolationError,
|
|
77
|
+
OxydeError,
|
|
78
|
+
UniqueViolationError,
|
|
79
|
+
)
|
|
80
|
+
from oxyde.models import Check, Field, Index, Model
|
|
81
|
+
from oxyde.queries import (
|
|
82
|
+
Avg,
|
|
83
|
+
Coalesce,
|
|
84
|
+
Concat,
|
|
85
|
+
Count,
|
|
86
|
+
F,
|
|
87
|
+
Max,
|
|
88
|
+
Min,
|
|
89
|
+
Q,
|
|
90
|
+
Query,
|
|
91
|
+
QueryManager,
|
|
92
|
+
RawSQL,
|
|
93
|
+
Sum,
|
|
94
|
+
execute_raw,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
__version__ = "0.1.0"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def __getattr__(name: str) -> type:
|
|
101
|
+
if name == "OxydeModel":
|
|
102
|
+
warnings.warn(
|
|
103
|
+
"OxydeModel is deprecated, use Model instead",
|
|
104
|
+
DeprecationWarning,
|
|
105
|
+
stacklevel=2,
|
|
106
|
+
)
|
|
107
|
+
return Model
|
|
108
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
__all__ = [
|
|
112
|
+
"Model",
|
|
113
|
+
"db",
|
|
114
|
+
"AsyncDatabase",
|
|
115
|
+
"PoolSettings",
|
|
116
|
+
"TransactionTimeoutError",
|
|
117
|
+
"register_connection",
|
|
118
|
+
"get_connection",
|
|
119
|
+
"disconnect_all",
|
|
120
|
+
"create_tables",
|
|
121
|
+
"drop_tables",
|
|
122
|
+
"Field",
|
|
123
|
+
"Index",
|
|
124
|
+
"Check",
|
|
125
|
+
"Query",
|
|
126
|
+
"QueryManager",
|
|
127
|
+
"OxydeError",
|
|
128
|
+
"FieldError",
|
|
129
|
+
"FieldLookupError",
|
|
130
|
+
"FieldLookupValueError",
|
|
131
|
+
"ManagerError",
|
|
132
|
+
"NotFoundError",
|
|
133
|
+
"MultipleObjectsReturned",
|
|
134
|
+
"IntegrityError",
|
|
135
|
+
"UniqueViolationError",
|
|
136
|
+
"ForeignKeyViolationError",
|
|
137
|
+
"NotNullViolationError",
|
|
138
|
+
"CheckViolationError",
|
|
139
|
+
"atomic",
|
|
140
|
+
"F",
|
|
141
|
+
"Q",
|
|
142
|
+
"Count",
|
|
143
|
+
"Sum",
|
|
144
|
+
"Avg",
|
|
145
|
+
"Max",
|
|
146
|
+
"Min",
|
|
147
|
+
"Concat",
|
|
148
|
+
"Coalesce",
|
|
149
|
+
"RawSQL",
|
|
150
|
+
"execute_raw",
|
|
151
|
+
]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Oxyde CLI - Database migration and management tool."""
|
|
2
|
+
|
|
3
|
+
# Import command modules to register them with the app (side-effect: registers commands)
|
|
4
|
+
from oxyde.cli import codegen, config, migrations # noqa: F401
|
|
5
|
+
from oxyde.cli.app import app
|
|
6
|
+
|
|
7
|
+
__all__ = ["app"]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Main CLI application and shared utilities."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from oxyde.migrations.config import OxydeConfig, load_config
|
|
8
|
+
|
|
9
|
+
# Main application
|
|
10
|
+
app = typer.Typer(
|
|
11
|
+
name="oxyde",
|
|
12
|
+
help="Oxyde ORM - Database migration and management tool",
|
|
13
|
+
no_args_is_help=True,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_config_or_exit() -> OxydeConfig:
|
|
18
|
+
"""Load config from oxyde_config.py or exit with error.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
OxydeConfig instance
|
|
22
|
+
"""
|
|
23
|
+
config = load_config()
|
|
24
|
+
if config is None:
|
|
25
|
+
typer.secho(
|
|
26
|
+
"❌ No oxyde_config.py found",
|
|
27
|
+
fg=typer.colors.RED,
|
|
28
|
+
)
|
|
29
|
+
typer.echo()
|
|
30
|
+
typer.echo("Run 'oxyde init' to create configuration.")
|
|
31
|
+
raise typer.Exit(1)
|
|
32
|
+
|
|
33
|
+
if not config.models:
|
|
34
|
+
typer.secho(
|
|
35
|
+
"❌ No models configured in oxyde_config.py",
|
|
36
|
+
fg=typer.colors.RED,
|
|
37
|
+
)
|
|
38
|
+
typer.echo()
|
|
39
|
+
typer.echo("Add models to oxyde_config.py:")
|
|
40
|
+
typer.secho(' MODELS = ["models", "app.models"]', fg=typer.colors.CYAN)
|
|
41
|
+
raise typer.Exit(1)
|
|
42
|
+
|
|
43
|
+
return config
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def require_databases(config: OxydeConfig, db_alias: str = "default") -> None:
|
|
47
|
+
"""Validate that databases are configured and alias exists.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
config: OxydeConfig instance
|
|
51
|
+
db_alias: Database alias to check
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
typer.Exit: If validation fails
|
|
55
|
+
"""
|
|
56
|
+
if not config.databases:
|
|
57
|
+
typer.secho(
|
|
58
|
+
"❌ No databases configured in oxyde_config.py",
|
|
59
|
+
fg=typer.colors.RED,
|
|
60
|
+
)
|
|
61
|
+
typer.echo()
|
|
62
|
+
typer.echo("Add DATABASES to oxyde_config.py:")
|
|
63
|
+
typer.secho(
|
|
64
|
+
' DATABASES = {"default": "postgresql://localhost/mydb"}',
|
|
65
|
+
fg=typer.colors.CYAN,
|
|
66
|
+
)
|
|
67
|
+
raise typer.Exit(1)
|
|
68
|
+
|
|
69
|
+
if db_alias not in config.databases:
|
|
70
|
+
typer.secho(
|
|
71
|
+
f"❌ Database '{db_alias}' not found in oxyde_config.py",
|
|
72
|
+
fg=typer.colors.RED,
|
|
73
|
+
)
|
|
74
|
+
typer.echo()
|
|
75
|
+
typer.echo(f"Available databases: {list(config.databases.keys())}")
|
|
76
|
+
raise typer.Exit(1)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def init_databases(databases: dict[str, str]) -> None:
|
|
80
|
+
"""Initialize database connections from config.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
databases: Dict mapping alias to connection URL
|
|
84
|
+
"""
|
|
85
|
+
from oxyde.db import AsyncDatabase
|
|
86
|
+
|
|
87
|
+
for name, url in databases.items():
|
|
88
|
+
db = AsyncDatabase(url, name=name)
|
|
89
|
+
await db.connect()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def ensure_migrations_dir(migrations_dir: str | Path, dry_run: bool = False) -> Path:
|
|
93
|
+
"""Ensure migrations directory exists.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
migrations_dir: Path to migrations directory
|
|
97
|
+
dry_run: If True, don't actually create directory
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Path to migrations directory
|
|
101
|
+
"""
|
|
102
|
+
path = Path(migrations_dir)
|
|
103
|
+
if not path.exists():
|
|
104
|
+
typer.echo(f" 📁 Creating migrations directory: {path.absolute()}")
|
|
105
|
+
if not dry_run:
|
|
106
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
return path
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Code generation commands (generate-stubs)."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from oxyde.cli.app import app, load_config_or_exit
|
|
6
|
+
from oxyde.migrations.config import import_models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@app.command(name="generate-stubs")
|
|
10
|
+
def generate_stubs() -> None:
|
|
11
|
+
"""
|
|
12
|
+
Generate .pyi stub files for all registered table models.
|
|
13
|
+
|
|
14
|
+
Creates type stub files with autocomplete support for QuerySet filter/exclude
|
|
15
|
+
methods with all field lookups (contains, gt, gte, etc.).
|
|
16
|
+
"""
|
|
17
|
+
from oxyde.codegen import generate_stubs_for_models, write_stubs
|
|
18
|
+
from oxyde.models.registry import registered_tables
|
|
19
|
+
|
|
20
|
+
# Load config and import models
|
|
21
|
+
config = load_config_or_exit()
|
|
22
|
+
|
|
23
|
+
typer.echo("🔧 Generating type stubs...")
|
|
24
|
+
typer.echo()
|
|
25
|
+
|
|
26
|
+
# Import models first
|
|
27
|
+
imported = import_models(config.models)
|
|
28
|
+
if imported == 0:
|
|
29
|
+
typer.secho("❌ No modules imported", fg=typer.colors.RED)
|
|
30
|
+
raise typer.Exit(1)
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
# Get all registered table models
|
|
34
|
+
models = list(registered_tables().values())
|
|
35
|
+
|
|
36
|
+
if not models:
|
|
37
|
+
typer.secho("⚠️ No table models found", fg=typer.colors.YELLOW)
|
|
38
|
+
typer.echo("Make sure your models have 'class Meta: is_table = True'")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
typer.echo(f"Found {len(models)} table model(s):")
|
|
42
|
+
for model in models:
|
|
43
|
+
typer.echo(f" - {model.__module__}.{model.__name__}")
|
|
44
|
+
|
|
45
|
+
typer.echo()
|
|
46
|
+
typer.echo("Generating stubs...")
|
|
47
|
+
|
|
48
|
+
# Generate stubs
|
|
49
|
+
stub_mapping = generate_stubs_for_models(models)
|
|
50
|
+
|
|
51
|
+
# Write to disk
|
|
52
|
+
write_stubs(stub_mapping)
|
|
53
|
+
|
|
54
|
+
typer.echo()
|
|
55
|
+
typer.secho(
|
|
56
|
+
f"✅ Generated {len(stub_mapping)} stub file(s)",
|
|
57
|
+
fg=typer.colors.GREEN,
|
|
58
|
+
bold=True,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
except Exception as e:
|
|
62
|
+
typer.secho(f"❌ Error generating stubs: {e}", fg=typer.colors.RED)
|
|
63
|
+
import traceback
|
|
64
|
+
|
|
65
|
+
traceback.print_exc()
|
|
66
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Configuration commands (init)."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from oxyde.cli.app import app
|
|
6
|
+
from oxyde.migrations.config import load_config, save_config
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@app.command()
|
|
10
|
+
def init() -> None:
|
|
11
|
+
"""
|
|
12
|
+
Initialize Oxyde configuration.
|
|
13
|
+
|
|
14
|
+
Interactively asks for configuration values and creates oxyde_config.py.
|
|
15
|
+
"""
|
|
16
|
+
typer.echo("🔧 Initializing Oxyde configuration...")
|
|
17
|
+
typer.echo()
|
|
18
|
+
|
|
19
|
+
# Check if config already exists
|
|
20
|
+
existing = load_config()
|
|
21
|
+
if existing:
|
|
22
|
+
typer.secho("Found existing oxyde_config.py:", fg=typer.colors.YELLOW)
|
|
23
|
+
typer.echo(f" MODELS = {existing.models}")
|
|
24
|
+
typer.echo(f" DIALECT = {existing.dialect}")
|
|
25
|
+
typer.echo(f" MIGRATIONS_DIR = {existing.migrations_dir}")
|
|
26
|
+
typer.echo(f" DATABASES = {existing.databases}")
|
|
27
|
+
typer.echo()
|
|
28
|
+
if not typer.confirm("Overwrite existing configuration?"):
|
|
29
|
+
typer.echo("Aborted.")
|
|
30
|
+
raise typer.Exit(0)
|
|
31
|
+
typer.echo()
|
|
32
|
+
|
|
33
|
+
# Ask for models
|
|
34
|
+
typer.echo("📦 Models configuration")
|
|
35
|
+
typer.echo(" Enter Python modules containing your Model classes.")
|
|
36
|
+
typer.echo(" Examples: 'models', 'app.models', 'myapp.db.models'")
|
|
37
|
+
typer.echo()
|
|
38
|
+
|
|
39
|
+
models_input = typer.prompt(
|
|
40
|
+
" Models module(s)",
|
|
41
|
+
default="models",
|
|
42
|
+
)
|
|
43
|
+
# Parse comma or space separated list
|
|
44
|
+
models = [m.strip() for m in models_input.replace(",", " ").split() if m.strip()]
|
|
45
|
+
|
|
46
|
+
typer.echo()
|
|
47
|
+
|
|
48
|
+
dialect = "postgres"
|
|
49
|
+
|
|
50
|
+
# Ask for database URL
|
|
51
|
+
typer.echo("🔗 Database connection")
|
|
52
|
+
default_url = typer.prompt(
|
|
53
|
+
" Database URL",
|
|
54
|
+
default="postgresql://localhost/mydb",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
typer.echo()
|
|
58
|
+
|
|
59
|
+
# Ask for migrations directory
|
|
60
|
+
migrations_dir = typer.prompt(
|
|
61
|
+
"📁 Migrations directory",
|
|
62
|
+
default="migrations",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
typer.echo()
|
|
66
|
+
|
|
67
|
+
# Save config
|
|
68
|
+
databases = {"default": default_url}
|
|
69
|
+
save_config(
|
|
70
|
+
models=models,
|
|
71
|
+
dialect=dialect,
|
|
72
|
+
migrations_dir=migrations_dir,
|
|
73
|
+
databases=databases,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
typer.secho(
|
|
77
|
+
"✅ Configuration saved to oxyde_config.py", fg=typer.colors.GREEN, bold=True
|
|
78
|
+
)
|
|
79
|
+
typer.echo()
|
|
80
|
+
typer.echo("Next steps:")
|
|
81
|
+
typer.echo(" 1. Edit oxyde_config.py to adjust settings if needed")
|
|
82
|
+
typer.echo(" 2. Create your models in the specified module")
|
|
83
|
+
typer.echo(" 3. Run 'oxyde makemigrations' to generate migrations")
|
|
84
|
+
typer.echo(" 4. Run 'oxyde migrate' to apply migrations")
|