sqlobjects 1.0.0__tar.gz → 1.0.1__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.0.0/sqlobjects.egg-info → sqlobjects-1.0.1}/PKG-INFO +5 -5
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/README.md +4 -4
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/pyproject.toml +4 -4
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/database/manager.py +12 -12
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/core.py +310 -31
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/mixins.py +5 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1/sqlobjects.egg-info}/PKG-INFO +5 -5
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/LICENSE +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/setup.cfg +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/__init__.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/database/__init__.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/database/config.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/exceptions.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/expressions/__init__.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/expressions/aggregate.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/expressions/base.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/expressions/function.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/expressions/mixins.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/expressions/scalar.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/expressions/subquery.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/expressions/terminal.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/__init__.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/functions.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/proxies.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/relations/__init__.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/relations/descriptors.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/relations/managers.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/relations/proxies.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/relations/utils.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/shortcuts.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/types/__init__.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/types/base.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/types/comparators.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/types/registry.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/fields/utils.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/metadata.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/model.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/objects/__init__.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/objects/bulk.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/objects/core.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/queries/__init__.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/queries/builder.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/queries/executor.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/queryset.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/session.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/signals.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/utils/__init__.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/utils/inspect.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/utils/naming.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/utils/pattern.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects/validators.py +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects.egg-info/SOURCES.txt +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects.egg-info/dependency_links.txt +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects.egg-info/requires.txt +0 -0
- {sqlobjects-1.0.0 → sqlobjects-1.0.1}/sqlobjects.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlobjects
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
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>
|
|
@@ -258,9 +258,9 @@ user = await User.objects.using("analytics").create(username="analyst")
|
|
|
258
258
|
async for user in User.objects.iterator(chunk_size=1000):
|
|
259
259
|
await process_user(user)
|
|
260
260
|
|
|
261
|
-
#
|
|
262
|
-
users = await User.objects.
|
|
263
|
-
live_data = await User.objects.
|
|
261
|
+
# Field selection for performance
|
|
262
|
+
users = await User.objects.only("id", "username", "email").all() # Load only needed fields
|
|
263
|
+
live_data = await User.objects.defer("bio", "profile_image").all() # Defer heavy fields
|
|
264
264
|
|
|
265
265
|
# Field-level performance optimization
|
|
266
266
|
class User(ObjectModel):
|
|
@@ -336,7 +336,7 @@ uv run pytest
|
|
|
336
336
|
See our [TODO.md](TODO.md) for planned features:
|
|
337
337
|
|
|
338
338
|
- **v2.0**: Database health checks, window functions, advanced bulk operations
|
|
339
|
-
- **v2.1**: Advanced
|
|
339
|
+
- **v2.1**: Advanced field optimization, query performance tools
|
|
340
340
|
- **v2.2+**: CTE support, advanced SQL functions
|
|
341
341
|
|
|
342
342
|
## 📄 License
|
|
@@ -228,9 +228,9 @@ user = await User.objects.using("analytics").create(username="analyst")
|
|
|
228
228
|
async for user in User.objects.iterator(chunk_size=1000):
|
|
229
229
|
await process_user(user)
|
|
230
230
|
|
|
231
|
-
#
|
|
232
|
-
users = await User.objects.
|
|
233
|
-
live_data = await User.objects.
|
|
231
|
+
# Field selection for performance
|
|
232
|
+
users = await User.objects.only("id", "username", "email").all() # Load only needed fields
|
|
233
|
+
live_data = await User.objects.defer("bio", "profile_image").all() # Defer heavy fields
|
|
234
234
|
|
|
235
235
|
# Field-level performance optimization
|
|
236
236
|
class User(ObjectModel):
|
|
@@ -306,7 +306,7 @@ uv run pytest
|
|
|
306
306
|
See our [TODO.md](TODO.md) for planned features:
|
|
307
307
|
|
|
308
308
|
- **v2.0**: Database health checks, window functions, advanced bulk operations
|
|
309
|
-
- **v2.1**: Advanced
|
|
309
|
+
- **v2.1**: Advanced field optimization, query performance tools
|
|
310
310
|
- **v2.2+**: CTE support, advanced SQL functions
|
|
311
311
|
|
|
312
312
|
## 📄 License
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlobjects"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.1"
|
|
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 = { text = "MIT" }
|
|
@@ -49,7 +49,7 @@ Changelog = "https://github.com/XtraVisionsAI/sqlobjects/blob/main/CHANGELOG.md"
|
|
|
49
49
|
dev = [
|
|
50
50
|
"pre-commit>=4.3.0",
|
|
51
51
|
"pyright>=1.1.405",
|
|
52
|
-
"ruff>=0.
|
|
52
|
+
"ruff>=0.13.0",
|
|
53
53
|
"setuptools>=80.9.0",
|
|
54
54
|
]
|
|
55
55
|
test = [
|
|
@@ -57,8 +57,8 @@ test = [
|
|
|
57
57
|
"aiosqlite>=0.20.0",
|
|
58
58
|
"asyncpg>=0.30.0",
|
|
59
59
|
"pytest>=8.4.2",
|
|
60
|
-
"pytest-asyncio>=
|
|
61
|
-
"psutil>=7.
|
|
60
|
+
"pytest-asyncio>=1.2.0",
|
|
61
|
+
"psutil>=7.1.0", # For memory monitoring in performance tests
|
|
62
62
|
]
|
|
63
63
|
|
|
64
64
|
[[tool.uv.index]]
|
|
@@ -174,37 +174,37 @@ class Database:
|
|
|
174
174
|
|
|
175
175
|
return event.listens_for(target, event_name)
|
|
176
176
|
|
|
177
|
-
async def create_tables(self,
|
|
178
|
-
"""Create all tables defined in the
|
|
177
|
+
async def create_tables(self, base_class) -> None:
|
|
178
|
+
"""Create all tables defined in the model registry of SQLObjects base class
|
|
179
179
|
|
|
180
180
|
Creates all tables, indexes, and constraints defined in the provided
|
|
181
181
|
SQLAlchemy metadata object using the database engine.
|
|
182
182
|
|
|
183
183
|
Args:
|
|
184
|
-
|
|
184
|
+
base_class: SQLObjects base class containing model registry
|
|
185
185
|
|
|
186
186
|
Examples:
|
|
187
187
|
>>> from sqlobjects.base import ObjectModel
|
|
188
|
-
>>> await db.create_tables(ObjectModel
|
|
188
|
+
>>> await db.create_tables(ObjectModel)
|
|
189
189
|
"""
|
|
190
190
|
async with self.engine.begin() as conn:
|
|
191
|
-
await conn.run_sync(
|
|
191
|
+
await conn.run_sync(base_class.__registry__.create_all)
|
|
192
192
|
|
|
193
|
-
async def drop_tables(self,
|
|
194
|
-
"""Drop all tables defined in the
|
|
193
|
+
async def drop_tables(self, base_class) -> None:
|
|
194
|
+
"""Drop all tables defined in the model registry of SQLObjects base class
|
|
195
195
|
|
|
196
196
|
Drops all tables, indexes, and constraints defined in the provided
|
|
197
197
|
SQLAlchemy metadata object from the database.
|
|
198
198
|
|
|
199
199
|
Args:
|
|
200
|
-
|
|
200
|
+
base_class: SQLObjects base class containing model registry
|
|
201
201
|
|
|
202
202
|
Examples:
|
|
203
203
|
>>> from sqlobjects.base import ObjectModel
|
|
204
|
-
>>> await db.drop_tables(ObjectModel
|
|
204
|
+
>>> await db.drop_tables(ObjectModel)
|
|
205
205
|
"""
|
|
206
206
|
async with self.engine.begin() as conn:
|
|
207
|
-
await conn.run_sync(
|
|
207
|
+
await conn.run_sync(base_class.__registry__.drop_all)
|
|
208
208
|
|
|
209
209
|
async def disconnect(self) -> None:
|
|
210
210
|
"""Disconnect database and clean up resources
|
|
@@ -305,7 +305,7 @@ class _DatabaseManager:
|
|
|
305
305
|
>>> await DatabaseManager.create_tables(ObjectModel, "analytics")
|
|
306
306
|
"""
|
|
307
307
|
database = cls.get_database(db_name)
|
|
308
|
-
await database.create_tables(base_class
|
|
308
|
+
await database.create_tables(base_class)
|
|
309
309
|
|
|
310
310
|
@classmethod
|
|
311
311
|
async def drop_tables(cls, base_class, db_name: str | None = None) -> None:
|
|
@@ -327,7 +327,7 @@ class _DatabaseManager:
|
|
|
327
327
|
>>> await DatabaseManager.drop_tables(ObjectModel, "analytics")
|
|
328
328
|
"""
|
|
329
329
|
database = cls.get_database(db_name)
|
|
330
|
-
await database.drop_tables(base_class
|
|
330
|
+
await database.drop_tables(base_class)
|
|
331
331
|
|
|
332
332
|
@classmethod
|
|
333
333
|
async def close(cls, db_name: str | None = None, auto_default: bool = False) -> None:
|
|
@@ -20,9 +20,36 @@ NullableT = TypeVar("NullableT")
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class Column(Generic[T]):
|
|
23
|
-
"""Field descriptor for parameter collection and ColumnAttribute creation.
|
|
23
|
+
"""Field descriptor for parameter collection and ColumnAttribute creation.
|
|
24
|
+
|
|
25
|
+
This class serves as a field descriptor that collects field parameters during
|
|
26
|
+
class definition and creates the appropriate ColumnAttribute or relationship
|
|
27
|
+
descriptor when the field is accessed.
|
|
28
|
+
|
|
29
|
+
Type Parameters:
|
|
30
|
+
T: The Python type of the field value
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
name: Field name set by __set_name__
|
|
34
|
+
_params: Dictionary of field parameters
|
|
35
|
+
_column_attribute: Created ColumnAttribute instance
|
|
36
|
+
_relationship_descriptor: Created relationship descriptor (if applicable)
|
|
37
|
+
_is_relationship: Whether this is a relationship field
|
|
38
|
+
_nullable: Whether the field accepts None values
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
>>> class User(ObjectModel):
|
|
42
|
+
... name: Column[str] = StringColumn(length=50)
|
|
43
|
+
... age: Column[int] = IntegerColumn(nullable=True)
|
|
44
|
+
"""
|
|
24
45
|
|
|
25
46
|
def __init__(self, **params):
|
|
47
|
+
"""Initialize field descriptor with parameters.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
**params: Field configuration parameters including type, constraints,
|
|
51
|
+
validation rules, and performance settings
|
|
52
|
+
"""
|
|
26
53
|
self._params = params
|
|
27
54
|
self._column_attribute = None
|
|
28
55
|
self._relationship_descriptor = None
|
|
@@ -32,6 +59,16 @@ class Column(Generic[T]):
|
|
|
32
59
|
self._private_name = None
|
|
33
60
|
|
|
34
61
|
def __set_name__(self, owner, name):
|
|
62
|
+
"""Set field name and initialize appropriate descriptor.
|
|
63
|
+
|
|
64
|
+
Called automatically by Python when the field is assigned to a class.
|
|
65
|
+
Creates either a ColumnAttribute for database fields or a relationship
|
|
66
|
+
descriptor for relationship fields.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
owner: The model class that owns this field
|
|
70
|
+
name: The field name
|
|
71
|
+
"""
|
|
35
72
|
self.name = name
|
|
36
73
|
self._private_name = f"_{name}"
|
|
37
74
|
|
|
@@ -41,7 +78,12 @@ class Column(Generic[T]):
|
|
|
41
78
|
self._setup_column(owner, name)
|
|
42
79
|
|
|
43
80
|
def _setup_relationship(self, owner, name):
|
|
44
|
-
"""Set relationship field
|
|
81
|
+
"""Set up relationship field descriptor.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
owner: The model class that owns this field
|
|
85
|
+
name: The field name
|
|
86
|
+
"""
|
|
45
87
|
from .relations.descriptors import RelationshipDescriptor
|
|
46
88
|
|
|
47
89
|
relationship_property = self._params.get("relationship_property")
|
|
@@ -50,9 +92,17 @@ class Column(Generic[T]):
|
|
|
50
92
|
self._relationship_descriptor.__set_name__(owner, name)
|
|
51
93
|
|
|
52
94
|
def _setup_column(self, owner, name):
|
|
53
|
-
"""Set database field
|
|
95
|
+
"""Set up database column field.
|
|
96
|
+
|
|
97
|
+
Processes field parameters, applies defaults, and creates a ColumnAttribute
|
|
98
|
+
instance with proper type handling and parameter organization.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
owner: The model class that owns this field
|
|
102
|
+
name: The field name
|
|
103
|
+
"""
|
|
54
104
|
params = self._params.copy()
|
|
55
|
-
|
|
105
|
+
fk = params.pop("foreign_key", None)
|
|
56
106
|
type_name = params.pop("type", "auto")
|
|
57
107
|
|
|
58
108
|
# Process extended parameters
|
|
@@ -111,7 +161,7 @@ class Column(Generic[T]):
|
|
|
111
161
|
|
|
112
162
|
# Create ColumnAttribute
|
|
113
163
|
self._column_attribute = ColumnAttribute(
|
|
114
|
-
name, enhanced_type, foreign_key=
|
|
164
|
+
name, enhanced_type, foreign_key=fk, model_class=owner, **column_params
|
|
115
165
|
)
|
|
116
166
|
|
|
117
167
|
@overload
|
|
@@ -121,6 +171,18 @@ class Column(Generic[T]):
|
|
|
121
171
|
def __get__(self, instance: Any, owner: type) -> T: ...
|
|
122
172
|
|
|
123
173
|
def __get__(self, instance, owner):
|
|
174
|
+
"""Get field value or ColumnAttribute.
|
|
175
|
+
|
|
176
|
+
Returns the ColumnAttribute when accessed on the class (for query building)
|
|
177
|
+
or the actual field value when accessed on an instance.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
instance: Model instance or None if accessed on class
|
|
181
|
+
owner: The model class
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
ColumnAttribute when accessed on class, field value when accessed on instance
|
|
185
|
+
"""
|
|
124
186
|
if self._is_relationship and self._relationship_descriptor:
|
|
125
187
|
return self._relationship_descriptor.__get__(instance, owner)
|
|
126
188
|
|
|
@@ -136,6 +198,15 @@ class Column(Generic[T]):
|
|
|
136
198
|
return cast(T, value)
|
|
137
199
|
|
|
138
200
|
def __set__(self, instance, value):
|
|
201
|
+
"""Set field value on instance.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
instance: Model instance to set value on
|
|
205
|
+
value: Value to set
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
AttributeError: If trying to set value on class rather than instance
|
|
209
|
+
"""
|
|
139
210
|
if self._is_relationship:
|
|
140
211
|
# Relationship fields may not support direct setting
|
|
141
212
|
pass
|
|
@@ -147,18 +218,6 @@ class Column(Generic[T]):
|
|
|
147
218
|
|
|
148
219
|
|
|
149
220
|
class ColumnAttribute(ColumnAttributeFunctionMixin, Generic[T]):
|
|
150
|
-
def __getattr__(self, name):
|
|
151
|
-
"""Handle attribute access with proper priority
|
|
152
|
-
|
|
153
|
-
First check for SQLAlchemy column attributes, then delegate to function mixin.
|
|
154
|
-
"""
|
|
155
|
-
# First try the underlying SQLAlchemy column for its own attributes
|
|
156
|
-
if hasattr(self.__column__, name):
|
|
157
|
-
return getattr(self.__column__, name)
|
|
158
|
-
|
|
159
|
-
# Then delegate to the function mixin for database functions
|
|
160
|
-
return super().__getattr__(name)
|
|
161
|
-
|
|
162
221
|
"""Enhanced column attribute with SQLAlchemy CoreColumn compatibility.
|
|
163
222
|
|
|
164
223
|
Extends SQLAlchemy's Column with additional functionality for validation,
|
|
@@ -180,7 +239,38 @@ class ColumnAttribute(ColumnAttributeFunctionMixin, Generic[T]):
|
|
|
180
239
|
|
|
181
240
|
inherit_cache = True # make use of the cache key generated by the superclass from SQLAlchemy
|
|
182
241
|
|
|
242
|
+
def __getattr__(self, name):
|
|
243
|
+
"""Handle attribute access with proper priority.
|
|
244
|
+
|
|
245
|
+
First checks for SQLAlchemy column attributes, then delegates to the
|
|
246
|
+
function mixin for database functions like like(), ilike(), etc.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
name: Attribute name to access
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Attribute value from SQLAlchemy column or function mixin
|
|
253
|
+
|
|
254
|
+
Raises:
|
|
255
|
+
AttributeError: If attribute is not found
|
|
256
|
+
"""
|
|
257
|
+
# First try the underlying SQLAlchemy column for its own attributes
|
|
258
|
+
if hasattr(self.__column__, name):
|
|
259
|
+
return getattr(self.__column__, name)
|
|
260
|
+
|
|
261
|
+
# Then delegate to the function mixin for database functions
|
|
262
|
+
return super().__getattr__(name)
|
|
263
|
+
|
|
183
264
|
def __init__(self, name, type_, foreign_key=None, *, model_class, **kwargs): # noqa
|
|
265
|
+
"""Initialize ColumnAttribute with enhanced functionality.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
name: Column name
|
|
269
|
+
type_: SQLAlchemy type instance
|
|
270
|
+
foreign_key: Foreign key constraint if applicable
|
|
271
|
+
model_class: The model class this column belongs to
|
|
272
|
+
**kwargs: Additional SQLAlchemy column parameters
|
|
273
|
+
"""
|
|
184
274
|
# Extract enhanced parameters from info dict
|
|
185
275
|
info = kwargs.get("info", {})
|
|
186
276
|
enhanced_params = info.get("_enhanced", {})
|
|
@@ -210,10 +300,26 @@ class ColumnAttribute(ColumnAttributeFunctionMixin, Generic[T]):
|
|
|
210
300
|
# Validation related
|
|
211
301
|
@property
|
|
212
302
|
def validators(self) -> list[Any]:
|
|
303
|
+
"""Get list of field validators.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
List of validation functions for this field
|
|
307
|
+
"""
|
|
213
308
|
return self._enhanced_params.get("validators", [])
|
|
214
309
|
|
|
215
310
|
def validate_value(self, value: Any, field_name: str) -> Any:
|
|
216
|
-
"""Validate field value using registered validators
|
|
311
|
+
"""Validate field value using registered validators.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
value: Value to validate
|
|
315
|
+
field_name: Name of the field being validated
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Validated value (may be transformed by validators)
|
|
319
|
+
|
|
320
|
+
Raises:
|
|
321
|
+
ValidationError: If validation fails
|
|
322
|
+
"""
|
|
217
323
|
validators = self.validators
|
|
218
324
|
if validators:
|
|
219
325
|
from ..validators import validate_field_value
|
|
@@ -223,16 +329,37 @@ class ColumnAttribute(ColumnAttributeFunctionMixin, Generic[T]):
|
|
|
223
329
|
|
|
224
330
|
# Default value related
|
|
225
331
|
def get_default_factory(self) -> Callable[[], Any] | None:
|
|
332
|
+
"""Get default value factory function.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
Callable that generates default values, or None if not set
|
|
336
|
+
"""
|
|
226
337
|
return self._enhanced_params.get("default_factory")
|
|
227
338
|
|
|
228
339
|
def get_insert_default(self) -> Any:
|
|
340
|
+
"""Get insert-only default value.
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
Default value used only for INSERT operations
|
|
344
|
+
"""
|
|
229
345
|
return self._enhanced_params.get("insert_default")
|
|
230
346
|
|
|
231
347
|
def has_insert_default(self) -> bool:
|
|
348
|
+
"""Check if field has insert-only default value.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
True if insert_default is configured, False otherwise
|
|
352
|
+
"""
|
|
232
353
|
return "insert_default" in self._enhanced_params
|
|
233
354
|
|
|
234
355
|
def get_effective_default(self) -> Any:
|
|
235
|
-
"""Get effective default value by priority order
|
|
356
|
+
"""Get effective default value by priority order.
|
|
357
|
+
|
|
358
|
+
Checks default sources in priority order: default, default_factory, insert_default.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
The effective default value or callable, or None if no default is set
|
|
362
|
+
"""
|
|
236
363
|
if self.default is not None:
|
|
237
364
|
return self.default
|
|
238
365
|
|
|
@@ -249,27 +376,59 @@ class ColumnAttribute(ColumnAttributeFunctionMixin, Generic[T]):
|
|
|
249
376
|
# Performance optimization related
|
|
250
377
|
@property
|
|
251
378
|
def is_deferred(self) -> bool:
|
|
379
|
+
"""Check if field is configured for deferred loading.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
True if field should be loaded lazily, False otherwise
|
|
383
|
+
"""
|
|
252
384
|
return self._performance_params.get("deferred", False)
|
|
253
385
|
|
|
254
386
|
@property
|
|
255
387
|
def deferred_group(self) -> str | None:
|
|
388
|
+
"""Get deferred loading group name.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
Name of the deferred loading group, or None if not grouped
|
|
392
|
+
"""
|
|
256
393
|
return self._performance_params.get("deferred_group")
|
|
257
394
|
|
|
258
395
|
@property
|
|
259
396
|
def has_active_history(self) -> bool:
|
|
397
|
+
"""Check if field tracks value changes.
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
True if active history tracking is enabled, False otherwise
|
|
401
|
+
"""
|
|
260
402
|
return self._performance_params.get("active_history", False)
|
|
261
403
|
|
|
262
404
|
@property
|
|
263
405
|
def deferred_raiseload(self) -> bool | None:
|
|
406
|
+
"""Check if accessing deferred field should raise an error.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
True to raise error, False to allow access, None for default behavior
|
|
410
|
+
"""
|
|
264
411
|
return self._performance_params.get("deferred_raiseload")
|
|
265
412
|
|
|
266
413
|
# Code generation related
|
|
267
414
|
@property
|
|
268
415
|
def include_in_init(self) -> bool | None:
|
|
416
|
+
"""Check if field should be included in __init__ method.
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
True to include, False to exclude, None for default behavior
|
|
420
|
+
"""
|
|
269
421
|
return self._codegen_params.get("init")
|
|
270
422
|
|
|
271
423
|
def create_table_column(self, name: str) -> CoreColumn:
|
|
272
|
-
"""Create independent Column
|
|
424
|
+
"""Create independent SQLAlchemy Column for table creation.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
name: Column name for the table
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
New SQLAlchemy Column instance independent of this ColumnAttribute
|
|
431
|
+
"""
|
|
273
432
|
# Create new ForeignKey instance instead of reusing existing one
|
|
274
433
|
foreign_keys = []
|
|
275
434
|
if self.__column__.foreign_keys:
|
|
@@ -445,38 +604,81 @@ class ColumnAttribute(ColumnAttributeFunctionMixin, Generic[T]):
|
|
|
445
604
|
return FunctionExpression(other % self.__column__)
|
|
446
605
|
|
|
447
606
|
def __hash__(self):
|
|
448
|
-
"""Delegate hash to underlying column for SQLAlchemy compatibility
|
|
607
|
+
"""Delegate hash to underlying column for SQLAlchemy compatibility.
|
|
608
|
+
|
|
609
|
+
Returns:
|
|
610
|
+
Hash value from the underlying SQLAlchemy column
|
|
611
|
+
"""
|
|
449
612
|
return self.__column__.__hash__()
|
|
450
613
|
|
|
451
614
|
@property
|
|
452
615
|
def include_in_repr(self) -> bool | None:
|
|
616
|
+
"""Check if field should be included in __repr__ method.
|
|
617
|
+
|
|
618
|
+
Returns:
|
|
619
|
+
True to include, False to exclude, None for default behavior
|
|
620
|
+
"""
|
|
453
621
|
return self._codegen_params.get("repr")
|
|
454
622
|
|
|
455
623
|
@property
|
|
456
624
|
def include_in_compare(self) -> bool | None:
|
|
625
|
+
"""Check if field should be included in __eq__ method.
|
|
626
|
+
|
|
627
|
+
Returns:
|
|
628
|
+
True to include, False to exclude, None for default behavior
|
|
629
|
+
"""
|
|
457
630
|
return self._codegen_params.get("compare")
|
|
458
631
|
|
|
459
632
|
@property
|
|
460
633
|
def include_in_hash(self) -> bool | None:
|
|
634
|
+
"""Check if field should be included in __hash__ method.
|
|
635
|
+
|
|
636
|
+
Returns:
|
|
637
|
+
True to include, False to exclude, None for default behavior
|
|
638
|
+
"""
|
|
461
639
|
return self._codegen_params.get("hash")
|
|
462
640
|
|
|
463
641
|
@property
|
|
464
642
|
def is_kw_only(self) -> bool | None:
|
|
643
|
+
"""Check if field should be keyword-only in __init__ method.
|
|
644
|
+
|
|
645
|
+
Returns:
|
|
646
|
+
True for keyword-only, False for positional, None for default behavior
|
|
647
|
+
"""
|
|
465
648
|
return self._codegen_params.get("kw_only")
|
|
466
649
|
|
|
467
650
|
# === General parameter access methods ===
|
|
468
651
|
|
|
469
652
|
def get_param(self, category: str, name: str, default: Any = None) -> Any:
|
|
470
|
-
"""Get parameter from specified category
|
|
653
|
+
"""Get parameter from specified category.
|
|
654
|
+
|
|
655
|
+
Args:
|
|
656
|
+
category: Parameter category ('enhanced', 'performance', 'codegen')
|
|
657
|
+
name: Parameter name
|
|
658
|
+
default: Default value if parameter not found
|
|
659
|
+
|
|
660
|
+
Returns:
|
|
661
|
+
Parameter value or default
|
|
662
|
+
"""
|
|
471
663
|
param_dict = getattr(self, f"_{category}_params", {})
|
|
472
664
|
return param_dict.get(name, default)
|
|
473
665
|
|
|
474
666
|
def get_codegen_params(self) -> dict[str, Any]:
|
|
475
|
-
"""Get code generation parameters
|
|
667
|
+
"""Get code generation parameters.
|
|
668
|
+
|
|
669
|
+
Returns:
|
|
670
|
+
Dictionary of parameters controlling __init__, __repr__, etc. generation
|
|
671
|
+
"""
|
|
476
672
|
return self._codegen_params
|
|
477
673
|
|
|
478
674
|
def get_python_type(self):
|
|
479
|
-
"""Get Python type from class annotations
|
|
675
|
+
"""Get Python type from class annotations.
|
|
676
|
+
|
|
677
|
+
Extracts the type parameter from Column[T] annotations for type safety.
|
|
678
|
+
|
|
679
|
+
Returns:
|
|
680
|
+
Python type class or None if not found
|
|
681
|
+
"""
|
|
480
682
|
if not self.model_class or not self._field_name:
|
|
481
683
|
return None
|
|
482
684
|
|
|
@@ -505,7 +707,12 @@ class ColumnAttribute(ColumnAttributeFunctionMixin, Generic[T]):
|
|
|
505
707
|
return None
|
|
506
708
|
|
|
507
709
|
def get_field_metadata(self) -> dict[str, Any]:
|
|
508
|
-
"""Get complete field metadata information
|
|
710
|
+
"""Get complete field metadata information.
|
|
711
|
+
|
|
712
|
+
Returns:
|
|
713
|
+
Dictionary containing all field metadata including type info,
|
|
714
|
+
constraints, and extended parameters
|
|
715
|
+
"""
|
|
509
716
|
metadata = {
|
|
510
717
|
"name": self.name,
|
|
511
718
|
"type": str(self.type),
|
|
@@ -569,11 +776,52 @@ def column(
|
|
|
569
776
|
hash: bool | None = None, # noqa
|
|
570
777
|
kw_only: bool | None = None,
|
|
571
778
|
# Foreign key constraint
|
|
572
|
-
foreign_key: ForeignKey | None = None,
|
|
779
|
+
foreign_key: ForeignKey | None = None, # noqa # shadows name
|
|
573
780
|
# Type parameters (passed through **kwargs)
|
|
574
781
|
**kwargs: Any,
|
|
575
782
|
) -> "Column[Any]":
|
|
576
|
-
"""Create
|
|
783
|
+
"""Create a database column with specified type and parameters.
|
|
784
|
+
|
|
785
|
+
Args:
|
|
786
|
+
type: SQLAlchemy type name (e.g., 'string', 'integer', 'datetime')
|
|
787
|
+
name: Column name (usually auto-detected from field name)
|
|
788
|
+
primary_key: Whether this is a primary key column
|
|
789
|
+
nullable: Whether the column accepts NULL values
|
|
790
|
+
default: Static default value
|
|
791
|
+
index: Whether to create an index on this column
|
|
792
|
+
unique: Whether values must be unique
|
|
793
|
+
autoincrement: Auto-increment behavior for integer primary keys
|
|
794
|
+
doc: Documentation string
|
|
795
|
+
key: Alternative key name for the column
|
|
796
|
+
onupdate: Value to set on UPDATE operations
|
|
797
|
+
comment: Database comment for the column
|
|
798
|
+
system: Whether this is a system column
|
|
799
|
+
server_default: Server-side default value
|
|
800
|
+
server_onupdate: Server-side update value
|
|
801
|
+
quote: Whether to quote the column name
|
|
802
|
+
info: Additional metadata dictionary
|
|
803
|
+
default_factory: Function to generate default values
|
|
804
|
+
validators: List of validation functions
|
|
805
|
+
deferred: Whether to defer loading this column
|
|
806
|
+
deferred_group: Group name for deferred loading
|
|
807
|
+
insert_default: Default value only for INSERT operations
|
|
808
|
+
init: Whether to include in __init__ method
|
|
809
|
+
repr: Whether to include in __repr__ method
|
|
810
|
+
compare: Whether to include in __eq__ method
|
|
811
|
+
active_history: Whether to track value changes
|
|
812
|
+
deferred_raiseload: Whether to raise error when accessing deferred field
|
|
813
|
+
hash: Whether to include in __hash__ method
|
|
814
|
+
kw_only: Whether parameter should be keyword-only in __init__
|
|
815
|
+
foreign_key: Foreign key constraint
|
|
816
|
+
**kwargs: Additional type-specific parameters
|
|
817
|
+
|
|
818
|
+
Returns:
|
|
819
|
+
Column descriptor configured with the specified parameters
|
|
820
|
+
|
|
821
|
+
Example:
|
|
822
|
+
>>> name: Column[str] = column(type="string", length=100, nullable=False)
|
|
823
|
+
>>> age: Column[int] = column(type="integer", default=0, validators=[validate_range(0, 150)])
|
|
824
|
+
"""
|
|
577
825
|
# Collect all parameters
|
|
578
826
|
all_params = {
|
|
579
827
|
"type": type,
|
|
@@ -617,7 +865,14 @@ def column(
|
|
|
617
865
|
|
|
618
866
|
|
|
619
867
|
def _extract_column_params(kwargs: dict) -> dict:
|
|
620
|
-
"""Extract SQLAlchemy Column
|
|
868
|
+
"""Extract parameters relevant to SQLAlchemy Column creation.
|
|
869
|
+
|
|
870
|
+
Args:
|
|
871
|
+
kwargs: All field parameters
|
|
872
|
+
|
|
873
|
+
Returns:
|
|
874
|
+
Dictionary containing only column-related parameters
|
|
875
|
+
"""
|
|
621
876
|
column_param_names = {
|
|
622
877
|
"primary_key",
|
|
623
878
|
"nullable",
|
|
@@ -639,7 +894,14 @@ def _extract_column_params(kwargs: dict) -> dict:
|
|
|
639
894
|
|
|
640
895
|
|
|
641
896
|
def _extract_type_params(kwargs: dict) -> dict:
|
|
642
|
-
"""Extract type
|
|
897
|
+
"""Extract parameters relevant to SQLAlchemy type creation.
|
|
898
|
+
|
|
899
|
+
Args:
|
|
900
|
+
kwargs: All field parameters
|
|
901
|
+
|
|
902
|
+
Returns:
|
|
903
|
+
Dictionary containing only type-related parameters
|
|
904
|
+
"""
|
|
643
905
|
column_param_names = {
|
|
644
906
|
"primary_key",
|
|
645
907
|
"nullable",
|
|
@@ -661,7 +923,15 @@ def _extract_type_params(kwargs: dict) -> dict:
|
|
|
661
923
|
|
|
662
924
|
|
|
663
925
|
def _apply_codegen_defaults(codegen_params: dict, column_kwargs: dict) -> dict:
|
|
664
|
-
"""Apply
|
|
926
|
+
"""Apply intelligent defaults for code generation parameters.
|
|
927
|
+
|
|
928
|
+
Args:
|
|
929
|
+
codegen_params: User-specified code generation parameters
|
|
930
|
+
column_kwargs: Column configuration for determining defaults
|
|
931
|
+
|
|
932
|
+
Returns:
|
|
933
|
+
Complete code generation parameters with defaults applied
|
|
934
|
+
"""
|
|
665
935
|
defaults = {"init": True, "repr": True, "compare": False, "hash": None, "kw_only": False}
|
|
666
936
|
|
|
667
937
|
# Primary key fields: don't participate in initialization, but participate in comparison and display
|
|
@@ -689,7 +959,16 @@ def _resolve_default_value(
|
|
|
689
959
|
default_factory: Callable[[], Any] | None,
|
|
690
960
|
insert_default: Any,
|
|
691
961
|
) -> Any:
|
|
692
|
-
"""Resolve default value
|
|
962
|
+
"""Resolve final default value from multiple sources.
|
|
963
|
+
|
|
964
|
+
Args:
|
|
965
|
+
default: Static default value
|
|
966
|
+
default_factory: Factory function for generating defaults
|
|
967
|
+
insert_default: Insert-only default value
|
|
968
|
+
|
|
969
|
+
Returns:
|
|
970
|
+
The resolved default value to use
|
|
971
|
+
"""
|
|
693
972
|
if default is not None:
|
|
694
973
|
return default
|
|
695
974
|
|
|
@@ -9,6 +9,10 @@ from .fields.utils import get_column_from_field, is_field_definition
|
|
|
9
9
|
from .session import AsyncSession, get_session
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .metadata import ModelRegistry
|
|
14
|
+
|
|
15
|
+
|
|
12
16
|
class _StateManager:
|
|
13
17
|
"""Unified state management for model instances."""
|
|
14
18
|
|
|
@@ -43,6 +47,7 @@ class BaseMixin:
|
|
|
43
47
|
|
|
44
48
|
if TYPE_CHECKING:
|
|
45
49
|
__table__: ClassVar[Table]
|
|
50
|
+
__registry__: ClassVar["ModelRegistry"]
|
|
46
51
|
|
|
47
52
|
def __init__(self):
|
|
48
53
|
"""Initialize state manager if not already present."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlobjects
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
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>
|
|
@@ -258,9 +258,9 @@ user = await User.objects.using("analytics").create(username="analyst")
|
|
|
258
258
|
async for user in User.objects.iterator(chunk_size=1000):
|
|
259
259
|
await process_user(user)
|
|
260
260
|
|
|
261
|
-
#
|
|
262
|
-
users = await User.objects.
|
|
263
|
-
live_data = await User.objects.
|
|
261
|
+
# Field selection for performance
|
|
262
|
+
users = await User.objects.only("id", "username", "email").all() # Load only needed fields
|
|
263
|
+
live_data = await User.objects.defer("bio", "profile_image").all() # Defer heavy fields
|
|
264
264
|
|
|
265
265
|
# Field-level performance optimization
|
|
266
266
|
class User(ObjectModel):
|
|
@@ -336,7 +336,7 @@ uv run pytest
|
|
|
336
336
|
See our [TODO.md](TODO.md) for planned features:
|
|
337
337
|
|
|
338
338
|
- **v2.0**: Database health checks, window functions, advanced bulk operations
|
|
339
|
-
- **v2.1**: Advanced
|
|
339
|
+
- **v2.1**: Advanced field optimization, query performance tools
|
|
340
340
|
- **v2.2+**: CTE support, advanced SQL functions
|
|
341
341
|
|
|
342
342
|
## 📄 License
|
|
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
|