sqlobjects 1.2.0__tar.gz → 1.2.2__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.2.0 → sqlobjects-1.2.2}/CHANGELOG.md +12 -0
- {sqlobjects-1.2.0/sqlobjects.egg-info → sqlobjects-1.2.2}/PKG-INFO +1 -1
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/pyproject.toml +1 -1
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/metadata.py +18 -6
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/mixins.py +1 -4
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/objects/core.py +1 -6
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/queries/dialect.py +5 -4
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/queries/executor.py +20 -9
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/session.py +8 -3
- {sqlobjects-1.2.0 → sqlobjects-1.2.2/sqlobjects.egg-info}/PKG-INFO +1 -1
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/LICENSE +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/README.md +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/docs/rules/01-database-session-guide.md +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/docs/rules/02-model-definition-guide.md +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/docs/rules/03-query-operations-guide.md +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/docs/rules/04-crud-operations-guide.md +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/docs/rules/05-relationships-guide.md +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/docs/rules/06-validation-signals-guide.md +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/docs/rules/07-performance-guide.md +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/docs/rules/README.md +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/setup.cfg +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/__init__.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/_install_rules.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/cascade.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/database/__init__.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/database/config.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/database/manager.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/exceptions.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/expressions/__init__.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/expressions/aggregate.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/expressions/base.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/expressions/cte.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/expressions/explain.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/expressions/function.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/expressions/mixins.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/expressions/scalar.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/expressions/subquery.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/expressions/terminal.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/expressions/window.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/__init__.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/core.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/functions.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/proxies.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/relations/__init__.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/relations/descriptors.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/relations/managers.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/relations/prefetch.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/relations/strategies.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/relations/utils.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/shortcuts.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/types/__init__.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/types/base.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/types/comparators.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/types/registry.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/fields/utils.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/internal/__init__.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/internal/operations.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/internal/results.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/model.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/objects/__init__.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/objects/bulk.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/objects/upsert.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/queries/__init__.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/queries/builder.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/queryset.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/signals.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/utils/__init__.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/utils/inspect.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/utils/naming.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/utils/pattern.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects/validators.py +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects.egg-info/SOURCES.txt +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects.egg-info/dependency_links.txt +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects.egg-info/entry_points.txt +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects.egg-info/requires.txt +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/sqlobjects.egg-info/top_level.txt +0 -0
- {sqlobjects-1.2.0 → sqlobjects-1.2.2}/tests/test_config.py +0 -0
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## 1.2.2 (2026-02-26)
|
|
2
|
+
|
|
3
|
+
### Fix
|
|
4
|
+
|
|
5
|
+
- clone Column descriptor per subclass to prevent shared ColumnAttribute binding
|
|
6
|
+
|
|
7
|
+
## 1.2.1 (2026-02-25)
|
|
8
|
+
|
|
9
|
+
### Refactor
|
|
10
|
+
|
|
11
|
+
- centralize session resolution in get_session()
|
|
12
|
+
|
|
1
13
|
## 1.2.0 (2026-02-25)
|
|
2
14
|
|
|
3
15
|
### Feat
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlobjects
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
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>
|
|
@@ -938,13 +938,25 @@ class ModelProcessor(type):
|
|
|
938
938
|
cls: Model class
|
|
939
939
|
table: SQLAlchemy Table instance
|
|
940
940
|
"""
|
|
941
|
-
for name
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
if
|
|
945
|
-
|
|
946
|
-
if
|
|
941
|
+
for name in table.columns.keys():
|
|
942
|
+
for klass in cls.__mro__:
|
|
943
|
+
descriptor = klass.__dict__.get(name)
|
|
944
|
+
if descriptor is not None and hasattr(descriptor, "_column_attribute"):
|
|
945
|
+
column_attr = descriptor._column_attribute
|
|
946
|
+
if column_attr is None or not hasattr(column_attr, "__column__"):
|
|
947
|
+
break
|
|
948
|
+
if klass is cls:
|
|
947
949
|
column_attr.__column__ = table.columns[name] # type: ignore[reportAttributeAccessIssue]
|
|
950
|
+
else:
|
|
951
|
+
new_col_attr = object.__new__(type(column_attr))
|
|
952
|
+
new_col_attr.__dict__.update(column_attr.__dict__)
|
|
953
|
+
new_col_attr.__column__ = table.columns[name]
|
|
954
|
+
new_col_attr.model_class = cls
|
|
955
|
+
new_descriptor = object.__new__(type(descriptor))
|
|
956
|
+
new_descriptor.__dict__.update(descriptor.__dict__)
|
|
957
|
+
new_descriptor._column_attribute = new_col_attr
|
|
958
|
+
type.__setattr__(cls, name, new_descriptor)
|
|
959
|
+
break
|
|
948
960
|
|
|
949
961
|
@classmethod
|
|
950
962
|
def _initialize_field_cache(mcs, cls: Any) -> None:
|
|
@@ -165,10 +165,7 @@ class SessionMixin(BaseMixin):
|
|
|
165
165
|
Returns:
|
|
166
166
|
AsyncSession instance for database operations
|
|
167
167
|
"""
|
|
168
|
-
|
|
169
|
-
if isinstance(bound_session, str):
|
|
170
|
-
return get_session(bound_session)
|
|
171
|
-
return bound_session or get_session()
|
|
168
|
+
return get_session(self._state_manager.get_bound_session())
|
|
172
169
|
|
|
173
170
|
def using(self, db_or_session: str | AsyncSession):
|
|
174
171
|
"""Return self bound to specific database/connection.
|
|
@@ -82,12 +82,7 @@ class ObjectsManager(Generic[T]):
|
|
|
82
82
|
Returns:
|
|
83
83
|
AsyncSession instance
|
|
84
84
|
"""
|
|
85
|
-
|
|
86
|
-
return get_session(readonly=readonly)
|
|
87
|
-
elif isinstance(self._db_or_session, str):
|
|
88
|
-
return get_session(self._db_or_session, readonly=readonly)
|
|
89
|
-
else:
|
|
90
|
-
return self._db_or_session
|
|
85
|
+
return get_session(self._db_or_session, readonly=readonly)
|
|
91
86
|
|
|
92
87
|
def _validate_field_names(self, **kwargs) -> None:
|
|
93
88
|
"""Validate that all field names exist on the model.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Database dialect handlers for database-specific SQL generation."""
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Sequence
|
|
4
5
|
from typing import TYPE_CHECKING, Any
|
|
5
6
|
|
|
6
7
|
import sqlalchemy as sa
|
|
@@ -46,7 +47,7 @@ class BaseDialect(ABC):
|
|
|
46
47
|
pass
|
|
47
48
|
|
|
48
49
|
@abstractmethod
|
|
49
|
-
def parse_explain_result(self, rows:
|
|
50
|
+
def parse_explain_result(self, rows: Sequence) -> str:
|
|
50
51
|
"""Parse EXPLAIN result for the database."""
|
|
51
52
|
pass
|
|
52
53
|
|
|
@@ -108,7 +109,7 @@ class PostgreSQLDialect(BaseDialect):
|
|
|
108
109
|
return f"EXPLAIN ({', '.join(options)}) {sql}"
|
|
109
110
|
return f"EXPLAIN {sql}"
|
|
110
111
|
|
|
111
|
-
def parse_explain_result(self, rows:
|
|
112
|
+
def parse_explain_result(self, rows: Sequence) -> str:
|
|
112
113
|
"""Parse PostgreSQL EXPLAIN result."""
|
|
113
114
|
return "\n".join(str(row[0]) for row in rows)
|
|
114
115
|
|
|
@@ -151,7 +152,7 @@ class MySQLDialect(BaseDialect):
|
|
|
151
152
|
return f"EXPLAIN ANALYZE {sql}"
|
|
152
153
|
return f"EXPLAIN {sql}"
|
|
153
154
|
|
|
154
|
-
def parse_explain_result(self, rows:
|
|
155
|
+
def parse_explain_result(self, rows: Sequence) -> str:
|
|
155
156
|
"""Parse MySQL EXPLAIN result."""
|
|
156
157
|
return "\n".join(str(row[0]) for row in rows)
|
|
157
158
|
|
|
@@ -199,7 +200,7 @@ class SQLiteDialect(BaseDialect):
|
|
|
199
200
|
"""
|
|
200
201
|
return f"EXPLAIN QUERY PLAN {sql}"
|
|
201
202
|
|
|
202
|
-
def parse_explain_result(self, rows:
|
|
203
|
+
def parse_explain_result(self, rows: Sequence) -> str:
|
|
203
204
|
"""Parse SQLite EXPLAIN result.
|
|
204
205
|
|
|
205
206
|
SQLite EXPLAIN QUERY PLAN returns: (id, parent, notused, detail)
|
|
@@ -18,13 +18,22 @@ class QueryExecutor:
|
|
|
18
18
|
aggregations, and memory-efficient iteration for large datasets.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
+
_WRITE_TYPES = frozenset({"update", "delete"})
|
|
22
|
+
|
|
21
23
|
def __init__(self, session=None):
|
|
22
24
|
"""Initialize executor with optional session.
|
|
23
25
|
|
|
24
26
|
Args:
|
|
25
|
-
session: Database session
|
|
27
|
+
session: Database session, database name string, or None
|
|
26
28
|
"""
|
|
27
|
-
self.
|
|
29
|
+
self._db_or_session = session
|
|
30
|
+
|
|
31
|
+
def _get_session(self, query_type: str):
|
|
32
|
+
"""Resolve session with appropriate readonly flag."""
|
|
33
|
+
from ..session import get_session
|
|
34
|
+
|
|
35
|
+
readonly = query_type not in self._WRITE_TYPES
|
|
36
|
+
return get_session(self._db_or_session, readonly=readonly)
|
|
28
37
|
|
|
29
38
|
async def execute(
|
|
30
39
|
self,
|
|
@@ -115,23 +124,24 @@ class QueryExecutor:
|
|
|
115
124
|
Returns:
|
|
116
125
|
Query execution plan as string
|
|
117
126
|
"""
|
|
118
|
-
|
|
127
|
+
session = self._get_session("all")
|
|
128
|
+
if not session:
|
|
119
129
|
raise RuntimeError("No session available for explain operation")
|
|
120
130
|
|
|
121
131
|
# Get dialect handler
|
|
122
132
|
from .dialect import DialectHandler
|
|
123
133
|
|
|
124
|
-
dialect = DialectHandler.create(
|
|
134
|
+
dialect = DialectHandler.create(session)
|
|
125
135
|
|
|
126
136
|
# Compile query to SQL
|
|
127
|
-
compiled = query.compile(dialect=
|
|
137
|
+
compiled = query.compile(dialect=session.bind.dialect, compile_kwargs={"literal_binds": True})
|
|
128
138
|
sql_str = str(compiled)
|
|
129
139
|
|
|
130
140
|
# Build EXPLAIN query using dialect handler
|
|
131
141
|
explain_sql = dialect.build_explain_query(sql_str, analyze, verbose)
|
|
132
142
|
|
|
133
143
|
# Execute query
|
|
134
|
-
result = await
|
|
144
|
+
result = await session.execute(text(explain_sql))
|
|
135
145
|
rows = result.fetchall()
|
|
136
146
|
|
|
137
147
|
# Parse result using dialect handler
|
|
@@ -196,7 +206,8 @@ class QueryExecutor:
|
|
|
196
206
|
relationships=None,
|
|
197
207
|
):
|
|
198
208
|
"""Execute query and return appropriate result."""
|
|
199
|
-
|
|
209
|
+
session = self._get_session(query_type)
|
|
210
|
+
if not session:
|
|
200
211
|
if query_type == "all":
|
|
201
212
|
return []
|
|
202
213
|
elif query_type in ("count", "update", "delete"):
|
|
@@ -206,7 +217,7 @@ class QueryExecutor:
|
|
|
206
217
|
else: # exists
|
|
207
218
|
return False
|
|
208
219
|
|
|
209
|
-
result = await
|
|
220
|
+
result = await session.execute(query)
|
|
210
221
|
|
|
211
222
|
if query_type == "all":
|
|
212
223
|
rows = result.fetchall()
|
|
@@ -417,7 +428,7 @@ class QueryExecutor:
|
|
|
417
428
|
if not instances or not prefetch_relationships:
|
|
418
429
|
return instances
|
|
419
430
|
|
|
420
|
-
prefetch_handler = PrefetchHandler(self.
|
|
431
|
+
prefetch_handler = PrefetchHandler(self._get_session("all"))
|
|
421
432
|
return await prefetch_handler.handle_prefetch_relationships(instances, prefetch_relationships)
|
|
422
433
|
|
|
423
434
|
@staticmethod
|
|
@@ -345,11 +345,13 @@ async def ctx_sessions(*db_names: str) -> AsyncGenerator[dict[str, AsyncSession]
|
|
|
345
345
|
_SessionContextManager.clear_session(db_name)
|
|
346
346
|
|
|
347
347
|
|
|
348
|
-
def get_session(
|
|
348
|
+
def get_session(
|
|
349
|
+
db_or_name: str | AsyncSession | None = None, readonly: bool = True, auto_commit: bool = True
|
|
350
|
+
) -> AsyncSession:
|
|
349
351
|
"""Get database session with readonly optimization.
|
|
350
352
|
|
|
351
353
|
Args:
|
|
352
|
-
|
|
354
|
+
db_or_name: Database name, existing AsyncSession, or None for default database
|
|
353
355
|
readonly: True for readonly (no transaction), False for transactional
|
|
354
356
|
auto_commit: True to auto-commit transactions (ignored if readonly=True)
|
|
355
357
|
|
|
@@ -357,10 +359,13 @@ def get_session(db_name: str | None = None, readonly: bool = True, auto_commit:
|
|
|
357
359
|
AsyncSession instance
|
|
358
360
|
|
|
359
361
|
Priority:
|
|
362
|
+
- If db_or_name is an AsyncSession, return it directly
|
|
360
363
|
- First try use explicitly set session (ctx_session, ctx_sessions)
|
|
361
364
|
- Create a new AsyncSession with specified parameters if no explicit session
|
|
362
365
|
"""
|
|
363
|
-
|
|
366
|
+
if isinstance(db_or_name, AsyncSession):
|
|
367
|
+
return db_or_name
|
|
368
|
+
return _SessionContextManager.get_session(db_or_name, readonly, auto_commit)
|
|
364
369
|
|
|
365
370
|
|
|
366
371
|
def has_session(db_name: str | None = None) -> bool:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlobjects
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
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
|