sqlobjects 1.4.0__tar.gz → 1.6.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.
Files changed (80) hide show
  1. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/CHANGELOG.md +12 -0
  2. {sqlobjects-1.4.0/sqlobjects.egg-info → sqlobjects-1.6.0}/PKG-INFO +1 -1
  3. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/pyproject.toml +1 -1
  4. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/cascade.py +37 -2
  5. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/metadata.py +55 -2
  6. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/model.py +9 -2
  7. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/queryset.py +3 -2
  8. {sqlobjects-1.4.0 → sqlobjects-1.6.0/sqlobjects.egg-info}/PKG-INFO +1 -1
  9. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/LICENSE +0 -0
  10. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/README.md +0 -0
  11. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/docs/rules/01-database-session-guide.md +0 -0
  12. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/docs/rules/02-model-definition-guide.md +0 -0
  13. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/docs/rules/03-query-operations-guide.md +0 -0
  14. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/docs/rules/04-crud-operations-guide.md +0 -0
  15. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/docs/rules/05-relationships-guide.md +0 -0
  16. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/docs/rules/06-validation-signals-guide.md +0 -0
  17. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/docs/rules/07-performance-guide.md +0 -0
  18. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/docs/rules/README.md +0 -0
  19. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/setup.cfg +0 -0
  20. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/__init__.py +0 -0
  21. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/_install_rules.py +0 -0
  22. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/contrib/__init__.py +0 -0
  23. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/contrib/asgi.py +0 -0
  24. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/contrib/fastapi.py +0 -0
  25. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/database/__init__.py +0 -0
  26. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/database/config.py +0 -0
  27. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/database/manager.py +0 -0
  28. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/exceptions.py +0 -0
  29. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/expressions/__init__.py +0 -0
  30. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/expressions/aggregate.py +0 -0
  31. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/expressions/base.py +0 -0
  32. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/expressions/cte.py +0 -0
  33. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/expressions/explain.py +0 -0
  34. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/expressions/function.py +0 -0
  35. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/expressions/mixins.py +0 -0
  36. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/expressions/scalar.py +0 -0
  37. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/expressions/subquery.py +0 -0
  38. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/expressions/terminal.py +0 -0
  39. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/expressions/window.py +0 -0
  40. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/__init__.py +0 -0
  41. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/core.py +0 -0
  42. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/functions.py +0 -0
  43. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/proxies.py +0 -0
  44. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/relations/__init__.py +0 -0
  45. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/relations/descriptors.py +0 -0
  46. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/relations/managers.py +0 -0
  47. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/relations/prefetch.py +0 -0
  48. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/relations/strategies.py +0 -0
  49. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/relations/utils.py +0 -0
  50. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/shortcuts.py +0 -0
  51. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/types/__init__.py +0 -0
  52. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/types/base.py +0 -0
  53. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/types/comparators.py +0 -0
  54. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/types/registry.py +0 -0
  55. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/fields/utils.py +0 -0
  56. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/internal/__init__.py +0 -0
  57. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/internal/operations.py +0 -0
  58. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/internal/results.py +0 -0
  59. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/mixins.py +0 -0
  60. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/objects/__init__.py +0 -0
  61. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/objects/bulk.py +0 -0
  62. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/objects/core.py +0 -0
  63. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/objects/upsert.py +0 -0
  64. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/queries/__init__.py +0 -0
  65. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/queries/builder.py +0 -0
  66. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/queries/dialect.py +0 -0
  67. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/queries/executor.py +0 -0
  68. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/session.py +0 -0
  69. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/signals.py +0 -0
  70. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/utils/__init__.py +0 -0
  71. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/utils/inspect.py +0 -0
  72. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/utils/naming.py +0 -0
  73. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/utils/pattern.py +0 -0
  74. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects/validators.py +0 -0
  75. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects.egg-info/SOURCES.txt +0 -0
  76. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects.egg-info/dependency_links.txt +0 -0
  77. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects.egg-info/entry_points.txt +0 -0
  78. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects.egg-info/requires.txt +0 -0
  79. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/sqlobjects.egg-info/top_level.txt +0 -0
  80. {sqlobjects-1.4.0 → sqlobjects-1.6.0}/tests/test_config.py +0 -0
@@ -1,3 +1,15 @@
1
+ ## 1.6.0 (2026-03-18)
2
+
3
+ ### Feat
4
+
5
+ - **metadata**: add foreignkey() constraint builder
6
+
7
+ ## 1.5.0 (2026-03-16)
8
+
9
+ ### Feat
10
+
11
+ - **cascade**: unify cascade strategy with auto-detection for Model.delete()
12
+
1
13
  ## 1.4.0 (2026-03-11)
2
14
 
3
15
  ### Feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlobjects
3
- Version: 1.4.0
3
+ Version: 1.6.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.4.0"
3
+ version = "1.6.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"
@@ -28,6 +28,7 @@ __all__ = [
28
28
  "normalize_onupdate",
29
29
  "normalize_cascade",
30
30
  "has_cascade_delete_relations",
31
+ "has_delete_signals",
31
32
  ]
32
33
 
33
34
 
@@ -130,6 +131,30 @@ def has_cascade_delete_relations(model_class) -> bool:
130
131
  return False
131
132
 
132
133
 
134
+ def has_delete_signals(model_class) -> bool:
135
+ """Check if model class has delete signal handlers defined.
136
+
137
+ Args:
138
+ model_class: Model class to check
139
+
140
+ Returns:
141
+ True if model has before_delete/after_delete handlers, False otherwise
142
+ """
143
+ from .signals import SignalMixin
144
+
145
+ if not issubclass(model_class, SignalMixin):
146
+ return False
147
+
148
+ for method_name in ("before_delete", "after_delete"):
149
+ method = getattr(model_class, method_name, None)
150
+ if method is None:
151
+ continue
152
+ # Must be defined on the model itself, not inherited from SignalMixin
153
+ if method_name in model_class.__dict__:
154
+ return True
155
+ return False
156
+
157
+
133
158
  def normalize_cascade(cascade: CascadeType) -> str:
134
159
  """Normalize cascade parameter to SQLAlchemy string format."""
135
160
  if cascade is None:
@@ -344,7 +369,7 @@ class CascadeExecutor:
344
369
  return instance
345
370
 
346
371
  async def execute_delete_operation(
347
- self, target, cascade_strategy: str = "full", session: "AsyncSession | None" = None
372
+ self, target, cascade_strategy: str = "auto", session: "AsyncSession | None" = None
348
373
  ) -> int:
349
374
  """Execute delete operation with cascade handling."""
350
375
  if session is None:
@@ -355,7 +380,8 @@ class CascadeExecutor:
355
380
  return await self._execute_queryset_delete(target, cascade_strategy, session)
356
381
 
357
382
  # Handle single instance deletion
358
- await self._delete_related_objects(target, session)
383
+ if has_cascade_delete_relations(target.__class__):
384
+ await self._delete_related_objects(target, session)
359
385
  await target._delete_internal(session=session) # noqa
360
386
  return 1
361
387
 
@@ -478,6 +504,15 @@ class CascadeExecutor:
478
504
 
479
505
  async def _execute_queryset_delete(self, queryset, cascade_strategy: str, session: "AsyncSession") -> int:
480
506
  """Execute QuerySet delete with different cascade strategies."""
507
+ if cascade_strategy == "auto":
508
+ model_class = queryset._model_class
509
+ if has_delete_signals(model_class):
510
+ cascade_strategy = "full"
511
+ elif has_cascade_delete_relations(model_class):
512
+ cascade_strategy = "fast"
513
+ else:
514
+ cascade_strategy = "none"
515
+
481
516
  if cascade_strategy == "full":
482
517
  return await self._delete_with_full_cascade(queryset, session)
483
518
  elif cascade_strategy == "fast":
@@ -22,6 +22,7 @@ __all__ = [
22
22
  "index",
23
23
  "constraint",
24
24
  "unique",
25
+ "foreignkey",
25
26
  ]
26
27
 
27
28
  _FIELD_NAME_PATTERN = re.compile(r"\b([a-zA-Z_][a-zA-Z0-9_]*)\b")
@@ -37,7 +38,7 @@ class _RawModelConfig:
37
38
  verbose_name_plural: str | None = None
38
39
  ordering: list[str] = field(default_factory=list)
39
40
  indexes: list[Index] = field(default_factory=list)
40
- constraints: list[CheckConstraint | UniqueConstraint] = field(default_factory=list)
41
+ constraints: list[CheckConstraint | UniqueConstraint | ForeignKeyConstraint] = field(default_factory=list)
41
42
  description: str | None = None
42
43
  db_options: dict[str, dict[str, Any]] = field(default_factory=dict)
43
44
  custom: dict[str, Any] = field(default_factory=dict)
@@ -52,7 +53,7 @@ class ModelConfig:
52
53
  verbose_name_plural: str
53
54
  ordering: list[str] = field(default_factory=list)
54
55
  indexes: list[Index] = field(default_factory=list)
55
- constraints: list[CheckConstraint | UniqueConstraint] = field(default_factory=list)
56
+ constraints: list[CheckConstraint | UniqueConstraint | ForeignKeyConstraint] = field(default_factory=list)
56
57
  description: str | None = None
57
58
  db_options: dict[str, dict[str, Any]] = field(default_factory=dict)
58
59
  custom: dict[str, Any] = field(default_factory=dict)
@@ -1109,3 +1110,55 @@ def unique(
1109
1110
  >>> unique("first_name", "last_name", name="uq_full_name")
1110
1111
  """
1111
1112
  return UniqueConstraint(*fields, name=name, **kwargs)
1113
+
1114
+
1115
+ def foreignkey(
1116
+ fields: str | list[str],
1117
+ references: str | list[str],
1118
+ *,
1119
+ name: str | None = None,
1120
+ ondelete: str | None = None,
1121
+ onupdate: str | None = None,
1122
+ deferrable: bool = False,
1123
+ initially: str = "IMMEDIATE",
1124
+ **kwargs: Any,
1125
+ ) -> ForeignKeyConstraint:
1126
+ """Create a ForeignKeyConstraint with convenient field name support.
1127
+
1128
+ Use this in Config.constraints for explicit constraint definition, custom
1129
+ names, or composite foreign keys. For simple single-column foreign keys,
1130
+ prefer the ``foreign_key()`` field descriptor instead.
1131
+
1132
+ Args:
1133
+ fields: Local field name(s) as string or list of strings.
1134
+ references: Referenced column(s) as "Table.column" or list thereof.
1135
+ Supports class names (e.g. "User.id") or table names (e.g. "users.id").
1136
+ name: Constraint name (optional, auto-generated if not provided).
1137
+ ondelete: Referential action on delete (CASCADE, SET NULL, RESTRICT, etc.).
1138
+ onupdate: Referential action on update.
1139
+ deferrable: Whether the constraint can be deferred.
1140
+ initially: Initial deferral state ("IMMEDIATE" or "DEFERRED").
1141
+ **kwargs: Additional SQLAlchemy ForeignKeyConstraint arguments.
1142
+
1143
+ Returns:
1144
+ SQLAlchemy ForeignKeyConstraint instance.
1145
+
1146
+ Examples:
1147
+ >>> foreignkey("author_id", "User.id")
1148
+ >>> foreignkey("author_id", "User.id", name="fk_posts_author", ondelete="CASCADE")
1149
+ >>> foreignkey(["a_id", "b_id"], ["A.id", "B.id"])
1150
+ """
1151
+ if isinstance(fields, str):
1152
+ fields = [fields]
1153
+ if isinstance(references, str):
1154
+ references = [references]
1155
+ return ForeignKeyConstraint(
1156
+ fields,
1157
+ references,
1158
+ name=name,
1159
+ ondelete=ondelete,
1160
+ onupdate=onupdate,
1161
+ deferrable=deferrable or None,
1162
+ initially=initially if deferrable else None,
1163
+ **kwargs,
1164
+ )
@@ -278,11 +278,14 @@ class ModelMixin(DataConversionMixin, SignalMixin):
278
278
  return await self._save_internal(validate=validate, session=session)
279
279
 
280
280
  @emit_signals(Operation.DELETE)
281
- async def delete(self, cascade: bool = True):
281
+ async def delete(self, cascade: bool | None = None):
282
282
  """Delete this model instance from the database with cascade support.
283
283
 
284
284
  Args:
285
- cascade: Whether to handle cascade deletion (default: True)
285
+ cascade: Whether to handle cascade deletion.
286
+ None (default): auto-detect based on model relationships
287
+ True: force cascade handling
288
+ False: skip cascade, direct delete
286
289
 
287
290
  Raises:
288
291
  PrimaryKeyError: If instance has no primary key values or delete fails
@@ -292,6 +295,10 @@ class ModelMixin(DataConversionMixin, SignalMixin):
292
295
 
293
296
  session = self.get_session()
294
297
 
298
+ # Auto-detect cascade need when not explicitly specified
299
+ if cascade is None:
300
+ cascade = self._has_on_delete_relations()
301
+
295
302
  # Use cascade executor for cascade operations
296
303
  if cascade:
297
304
  executor = CascadeExecutor()
@@ -1221,12 +1221,13 @@ class QuerySet(Generic[T]):
1221
1221
  return await executor.execute_update_operation(self, values)
1222
1222
 
1223
1223
  @emit_signals(Operation.DELETE, is_bulk=True)
1224
- async def delete(self, cascade: str = "full") -> int:
1224
+ async def delete(self, cascade: str = "auto") -> int:
1225
1225
  """Perform bulk delete on objects matching query conditions.
1226
1226
 
1227
1227
  Args:
1228
1228
  cascade: Cascade deletion strategy
1229
- - "full" (default): Complete cascade deletion with full ORM functionality
1229
+ - "auto" (default): Automatically choose based on model relationships
1230
+ - "full": Complete cascade deletion with full ORM functionality
1230
1231
  - "fast": Fast cascade deletion with minimal ORM processing
1231
1232
  - "none": Direct SQL deletion without ORM cascade processing
1232
1233
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlobjects
3
- Version: 1.4.0
3
+ Version: 1.6.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