sqlobjects 1.1.0__tar.gz → 1.2.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.
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/CHANGELOG.md +6 -0
- {sqlobjects-1.1.0/sqlobjects.egg-info → sqlobjects-1.2.0}/PKG-INFO +1 -1
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/pyproject.toml +10 -10
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/metadata.py +33 -12
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/session.py +17 -3
- {sqlobjects-1.1.0 → sqlobjects-1.2.0/sqlobjects.egg-info}/PKG-INFO +1 -1
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/LICENSE +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/README.md +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/docs/rules/01-database-session-guide.md +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/docs/rules/02-model-definition-guide.md +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/docs/rules/03-query-operations-guide.md +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/docs/rules/04-crud-operations-guide.md +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/docs/rules/05-relationships-guide.md +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/docs/rules/06-validation-signals-guide.md +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/docs/rules/07-performance-guide.md +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/docs/rules/README.md +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/setup.cfg +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/__init__.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/_install_rules.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/cascade.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/database/__init__.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/database/config.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/database/manager.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/exceptions.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/expressions/__init__.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/expressions/aggregate.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/expressions/base.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/expressions/cte.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/expressions/explain.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/expressions/function.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/expressions/mixins.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/expressions/scalar.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/expressions/subquery.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/expressions/terminal.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/expressions/window.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/__init__.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/core.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/functions.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/proxies.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/relations/__init__.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/relations/descriptors.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/relations/managers.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/relations/prefetch.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/relations/strategies.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/relations/utils.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/shortcuts.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/types/__init__.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/types/base.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/types/comparators.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/types/registry.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/fields/utils.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/internal/__init__.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/internal/operations.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/internal/results.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/mixins.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/model.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/objects/__init__.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/objects/bulk.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/objects/core.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/objects/upsert.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/queries/__init__.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/queries/builder.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/queries/dialect.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/queries/executor.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/queryset.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/signals.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/utils/__init__.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/utils/inspect.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/utils/naming.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/utils/pattern.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects/validators.py +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects.egg-info/SOURCES.txt +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects.egg-info/dependency_links.txt +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects.egg-info/entry_points.txt +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects.egg-info/requires.txt +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/sqlobjects.egg-info/top_level.txt +0 -0
- {sqlobjects-1.1.0 → sqlobjects-1.2.0}/tests/test_config.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlobjects
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Django-style async ORM library based on SQLAlchemy with chainable queries, Q objects, and relationship loading
|
|
5
5
|
Author-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
|
|
6
6
|
Maintainer-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlobjects"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.2.0"
|
|
4
4
|
description = "Django-style async ORM library based on SQLAlchemy with chainable queries, Q objects, and relationship loading"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -49,22 +49,22 @@ Changelog = "https://github.com/XtraVisionsAI/sqlobjects/blob/main/CHANGELOG.md"
|
|
|
49
49
|
|
|
50
50
|
[dependency-groups]
|
|
51
51
|
dev = [
|
|
52
|
-
"pre-commit>=4.
|
|
53
|
-
"pyright>=1.1.
|
|
54
|
-
"ruff>=0.
|
|
55
|
-
"setuptools>=
|
|
52
|
+
"pre-commit>=4.5.1",
|
|
53
|
+
"pyright>=1.1.408",
|
|
54
|
+
"ruff>=0.15.2",
|
|
55
|
+
"setuptools>=82.0.0",
|
|
56
56
|
]
|
|
57
57
|
test = [
|
|
58
58
|
"aiomysql>=0.3.2",
|
|
59
|
-
"aiosqlite>=0.
|
|
60
|
-
"asyncpg>=0.
|
|
61
|
-
"pytest>=9.0.
|
|
59
|
+
"aiosqlite>=0.22.1",
|
|
60
|
+
"asyncpg>=0.31.0",
|
|
61
|
+
"pytest>=9.0.2",
|
|
62
62
|
"pytest-asyncio>=1.3.0",
|
|
63
|
-
"psutil>=7.
|
|
63
|
+
"psutil>=7.2.2", # For memory monitoring in performance tests
|
|
64
64
|
]
|
|
65
65
|
|
|
66
66
|
[build-system]
|
|
67
|
-
requires = ["setuptools>=
|
|
67
|
+
requires = ["setuptools>=82.0.0", "wheel"]
|
|
68
68
|
build-backend = "setuptools.build_meta"
|
|
69
69
|
|
|
70
70
|
[tool.setuptools]
|
|
@@ -2,7 +2,7 @@ import re
|
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
from typing import TYPE_CHECKING, Any, Union, cast
|
|
4
4
|
|
|
5
|
-
from sqlalchemy import CheckConstraint, Index, UniqueConstraint
|
|
5
|
+
from sqlalchemy import CheckConstraint, ForeignKeyConstraint, Index, UniqueConstraint
|
|
6
6
|
from sqlalchemy import MetaData as SqlAlchemyMetaData
|
|
7
7
|
|
|
8
8
|
from .fields import ColumnAttribute
|
|
@@ -24,8 +24,8 @@ __all__ = [
|
|
|
24
24
|
"unique",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
|
-
|
|
28
27
|
_FIELD_NAME_PATTERN = re.compile(r"\b([a-zA-Z_][a-zA-Z0-9_]*)\b")
|
|
28
|
+
_TEMP_INDEX_PREFIX = "__temp__idx_"
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
@dataclass
|
|
@@ -574,7 +574,9 @@ class ModelProcessor(type):
|
|
|
574
574
|
|
|
575
575
|
@classmethod
|
|
576
576
|
def _normalize_all_indexes(mcs, indexes: list[Index], table_name: str) -> list[Index]:
|
|
577
|
-
"""
|
|
577
|
+
"""Normalize index names that need normalization.
|
|
578
|
+
|
|
579
|
+
Only normalizes indexes with temporary names (starting with __temp__idx_).
|
|
578
580
|
|
|
579
581
|
Args:
|
|
580
582
|
indexes: List of indexes to normalize
|
|
@@ -586,6 +588,11 @@ class ModelProcessor(type):
|
|
|
586
588
|
normalized_indexes = []
|
|
587
589
|
|
|
588
590
|
for idx in indexes:
|
|
591
|
+
# Only normalize temporary names
|
|
592
|
+
if not idx.name or not idx.name.startswith(_TEMP_INDEX_PREFIX):
|
|
593
|
+
normalized_indexes.append(idx)
|
|
594
|
+
continue
|
|
595
|
+
|
|
589
596
|
# Get field name list
|
|
590
597
|
if hasattr(idx, "columns") and idx.columns:
|
|
591
598
|
field_names = "_".join(col.name for col in idx.columns) # noqa
|
|
@@ -597,10 +604,7 @@ class ModelProcessor(type):
|
|
|
597
604
|
continue
|
|
598
605
|
|
|
599
606
|
# Generate standardized name
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
# Directly modify index name (instead of rebuilding)
|
|
603
|
-
idx.name = new_name # type: ignore[reportAttributeAccessIssue]
|
|
607
|
+
idx.name = f"idx_{table_name}_{field_names}" # type: ignore[reportAttributeAccessIssue]
|
|
604
608
|
normalized_indexes.append(idx)
|
|
605
609
|
|
|
606
610
|
return normalized_indexes
|
|
@@ -683,15 +687,20 @@ class ModelProcessor(type):
|
|
|
683
687
|
def _post_process_table_indexes(mcs, table, table_name: str) -> None:
|
|
684
688
|
"""Normalize index names after table construction.
|
|
685
689
|
|
|
690
|
+
Only normalizes indexes with temporary names (starting with __temp__idx_).
|
|
691
|
+
|
|
686
692
|
Args:
|
|
687
693
|
table: SQLAlchemy Table instance
|
|
688
694
|
table_name: Database table name
|
|
689
695
|
"""
|
|
690
696
|
for idx in table.indexes:
|
|
697
|
+
# Only normalize temporary names
|
|
698
|
+
if not idx.name or not idx.name.startswith(_TEMP_INDEX_PREFIX):
|
|
699
|
+
continue
|
|
700
|
+
|
|
691
701
|
if hasattr(idx, "columns") and idx.columns:
|
|
692
702
|
field_names = "_".join(col.name for col in idx.columns)
|
|
693
|
-
|
|
694
|
-
idx.name = new_name
|
|
703
|
+
idx.name = f"idx_{table_name}_{field_names}"
|
|
695
704
|
|
|
696
705
|
@classmethod
|
|
697
706
|
def _post_process_table_constraints(mcs, table, table_name: str) -> None:
|
|
@@ -714,6 +723,20 @@ class ModelProcessor(type):
|
|
|
714
723
|
elif isinstance(cst, UniqueConstraint) and hasattr(cst, "columns"):
|
|
715
724
|
field_names = "_".join(col.name for col in cst.columns)
|
|
716
725
|
cst.name = f"uq_{table_name}_{field_names}"
|
|
726
|
+
elif isinstance(cst, ForeignKeyConstraint) and hasattr(cst, "columns"):
|
|
727
|
+
# Handle foreign key constraints
|
|
728
|
+
field_names = "_".join(col.name for col in cst.columns)
|
|
729
|
+
# Get referenced table and column names
|
|
730
|
+
if cst.elements:
|
|
731
|
+
try:
|
|
732
|
+
ref_table = cst.elements[0].column.table.name
|
|
733
|
+
ref_columns = "_".join(elem.column.name for elem in cst.elements)
|
|
734
|
+
cst.name = f"fk_{table_name}_{field_names}_{ref_table}_{ref_columns}"
|
|
735
|
+
except Exception:
|
|
736
|
+
# Fallback if reference cannot be resolved yet
|
|
737
|
+
cst.name = f"fk_{table_name}_{field_names}"
|
|
738
|
+
else:
|
|
739
|
+
cst.name = f"fk_{table_name}_{field_names}"
|
|
717
740
|
|
|
718
741
|
@classmethod
|
|
719
742
|
def _apply_dataclass_functionality(mcs, cls: Any) -> Any:
|
|
@@ -1114,11 +1137,9 @@ def index(
|
|
|
1114
1137
|
>>> index("idx_users_status", "status", postgresql_where="status = 'active'")
|
|
1115
1138
|
>>> index("idx_users_tags", "tags", postgresql_using="gin")
|
|
1116
1139
|
"""
|
|
1117
|
-
# Note: Don't auto-generate name here because table_name is needed
|
|
1118
|
-
# Actual name normalization is handled in _merge_indexes
|
|
1119
1140
|
if name is None:
|
|
1120
1141
|
field_part = "_".join(fields)
|
|
1121
|
-
name = f"
|
|
1142
|
+
name = f"{_TEMP_INDEX_PREFIX}{field_part}"
|
|
1122
1143
|
|
|
1123
1144
|
# Build dialect-specific kwargs
|
|
1124
1145
|
dialect_kwargs = {}
|
|
@@ -3,7 +3,7 @@ from collections.abc import AsyncGenerator
|
|
|
3
3
|
from contextlib import asynccontextmanager
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
-
from sqlalchemy import CursorResult
|
|
6
|
+
from sqlalchemy import CursorResult, text
|
|
7
7
|
from sqlalchemy.exc import SQLAlchemyError
|
|
8
8
|
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncResult
|
|
9
9
|
|
|
@@ -13,7 +13,6 @@ from .exceptions import convert_sqlalchemy_error
|
|
|
13
13
|
|
|
14
14
|
__all__ = ["AsyncSession", "ctx_session", "ctx_sessions", "get_session", "has_session"]
|
|
15
15
|
|
|
16
|
-
|
|
17
16
|
# Explicit session management (highest priority)
|
|
18
17
|
_explicit_sessions: contextvars.ContextVar[dict[str, "AsyncSession"]] = contextvars.ContextVar("explicit_sessions")
|
|
19
18
|
|
|
@@ -64,9 +63,17 @@ class AsyncSession:
|
|
|
64
63
|
return get_database(self._db_name).engine
|
|
65
64
|
|
|
66
65
|
async def execute(self, statement: Any, parameters: Any = None) -> CursorResult[Any]:
|
|
67
|
-
"""Execute statement with automatic transaction management.
|
|
66
|
+
"""Execute statement with automatic transaction management.
|
|
67
|
+
|
|
68
|
+
Supports both SQLAlchemy statement objects and raw SQL strings.
|
|
69
|
+
Raw SQL strings are automatically wrapped with text().
|
|
70
|
+
"""
|
|
68
71
|
await self._ensure_connection()
|
|
69
72
|
|
|
73
|
+
# Auto-wrap string SQL with text()
|
|
74
|
+
if isinstance(statement, str):
|
|
75
|
+
statement = text(statement)
|
|
76
|
+
|
|
70
77
|
# Auto-begin transaction for non-readonly sessions
|
|
71
78
|
if not self.readonly and self._trans is None:
|
|
72
79
|
self._trans = await self._conn.begin() # type: ignore
|
|
@@ -89,6 +96,9 @@ class AsyncSession:
|
|
|
89
96
|
async def stream(self, statement: Any, parameters: Any = None) -> AsyncResult[Any]:
|
|
90
97
|
"""Execute statement and return streaming result.
|
|
91
98
|
|
|
99
|
+
Supports both SQLAlchemy statement objects and raw SQL strings.
|
|
100
|
+
Raw SQL strings are automatically wrapped with text().
|
|
101
|
+
|
|
92
102
|
Note: stream() is not supported with auto_commit=True sessions.
|
|
93
103
|
Use explicit sessions (ctx_session) for streaming operations.
|
|
94
104
|
"""
|
|
@@ -99,6 +109,10 @@ class AsyncSession:
|
|
|
99
109
|
|
|
100
110
|
await self._ensure_connection()
|
|
101
111
|
|
|
112
|
+
# Auto-wrap string SQL with text()
|
|
113
|
+
if isinstance(statement, str):
|
|
114
|
+
statement = text(statement)
|
|
115
|
+
|
|
102
116
|
# Auto-begin transaction for non-readonly sessions
|
|
103
117
|
if not self.readonly and self._trans is None:
|
|
104
118
|
self._trans = await self._conn.begin() # type: ignore
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlobjects
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Django-style async ORM library based on SQLAlchemy with chainable queries, Q objects, and relationship loading
|
|
5
5
|
Author-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
|
|
6
6
|
Maintainer-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|