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