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.
Files changed (71) hide show
  1. oxyde_postgres-0.1.0/LICENSE +21 -0
  2. oxyde_postgres-0.1.0/PKG-INFO +141 -0
  3. oxyde_postgres-0.1.0/README.md +72 -0
  4. oxyde_postgres-0.1.0/oxyde/__init__.py +151 -0
  5. oxyde_postgres-0.1.0/oxyde/_msgpack.py +3 -0
  6. oxyde_postgres-0.1.0/oxyde/cli/__init__.py +7 -0
  7. oxyde_postgres-0.1.0/oxyde/cli/app.py +107 -0
  8. oxyde_postgres-0.1.0/oxyde/cli/codegen.py +66 -0
  9. oxyde_postgres-0.1.0/oxyde/cli/config.py +84 -0
  10. oxyde_postgres-0.1.0/oxyde/cli/migrations.py +536 -0
  11. oxyde_postgres-0.1.0/oxyde/codegen/__init__.py +15 -0
  12. oxyde_postgres-0.1.0/oxyde/codegen/stub_generator.py +1229 -0
  13. oxyde_postgres-0.1.0/oxyde/core/__init__.py +90 -0
  14. oxyde_postgres-0.1.0/oxyde/core/column_types.py +367 -0
  15. oxyde_postgres-0.1.0/oxyde/core/ir.py +380 -0
  16. oxyde_postgres-0.1.0/oxyde/core/types.py +69 -0
  17. oxyde_postgres-0.1.0/oxyde/core/wrapper.py +242 -0
  18. oxyde_postgres-0.1.0/oxyde/db/__init__.py +230 -0
  19. oxyde_postgres-0.1.0/oxyde/db/pool.py +305 -0
  20. oxyde_postgres-0.1.0/oxyde/db/registry.py +113 -0
  21. oxyde_postgres-0.1.0/oxyde/db/schema.py +51 -0
  22. oxyde_postgres-0.1.0/oxyde/db/transaction.py +392 -0
  23. oxyde_postgres-0.1.0/oxyde/exceptions.py +89 -0
  24. oxyde_postgres-0.1.0/oxyde/migrations/__init__.py +49 -0
  25. oxyde_postgres-0.1.0/oxyde/migrations/config.py +187 -0
  26. oxyde_postgres-0.1.0/oxyde/migrations/context.py +598 -0
  27. oxyde_postgres-0.1.0/oxyde/migrations/executor.py +456 -0
  28. oxyde_postgres-0.1.0/oxyde/migrations/extract.py +316 -0
  29. oxyde_postgres-0.1.0/oxyde/migrations/generator.py +534 -0
  30. oxyde_postgres-0.1.0/oxyde/migrations/replay.py +367 -0
  31. oxyde_postgres-0.1.0/oxyde/migrations/squash.py +109 -0
  32. oxyde_postgres-0.1.0/oxyde/migrations/tracker.py +162 -0
  33. oxyde_postgres-0.1.0/oxyde/migrations/utils.py +145 -0
  34. oxyde_postgres-0.1.0/oxyde/models/__init__.py +83 -0
  35. oxyde_postgres-0.1.0/oxyde/models/base.py +871 -0
  36. oxyde_postgres-0.1.0/oxyde/models/decorators.py +111 -0
  37. oxyde_postgres-0.1.0/oxyde/models/field.py +204 -0
  38. oxyde_postgres-0.1.0/oxyde/models/lookups.py +667 -0
  39. oxyde_postgres-0.1.0/oxyde/models/metadata.py +174 -0
  40. oxyde_postgres-0.1.0/oxyde/models/registry.py +170 -0
  41. oxyde_postgres-0.1.0/oxyde/models/serializers.py +144 -0
  42. oxyde_postgres-0.1.0/oxyde/models/utils.py +116 -0
  43. oxyde_postgres-0.1.0/oxyde/py.typed +0 -0
  44. oxyde_postgres-0.1.0/oxyde/queries/__init__.py +79 -0
  45. oxyde_postgres-0.1.0/oxyde/queries/aggregates.py +145 -0
  46. oxyde_postgres-0.1.0/oxyde/queries/base.py +249 -0
  47. oxyde_postgres-0.1.0/oxyde/queries/conditions.py +92 -0
  48. oxyde_postgres-0.1.0/oxyde/queries/expressions.py +171 -0
  49. oxyde_postgres-0.1.0/oxyde/queries/insert.py +124 -0
  50. oxyde_postgres-0.1.0/oxyde/queries/joins.py +66 -0
  51. oxyde_postgres-0.1.0/oxyde/queries/manager.py +409 -0
  52. oxyde_postgres-0.1.0/oxyde/queries/mixins/__init__.py +68 -0
  53. oxyde_postgres-0.1.0/oxyde/queries/mixins/aggregation.py +335 -0
  54. oxyde_postgres-0.1.0/oxyde/queries/mixins/debug.py +154 -0
  55. oxyde_postgres-0.1.0/oxyde/queries/mixins/execution.py +701 -0
  56. oxyde_postgres-0.1.0/oxyde/queries/mixins/filtering.py +144 -0
  57. oxyde_postgres-0.1.0/oxyde/queries/mixins/joining.py +160 -0
  58. oxyde_postgres-0.1.0/oxyde/queries/mixins/mutation.py +508 -0
  59. oxyde_postgres-0.1.0/oxyde/queries/mixins/pagination.py +145 -0
  60. oxyde_postgres-0.1.0/oxyde/queries/q.py +250 -0
  61. oxyde_postgres-0.1.0/oxyde/queries/raw.py +114 -0
  62. oxyde_postgres-0.1.0/oxyde/queries/select.py +345 -0
  63. oxyde_postgres-0.1.0/oxyde/queries/typed.py +213 -0
  64. oxyde_postgres-0.1.0/oxyde_postgres.egg-info/PKG-INFO +141 -0
  65. oxyde_postgres-0.1.0/oxyde_postgres.egg-info/SOURCES.txt +69 -0
  66. oxyde_postgres-0.1.0/oxyde_postgres.egg-info/dependency_links.txt +1 -0
  67. oxyde_postgres-0.1.0/oxyde_postgres.egg-info/entry_points.txt +2 -0
  68. oxyde_postgres-0.1.0/oxyde_postgres.egg-info/requires.txt +20 -0
  69. oxyde_postgres-0.1.0/oxyde_postgres.egg-info/top_level.txt +1 -0
  70. oxyde_postgres-0.1.0/pyproject.toml +138 -0
  71. 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,3 @@
1
+ import msgpack # type: ignore[import-untyped]
2
+
3
+ __all__ = ["msgpack"]
@@ -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")