sqlobjects 1.2.4__tar.gz → 1.3.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.2.4 → sqlobjects-1.3.0}/CHANGELOG.md +13 -0
- {sqlobjects-1.2.4/sqlobjects.egg-info → sqlobjects-1.3.0}/PKG-INFO +1 -1
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/pyproject.toml +1 -1
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/expressions/function.py +2 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/expressions/mixins.py +19 -3
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/functions.py +34 -5
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/types/comparators.py +54 -5
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/types/registry.py +6 -4
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/metadata.py +37 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0/sqlobjects.egg-info}/PKG-INFO +1 -1
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/LICENSE +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/README.md +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/docs/rules/01-database-session-guide.md +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/docs/rules/02-model-definition-guide.md +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/docs/rules/03-query-operations-guide.md +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/docs/rules/04-crud-operations-guide.md +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/docs/rules/05-relationships-guide.md +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/docs/rules/06-validation-signals-guide.md +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/docs/rules/07-performance-guide.md +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/docs/rules/README.md +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/setup.cfg +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/__init__.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/_install_rules.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/cascade.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/database/__init__.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/database/config.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/database/manager.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/exceptions.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/expressions/__init__.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/expressions/aggregate.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/expressions/base.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/expressions/cte.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/expressions/explain.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/expressions/scalar.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/expressions/subquery.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/expressions/terminal.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/expressions/window.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/__init__.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/core.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/proxies.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/relations/__init__.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/relations/descriptors.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/relations/managers.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/relations/prefetch.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/relations/strategies.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/relations/utils.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/shortcuts.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/types/__init__.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/types/base.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/fields/utils.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/internal/__init__.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/internal/operations.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/internal/results.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/mixins.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/model.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/objects/__init__.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/objects/bulk.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/objects/core.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/objects/upsert.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/queries/__init__.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/queries/builder.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/queries/dialect.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/queries/executor.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/queryset.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/session.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/signals.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/utils/__init__.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/utils/inspect.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/utils/naming.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/utils/pattern.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects/validators.py +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects.egg-info/SOURCES.txt +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects.egg-info/dependency_links.txt +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects.egg-info/entry_points.txt +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects.egg-info/requires.txt +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/sqlobjects.egg-info/top_level.txt +0 -0
- {sqlobjects-1.2.4 → sqlobjects-1.3.0}/tests/test_config.py +0 -0
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## 1.3.0 (2026-03-10)
|
|
2
|
+
|
|
3
|
+
### Feat
|
|
4
|
+
|
|
5
|
+
- **fields**: support class name reference in foreign_key() with delayed matching
|
|
6
|
+
|
|
7
|
+
## 1.2.5 (2026-03-06)
|
|
8
|
+
|
|
9
|
+
### Fix
|
|
10
|
+
|
|
11
|
+
- **examples,docs**: fix PGVECTOR type definition and documentation errors
|
|
12
|
+
- **types**: fix JSON/JSONB field containment query generating wrong SQL
|
|
13
|
+
|
|
1
14
|
## 1.2.4 (2026-02-28)
|
|
2
15
|
|
|
3
16
|
### Fix
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlobjects
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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>
|
|
@@ -147,6 +147,8 @@ class FunctionExpression:
|
|
|
147
147
|
for building complex database expressions.
|
|
148
148
|
"""
|
|
149
149
|
|
|
150
|
+
is_clause_element = False # prevent __getattr__ from proxying this to expression.is_clause_element
|
|
151
|
+
|
|
150
152
|
if TYPE_CHECKING:
|
|
151
153
|
# Inherit all method hints for IDE support
|
|
152
154
|
def __new__(cls, *args, **kwargs) -> "FunctionExpression & _FunctionMethods": ... # type: ignore
|
|
@@ -247,9 +247,25 @@ class ColumnAttributeFunctionMixin(FunctionMixin):
|
|
|
247
247
|
def extract(self, field: str) -> "FunctionExpression": ...
|
|
248
248
|
def date_trunc(self, precision: str) -> "FunctionExpression": ...
|
|
249
249
|
|
|
250
|
-
# JSON methods
|
|
251
|
-
def
|
|
252
|
-
def
|
|
250
|
+
# JSON methods (MySQL json type)
|
|
251
|
+
def json_extract(self, path: str) -> "FunctionExpression": ...
|
|
252
|
+
def json_unquote(self, path: str) -> "FunctionExpression": ...
|
|
253
|
+
def json_contains(self, other, path: str | None = None) -> "FunctionExpression": ...
|
|
254
|
+
def json_contains_path(self, *paths: str, match: str = "one") -> "FunctionExpression": ...
|
|
255
|
+
def json_length(self, path: str | None = None) -> "FunctionExpression": ...
|
|
256
|
+
def json_keys(self, path: str | None = None) -> "FunctionExpression": ...
|
|
257
|
+
def json_overlaps(self, other) -> "FunctionExpression": ...
|
|
258
|
+
def json_search(self, value: str, match: str = "one", path: str | None = None) -> "FunctionExpression": ...
|
|
259
|
+
def json_type(self) -> "FunctionExpression": ...
|
|
260
|
+
|
|
261
|
+
# JSONB methods (PostgreSQL jsonb type)
|
|
262
|
+
def contained_by(self, other) -> "FunctionExpression": ...
|
|
263
|
+
def has_key(self, other) -> "FunctionExpression": ...
|
|
264
|
+
def has_all(self, other) -> "FunctionExpression": ...
|
|
265
|
+
def has_any(self, other) -> "FunctionExpression": ...
|
|
266
|
+
def path_exists(self, other) -> "FunctionExpression": ...
|
|
267
|
+
def path_match(self, other) -> "FunctionExpression": ...
|
|
268
|
+
def delete_path(self, array) -> "FunctionExpression": ...
|
|
253
269
|
|
|
254
270
|
# Common methods (all types)
|
|
255
271
|
def sum(self) -> "FunctionExpression": ...
|
|
@@ -8,6 +8,22 @@ from .core import Column, column
|
|
|
8
8
|
from .shortcuts import ComputedColumn, IdentityColumn
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
def _extract_table_or_class_name(reference: str) -> str | None:
|
|
12
|
+
"""从 'X.col' 或 'schema.X.col' 中提取 X(可能是表名或类名)。
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
X 部分,用于后续延迟匹配;格式不合法时返回 None
|
|
16
|
+
"""
|
|
17
|
+
parts = reference.split(".")
|
|
18
|
+
if len(parts) == 2:
|
|
19
|
+
# "X.col"
|
|
20
|
+
return parts[0]
|
|
21
|
+
if len(parts) == 3:
|
|
22
|
+
# "schema.X.col"
|
|
23
|
+
return parts[1]
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
|
|
11
27
|
def identity(
|
|
12
28
|
*,
|
|
13
29
|
start: int = 1,
|
|
@@ -76,7 +92,10 @@ def foreign_key(
|
|
|
76
92
|
"""Create foreign key column with database constraint behavior.
|
|
77
93
|
|
|
78
94
|
Args:
|
|
79
|
-
reference: Foreign key reference in format "
|
|
95
|
+
reference: Foreign key reference in format "X.column" where X can be either
|
|
96
|
+
a model class name (e.g. "User.id", "UserProfile.id") or a database table
|
|
97
|
+
name (e.g. "users.id"). Class names are automatically resolved to table
|
|
98
|
+
names via delayed matching. Schema is also supported: "schema.X.column".
|
|
80
99
|
type: Column type, "auto" for automatic type inference
|
|
81
100
|
nullable: Whether column can be null
|
|
82
101
|
ondelete: Database constraint behavior when referenced object is deleted
|
|
@@ -89,12 +108,15 @@ def foreign_key(
|
|
|
89
108
|
Column descriptor with foreign key constraint
|
|
90
109
|
|
|
91
110
|
Examples:
|
|
92
|
-
#
|
|
111
|
+
# Using class name (resolved automatically)
|
|
112
|
+
author_id: Column[int] = foreign_key("User.id")
|
|
113
|
+
|
|
114
|
+
# Using table name directly (also works)
|
|
93
115
|
author_id: Column[int] = foreign_key("users.id")
|
|
94
116
|
|
|
95
117
|
# Complete constraint configuration
|
|
96
118
|
author_id: Column[int] = foreign_key(
|
|
97
|
-
"
|
|
119
|
+
"User.id",
|
|
98
120
|
ondelete="CASCADE",
|
|
99
121
|
onupdate="CASCADE",
|
|
100
122
|
nullable=False
|
|
@@ -102,7 +124,7 @@ def foreign_key(
|
|
|
102
124
|
|
|
103
125
|
# Deferred constraint for circular references
|
|
104
126
|
parent_id: Column[int] = foreign_key(
|
|
105
|
-
"
|
|
127
|
+
"Category.id",
|
|
106
128
|
deferrable=True,
|
|
107
129
|
initially="DEFERRED"
|
|
108
130
|
)
|
|
@@ -122,9 +144,16 @@ def foreign_key(
|
|
|
122
144
|
fk_kwargs["deferrable"] = True
|
|
123
145
|
fk_kwargs["initially"] = initially
|
|
124
146
|
|
|
125
|
-
#
|
|
147
|
+
# Reference is passed directly to SQLAlchemy; delayed matching in
|
|
148
|
+
# ModelProcessor._resolve_class_foreign_keys will correct the table
|
|
149
|
+
# name if the first segment turns out to be a class name.
|
|
126
150
|
fk_constraint = ForeignKey(reference, **fk_kwargs)
|
|
127
151
|
|
|
152
|
+
# Store the table-or-class segment for delayed resolution
|
|
153
|
+
table_or_class = _extract_table_or_class_name(reference)
|
|
154
|
+
if table_or_class:
|
|
155
|
+
fk_constraint.info["_fk_ref"] = table_or_class
|
|
156
|
+
|
|
128
157
|
# Use existing column() function with foreign key
|
|
129
158
|
return column(
|
|
130
159
|
type=type,
|
|
@@ -323,13 +323,62 @@ class DateTimeComparator(ComparatorMixin, DateTime.Comparator): # type: ignore[
|
|
|
323
323
|
|
|
324
324
|
|
|
325
325
|
class JSONComparator(ComparatorMixin, JSON.Comparator): # type: ignore[reportIncompatibleMethodOverride]
|
|
326
|
-
"""JSON type comparator
|
|
326
|
+
"""MySQL JSON type comparator. For PostgreSQL, use jsonb type instead."""
|
|
327
327
|
|
|
328
|
-
def
|
|
329
|
-
|
|
328
|
+
def json_extract(self, path: str) -> FunctionExpression:
|
|
329
|
+
"""JSON_EXTRACT(col, path) - e.g. json_extract('$.name')"""
|
|
330
|
+
return FunctionExpression(func.json_extract(self, path))
|
|
330
331
|
|
|
331
|
-
def
|
|
332
|
-
|
|
332
|
+
def json_unquote(self, path: str) -> FunctionExpression:
|
|
333
|
+
"""JSON_UNQUOTE(JSON_EXTRACT(col, path)) - returns unquoted string value"""
|
|
334
|
+
return FunctionExpression(func.json_unquote(func.json_extract(self, path)))
|
|
335
|
+
|
|
336
|
+
def json_contains(self, other, path: str | None = None) -> FunctionExpression:
|
|
337
|
+
"""JSON_CONTAINS(col, val[, path]) - check if JSON contains value"""
|
|
338
|
+
import json
|
|
339
|
+
|
|
340
|
+
from sqlalchemy import literal
|
|
341
|
+
|
|
342
|
+
value = [other] if not isinstance(other, (list, dict)) else other
|
|
343
|
+
json_val = literal(json.dumps(value))
|
|
344
|
+
if path is not None:
|
|
345
|
+
return FunctionExpression(func.json_contains(self, json_val, path))
|
|
346
|
+
return FunctionExpression(func.json_contains(self, json_val))
|
|
347
|
+
|
|
348
|
+
def json_contains_path(self, *paths: str, match: str = "one") -> FunctionExpression:
|
|
349
|
+
"""JSON_CONTAINS_PATH(col, 'one'|'all', path, ...) - check if path(s) exist"""
|
|
350
|
+
return FunctionExpression(func.json_contains_path(self, match, *paths))
|
|
351
|
+
|
|
352
|
+
def json_length(self, path: str | None = None) -> FunctionExpression:
|
|
353
|
+
"""JSON_LENGTH(col[, path]) - array/object length"""
|
|
354
|
+
if path is not None:
|
|
355
|
+
return FunctionExpression(func.json_length(self, path))
|
|
356
|
+
return FunctionExpression(func.json_length(self))
|
|
357
|
+
|
|
358
|
+
def json_keys(self, path: str | None = None) -> FunctionExpression:
|
|
359
|
+
"""JSON_KEYS(col[, path]) - return all keys as JSON array"""
|
|
360
|
+
if path is not None:
|
|
361
|
+
return FunctionExpression(func.json_keys(self, path))
|
|
362
|
+
return FunctionExpression(func.json_keys(self))
|
|
363
|
+
|
|
364
|
+
def json_overlaps(self, other) -> FunctionExpression:
|
|
365
|
+
"""JSON_OVERLAPS(col, val) - check if two JSON documents share any key-value pairs (MySQL 8.0+)"""
|
|
366
|
+
import json
|
|
367
|
+
|
|
368
|
+
from sqlalchemy import literal
|
|
369
|
+
|
|
370
|
+
value = other if isinstance(other, str) else json.dumps(other)
|
|
371
|
+
return FunctionExpression(func.json_overlaps(self, literal(value)))
|
|
372
|
+
|
|
373
|
+
def json_search(self, value: str, match: str = "one", path: str | None = None) -> FunctionExpression:
|
|
374
|
+
"""JSON_SEARCH(col, 'one'|'all', val[, path]) - find path of value"""
|
|
375
|
+
if path is not None:
|
|
376
|
+
return FunctionExpression(func.json_search(self, match, value, None, path))
|
|
377
|
+
return FunctionExpression(func.json_search(self, match, value))
|
|
378
|
+
|
|
379
|
+
def json_type(self) -> FunctionExpression:
|
|
380
|
+
"""JSON_TYPE(col) - return JSON value type as string"""
|
|
381
|
+
return FunctionExpression(func.json_type(self))
|
|
333
382
|
|
|
334
383
|
|
|
335
384
|
class BooleanComparator(ComparatorMixin, Boolean.Comparator): # type: ignore[reportIncompatibleMethodOverride]
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from typing import Any, Callable, NotRequired, TypedDict
|
|
3
3
|
|
|
4
|
+
from sqlalchemy.dialects.mysql import JSON
|
|
5
|
+
from sqlalchemy.dialects.postgresql import JSONB
|
|
4
6
|
from sqlalchemy.sql.sqltypes import (
|
|
5
7
|
ARRAY,
|
|
6
|
-
JSON,
|
|
7
8
|
BigInteger,
|
|
8
9
|
Boolean,
|
|
9
10
|
Date,
|
|
@@ -344,14 +345,15 @@ class TypeRegistry:
|
|
|
344
345
|
(Interval, "interval", DateTimeComparator, [], {}),
|
|
345
346
|
(LargeBinary, "binary", DefaultComparator, ["bytes"], {}),
|
|
346
347
|
(Uuid, "uuid", StringComparator, [], {}),
|
|
347
|
-
(JSON, "json", JSONComparator, ["dict"], {}),
|
|
348
348
|
]
|
|
349
349
|
|
|
350
|
-
# Special types
|
|
350
|
+
# Special types: dialect-specific or non-standard types
|
|
351
351
|
special_types = [
|
|
352
|
-
(ARRAY, "array", DefaultComparator, [], {}),
|
|
352
|
+
(ARRAY, "array", DefaultComparator, [], {}), # PostgreSQL only
|
|
353
353
|
(Enum, "enum", DefaultComparator, [], {}),
|
|
354
354
|
(Auto, "auto", DefaultComparator, [], {}),
|
|
355
|
+
(JSON, "json", JSONComparator, ["dict"], {}), # MySQL: contains -> JSON_CONTAINS
|
|
356
|
+
(JSONB, "jsonb", JSONB.Comparator, [], {}), # PostgreSQL: contains -> @>
|
|
355
357
|
]
|
|
356
358
|
|
|
357
359
|
for field_type, name, comparator, aliases, defaults in builtin_types + special_types:
|
|
@@ -368,6 +368,9 @@ class ModelProcessor(type):
|
|
|
368
368
|
# Auto-register model to ModelRegistry
|
|
369
369
|
registry.register_model(cast(type["ObjectModel"], cls))
|
|
370
370
|
|
|
371
|
+
# Resolve class name foreign key references to actual table names
|
|
372
|
+
mcs._resolve_class_foreign_keys(registry)
|
|
373
|
+
|
|
371
374
|
# Process pending M2M tables
|
|
372
375
|
registry.process_pending_m2m()
|
|
373
376
|
|
|
@@ -851,6 +854,40 @@ class ModelProcessor(type):
|
|
|
851
854
|
|
|
852
855
|
cls.__eq__ = __eq__
|
|
853
856
|
|
|
857
|
+
@classmethod
|
|
858
|
+
def _resolve_class_foreign_keys(mcs, registry):
|
|
859
|
+
"""延迟解析外键引用:优先按类名匹配,匹配不到则视为表名。
|
|
860
|
+
|
|
861
|
+
foreign_key("User.id") 传入时原样交给 SQLAlchemy(_colspec="User.id")。
|
|
862
|
+
每次新模型注册后,此方法遍历所有 FK:
|
|
863
|
+
1. 取出 info["_fk_ref"](即 "." 前的那段,如 "User")
|
|
864
|
+
2. 尝试在 registry 中按类名查找模型
|
|
865
|
+
3. 找到 → 替换 _colspec 为实际表名,标记 _resolved
|
|
866
|
+
4. 找不到 → 保留原样(视为已经是表名)
|
|
867
|
+
"""
|
|
868
|
+
for table in registry.tables.values():
|
|
869
|
+
for col in table.columns:
|
|
870
|
+
for fk in col.foreign_keys:
|
|
871
|
+
fk_info = fk.info
|
|
872
|
+
if fk_info.get("_fk_resolved"):
|
|
873
|
+
continue
|
|
874
|
+
ref_name = fk_info.get("_fk_ref")
|
|
875
|
+
if not ref_name:
|
|
876
|
+
continue
|
|
877
|
+
model = registry.get_model(ref_name)
|
|
878
|
+
if not model or not hasattr(model, "__table__"):
|
|
879
|
+
continue
|
|
880
|
+
# Found matching model — rewrite _colspec to actual table name
|
|
881
|
+
actual_table = model.__table__.name
|
|
882
|
+
schema, tname, colname = fk._column_tokens
|
|
883
|
+
if tname != actual_table:
|
|
884
|
+
if schema:
|
|
885
|
+
fk._colspec = f"{schema}.{actual_table}.{colname}"
|
|
886
|
+
else:
|
|
887
|
+
fk._colspec = f"{actual_table}.{colname}"
|
|
888
|
+
fk.__dict__.pop("_column_tokens", None)
|
|
889
|
+
fk_info["_fk_resolved"] = True
|
|
890
|
+
|
|
854
891
|
@classmethod
|
|
855
892
|
def _bind_column_attributes_to_table(mcs, cls: Any, table) -> None:
|
|
856
893
|
"""Bind ColumnAttribute instances to their corresponding table columns.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlobjects
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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
|