oxyde-postgres 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oxyde/__init__.py +151 -0
- oxyde/_msgpack.py +3 -0
- oxyde/cli/__init__.py +7 -0
- oxyde/cli/app.py +107 -0
- oxyde/cli/codegen.py +66 -0
- oxyde/cli/config.py +84 -0
- oxyde/cli/migrations.py +536 -0
- oxyde/codegen/__init__.py +15 -0
- oxyde/codegen/stub_generator.py +1229 -0
- oxyde/core/__init__.py +90 -0
- oxyde/core/column_types.py +367 -0
- oxyde/core/ir.py +380 -0
- oxyde/core/types.py +69 -0
- oxyde/core/wrapper.py +242 -0
- oxyde/db/__init__.py +230 -0
- oxyde/db/pool.py +305 -0
- oxyde/db/registry.py +113 -0
- oxyde/db/schema.py +51 -0
- oxyde/db/transaction.py +392 -0
- oxyde/exceptions.py +89 -0
- oxyde/migrations/__init__.py +49 -0
- oxyde/migrations/config.py +187 -0
- oxyde/migrations/context.py +598 -0
- oxyde/migrations/executor.py +456 -0
- oxyde/migrations/extract.py +316 -0
- oxyde/migrations/generator.py +534 -0
- oxyde/migrations/replay.py +367 -0
- oxyde/migrations/squash.py +109 -0
- oxyde/migrations/tracker.py +162 -0
- oxyde/migrations/utils.py +145 -0
- oxyde/models/__init__.py +83 -0
- oxyde/models/base.py +871 -0
- oxyde/models/decorators.py +111 -0
- oxyde/models/field.py +204 -0
- oxyde/models/lookups.py +667 -0
- oxyde/models/metadata.py +174 -0
- oxyde/models/registry.py +170 -0
- oxyde/models/serializers.py +144 -0
- oxyde/models/utils.py +116 -0
- oxyde/py.typed +0 -0
- oxyde/queries/__init__.py +79 -0
- oxyde/queries/aggregates.py +145 -0
- oxyde/queries/base.py +249 -0
- oxyde/queries/conditions.py +92 -0
- oxyde/queries/expressions.py +171 -0
- oxyde/queries/insert.py +124 -0
- oxyde/queries/joins.py +66 -0
- oxyde/queries/manager.py +409 -0
- oxyde/queries/mixins/__init__.py +68 -0
- oxyde/queries/mixins/aggregation.py +335 -0
- oxyde/queries/mixins/debug.py +154 -0
- oxyde/queries/mixins/execution.py +701 -0
- oxyde/queries/mixins/filtering.py +144 -0
- oxyde/queries/mixins/joining.py +160 -0
- oxyde/queries/mixins/mutation.py +508 -0
- oxyde/queries/mixins/pagination.py +145 -0
- oxyde/queries/q.py +250 -0
- oxyde/queries/raw.py +114 -0
- oxyde/queries/select.py +345 -0
- oxyde/queries/typed.py +213 -0
- oxyde_postgres-0.1.0.dist-info/METADATA +141 -0
- oxyde_postgres-0.1.0.dist-info/RECORD +66 -0
- oxyde_postgres-0.1.0.dist-info/WHEEL +5 -0
- oxyde_postgres-0.1.0.dist-info/entry_points.txt +2 -0
- oxyde_postgres-0.1.0.dist-info/licenses/LICENSE +21 -0
- oxyde_postgres-0.1.0.dist-info/top_level.txt +1 -0
oxyde/__init__.py
ADDED
|
@@ -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
|
+
]
|
oxyde/_msgpack.py
ADDED
oxyde/cli/__init__.py
ADDED
|
@@ -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"]
|
oxyde/cli/app.py
ADDED
|
@@ -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
|
oxyde/cli/codegen.py
ADDED
|
@@ -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)
|
oxyde/cli/config.py
ADDED
|
@@ -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")
|