SQLAlchemy-Continuum 1.5.1__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 (131) hide show
  1. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/CHANGES.rst +18 -7
  2. {sqlalchemy_continuum-1.5.1/SQLAlchemy_Continuum.egg-info → sqlalchemy_continuum-1.6.0}/PKG-INFO +2 -1
  3. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0/SQLAlchemy_Continuum.egg-info}/PKG-INFO +2 -1
  4. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/SQLAlchemy_Continuum.egg-info/SOURCES.txt +2 -0
  5. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/configuration.rst +6 -0
  6. sqlalchemy_continuum-1.6.0/docs/queries.rst +254 -0
  7. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/pyproject.toml +2 -1
  8. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/__init__.py +1 -1
  9. sqlalchemy_continuum-1.6.0/sqlalchemy_continuum/fetcher.py +353 -0
  10. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/manager.py +1 -0
  11. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/table_builder.py +60 -1
  12. sqlalchemy_continuum-1.6.0/sqlalchemy_continuum/version.py +176 -0
  13. sqlalchemy_continuum-1.6.0/tests/.DS_Store +0 -0
  14. sqlalchemy_continuum-1.6.0/tests/test_efficient_queries.py +207 -0
  15. sqlalchemy_continuum-1.5.1/docs/queries.rst +0 -73
  16. sqlalchemy_continuum-1.5.1/sqlalchemy_continuum/fetcher.py +0 -176
  17. sqlalchemy_continuum-1.5.1/sqlalchemy_continuum/version.py +0 -73
  18. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/LICENSE +0 -0
  19. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/MANIFEST.in +0 -0
  20. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/README.rst +0 -0
  21. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/SQLAlchemy_Continuum.egg-info/dependency_links.txt +0 -0
  22. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/SQLAlchemy_Continuum.egg-info/requires.txt +0 -0
  23. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/SQLAlchemy_Continuum.egg-info/top_level.txt +0 -0
  24. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/Makefile +0 -0
  25. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/alembic.rst +0 -0
  26. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/api.rst +0 -0
  27. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/conf.py +0 -0
  28. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/index.rst +0 -0
  29. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/intro.rst +0 -0
  30. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/license.rst +0 -0
  31. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/make.bat +0 -0
  32. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/native_versioning.rst +0 -0
  33. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/plugins.rst +0 -0
  34. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/revert.rst +0 -0
  35. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/schema.rst +0 -0
  36. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/transactions.rst +0 -0
  37. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/utilities.rst +0 -0
  38. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/version_objects.rst +0 -0
  39. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/setup.cfg +0 -0
  40. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/_compat.py +0 -0
  41. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/builder.py +0 -0
  42. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/dialects/__init__.py +0 -0
  43. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/dialects/postgresql.py +0 -0
  44. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/exc.py +0 -0
  45. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/expression_reflector.py +0 -0
  46. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/factory.py +0 -0
  47. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/model_builder.py +0 -0
  48. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/operation.py +0 -0
  49. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/__init__.py +0 -0
  50. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/activity.py +0 -0
  51. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/base.py +0 -0
  52. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/flask.py +0 -0
  53. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/null_delete.py +0 -0
  54. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/property_mod_tracker.py +0 -0
  55. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/transaction_changes.py +0 -0
  56. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/transaction_meta.py +0 -0
  57. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/relationship_builder.py +0 -0
  58. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/reverter.py +0 -0
  59. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/schema.py +0 -0
  60. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/transaction.py +0 -0
  61. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/unit_of_work.py +0 -0
  62. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/utils.py +0 -0
  63. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/__init__.py +0 -0
  64. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/builders/__init__.py +0 -0
  65. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/builders/test_model_builder.py +0 -0
  66. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/builders/test_relationship_builder.py +0 -0
  67. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/builders/test_table_builder.py +0 -0
  68. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/dialects/__init__.py +0 -0
  69. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/dialects/test_triggers.py +0 -0
  70. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/inheritance/__init__.py +0 -0
  71. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/inheritance/test_common_base_class.py +0 -0
  72. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/inheritance/test_concrete_inheritance.py +0 -0
  73. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/inheritance/test_join_table_inheritance.py +0 -0
  74. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/inheritance/test_multi_level_inheritance.py +0 -0
  75. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/inheritance/test_single_table_inheritance.py +0 -0
  76. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/__init__.py +0 -0
  77. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_activity.py +0 -0
  78. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_flask.py +0 -0
  79. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_null_delete.py +0 -0
  80. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_plugin_collection.py +0 -0
  81. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_property_mod_tracker.py +0 -0
  82. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_transaction_changes.py +0 -0
  83. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_transaction_meta.py +0 -0
  84. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/__init__.py +0 -0
  85. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_association_table_relations.py +0 -0
  86. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_custom_condition_relations.py +0 -0
  87. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_dynamic_relationships.py +0 -0
  88. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_many_to_many_relations.py +0 -0
  89. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_non_versioned_classes.py +0 -0
  90. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_one_to_many_relations.py +0 -0
  91. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_one_to_one_relations.py +0 -0
  92. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/revert/__init__.py +0 -0
  93. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/revert/test_deep_relationships.py +0 -0
  94. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/revert/test_many_to_many_relationships.py +0 -0
  95. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/revert/test_one_to_one_relationship.py +0 -0
  96. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/revert/test_one_to_one_with_secondary_table.py +0 -0
  97. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/schema/__init__.py +0 -0
  98. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/schema/test_update_end_transaction_id.py +0 -0
  99. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/schema/test_update_property_mod_flags.py +0 -0
  100. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_accessors.py +0 -0
  101. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_changeset.py +0 -0
  102. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_column_aliases.py +0 -0
  103. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_column_inclusion_and_exclusion.py +0 -0
  104. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_compatibility.py +0 -0
  105. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_composite_primary_key.py +0 -0
  106. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_configuration.py +0 -0
  107. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_custom_schema.py +0 -0
  108. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_custom_version_base_class.py +0 -0
  109. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_delete.py +0 -0
  110. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_exotic_listener_chaining.py +0 -0
  111. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_exotic_operation_combos.py +0 -0
  112. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_insert.py +0 -0
  113. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_mapper_args.py +0 -0
  114. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_raw_sql.py +0 -0
  115. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_revert.py +0 -0
  116. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_savepoints.py +0 -0
  117. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_sessions.py +0 -0
  118. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_transaction.py +0 -0
  119. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_update.py +0 -0
  120. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_vacuum.py +0 -0
  121. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_validity_strategy.py +0 -0
  122. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_validity_strategy_multithreaded.py +0 -0
  123. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_versions.py +0 -0
  124. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/__init__.py +0 -0
  125. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/test_changeset.py +0 -0
  126. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/test_count_versions.py +0 -0
  127. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/test_is_modified.py +0 -0
  128. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/test_parent_class.py +0 -0
  129. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/test_transaction_class.py +0 -0
  130. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/test_tx_column_name.py +0 -0
  131. {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/test_version_class.py +0 -0
@@ -7,9 +7,27 @@ Unreleased changes
7
7
  ^^^^^^^^^^^^^^^^^^
8
8
  - None currently
9
9
 
10
+ 1.6.0 (2026-01-22)
11
+ ^^^^^^^^^^^^^^^^^^
12
+ - Add ``version_at()`` class method on version objects for efficient retrieval of the version active at a specific transaction (#376)
13
+ - Add ``all_versions()`` class method with ``link`` option to batch fetch all versions for an entity in a single query, avoiding N+1 queries by pre-populating previous/next navigation caches (#376)
14
+ - Add automatic composite indexes on version tables for optimized version lookups: ``(pk_columns, transaction_id DESC)`` index and ``(pk_columns, transaction_id, end_transaction_id)`` for validity strategy temporal queries (#376)
15
+ - Add ``create_composite_index`` configuration option to control composite index creation (#376)
16
+
17
+ 1.5.2 (2025-10-10)
18
+ ^^^^^^^^^^^^^^^^^^
19
+ - Add Python 3.14 support
20
+
10
21
  1.5.1 (2025-10-01)
11
22
  ^^^^^^^^^^^^^^^^^^
12
23
  - Fix utc_now() to return a naive datetime (#373, thanks to dawhalen)
24
+ - Remove SQLAlchemy-Utils dependency by porting required functions to internal _compat module (#352)
25
+ Note: if you use SQLAlchemy-Utils directly, you may need to add it as a dependency.
26
+ - Port core functions: ImproperlyConfigured, get_declarative_base, naturally_equivalent
27
+ - Port column utilities: get_columns, get_primary_keys, identity, get_column_key
28
+ - Port advanced functionality: has_changes, JSONType, generic_relationship with full SQLAlchemy 2.x compatibility
29
+ - Maintain full backward compatibility while eliminating external dependency
30
+ - Reduce installation footprint and potential version conflicts
13
31
 
14
32
  1.5.0 (2025-08-30)
15
33
  ^^^^^^^^^^^^^^^^^^
@@ -28,13 +46,6 @@ Unreleased changes
28
46
  - Fix datetime.utcnow() deprecation warnings with cross-version compatibility function supporting Python 3.9-3.13+
29
47
  - Eliminate cartesian product warnings in many-to-many relationship queries with non-versioned classes
30
48
  - Improve code quality by modernizing mixed string formatting patterns to f-strings
31
- - **MAJOR**: Remove SQLAlchemy-Utils dependency by porting required functions to internal _compat module (#352)
32
-
33
- - Port core functions: ImproperlyConfigured, get_declarative_base, naturally_equivalent
34
- - Port column utilities: get_columns, get_primary_keys, identity, get_column_key
35
- - Port advanced functionality: has_changes, JSONType, generic_relationship with full SQLAlchemy 2.x compatibility
36
- - Maintain full backward compatibility while eliminating external dependency
37
- - Reduce installation footprint and potential version conflicts
38
49
 
39
50
  1.4.2 (2024-03-26)
40
51
  ^^^^^^^^^^^^^^^^^^
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SQLAlchemy-Continuum
3
- Version: 1.5.1
3
+ Version: 1.6.0
4
4
  Summary: Versioning and auditing extension for SQLAlchemy.
5
5
  Author: Mark Steward, Jose Soto
6
6
  Author-email: Konsta Vesterinen <konsta@fastmonkeys.com>
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
19
20
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
20
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
22
  Requires-Python: >=3.9
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SQLAlchemy-Continuum
3
- Version: 1.5.1
3
+ Version: 1.6.0
4
4
  Summary: Versioning and auditing extension for SQLAlchemy.
5
5
  Author: Mark Steward, Jose Soto
6
6
  Author-email: Konsta Vesterinen <konsta@fastmonkeys.com>
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
19
20
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
20
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
22
  Requires-Python: >=3.9
@@ -53,6 +53,7 @@ sqlalchemy_continuum/plugins/null_delete.py
53
53
  sqlalchemy_continuum/plugins/property_mod_tracker.py
54
54
  sqlalchemy_continuum/plugins/transaction_changes.py
55
55
  sqlalchemy_continuum/plugins/transaction_meta.py
56
+ tests/.DS_Store
56
57
  tests/__init__.py
57
58
  tests/test_accessors.py
58
59
  tests/test_changeset.py
@@ -64,6 +65,7 @@ tests/test_configuration.py
64
65
  tests/test_custom_schema.py
65
66
  tests/test_custom_version_base_class.py
66
67
  tests/test_delete.py
68
+ tests/test_efficient_queries.py
67
69
  tests/test_exotic_listener_chaining.py
68
70
  tests/test_exotic_operation_combos.py
69
71
  tests/test_insert.py
@@ -113,6 +113,12 @@ Here is a full list of configuration options:
113
113
  * strategy (default: 'validity')
114
114
  The versioning strategy to use. Either 'validity' or 'subquery'
115
115
 
116
+ * create_composite_index (default: True)
117
+ Whether to automatically create composite indexes on version tables for efficient version queries.
118
+ The composite index is created on ``(primary_key_columns, transaction_id DESC)`` which dramatically
119
+ speeds up common query patterns like finding a specific version of an entity. For validity strategy,
120
+ an additional index on ``(primary_key_columns, transaction_id, end_transaction_id)`` is also created.
121
+
116
122
 
117
123
  Example
118
124
  ::
@@ -0,0 +1,254 @@
1
+ Queries
2
+ =======
3
+
4
+
5
+ You can query history models just like any other sqlalchemy declarative model.
6
+
7
+ ::
8
+
9
+ from sqlalchemy_continuum import version_class
10
+
11
+
12
+ ArticleVersion = version_class(Article)
13
+
14
+ session.query(ArticleVersion).filter_by(name=u'some name').all()
15
+
16
+
17
+ How many transactions have been executed?
18
+ -----------------------------------------
19
+
20
+ ::
21
+
22
+ from sqlalchemy_continuum import transaction_class
23
+
24
+
25
+ Transaction = transaction_class(Article)
26
+
27
+
28
+ Transaction.query.count()
29
+
30
+
31
+ Querying for entities of a class at a given revision
32
+ ----------------------------------------------------
33
+
34
+
35
+ In the following example we find all articles which were affected by transaction 33.
36
+
37
+ ::
38
+
39
+ session.query(ArticleVersion).filter_by(transaction_id=33)
40
+
41
+
42
+
43
+ Querying for transactions, at which entities of a given class changed
44
+ ---------------------------------------------------------------------
45
+
46
+ In this example we find all transactions which affected any instance of 'Article' model. This query needs the TransactionChangesPlugin.
47
+
48
+ ::
49
+
50
+ TransactionChanges = Article.__versioned__['transaction_changes']
51
+
52
+
53
+ entries = (
54
+ session.query(Transaction)
55
+ .innerjoin(Transaction.changes)
56
+ .filter(
57
+ TransactionChanges.entity_name.in_(['Article'])
58
+ )
59
+ )
60
+
61
+
62
+
63
+ Querying for versions of entity that modified given property
64
+ ------------------------------------------------------------
65
+
66
+ In the following example we want to find all versions of Article class which changed the attribute 'name'. This example assumes you are using
67
+ PropertyModTrackerPlugin.
68
+
69
+ ::
70
+
71
+ ArticleVersion = version_class(Article)
72
+
73
+ session.query(ArticleHistory).filter(ArticleVersion.name_mod).all()
74
+
75
+
76
+ Efficient Version Queries
77
+ =========================
78
+
79
+ SQLAlchemy-Continuum provides several methods for efficiently querying version history
80
+ without incurring N+1 query problems.
81
+
82
+
83
+ Querying version at a specific transaction
84
+ ------------------------------------------
85
+
86
+ The ``version_at`` class method efficiently retrieves the version that was active
87
+ at a specific transaction. This is much faster than iterating through versions manually.
88
+
89
+ ::
90
+
91
+ ArticleVersion = version_class(Article)
92
+
93
+ # Get the version of Article #5 that was active at transaction #100
94
+ version = ArticleVersion.version_at(
95
+ session,
96
+ {'id': 5},
97
+ transaction_id=100
98
+ )
99
+
100
+ For the validity strategy (default), this uses an efficient range query::
101
+
102
+ WHERE transaction_id <= 100
103
+ AND (end_transaction_id > 100 OR end_transaction_id IS NULL)
104
+
105
+ For the subquery strategy, it finds the version with the highest transaction_id <= target.
106
+
107
+
108
+ Batch fetching all versions
109
+ ---------------------------
110
+
111
+ When you need to iterate through version history, avoid the N+1 query problem by
112
+ using ``all_versions`` instead of repeatedly accessing ``.previous`` or ``.next``:
113
+
114
+ ::
115
+
116
+ ArticleVersion = version_class(Article)
117
+
118
+ # Fetch all versions for Article #5 in a single query
119
+ versions = ArticleVersion.all_versions(
120
+ session,
121
+ {'id': 5},
122
+ limit=10, # Optional: limit to 10 most recent
123
+ desc=True, # Newest first (default)
124
+ link=True # Pre-populate previous/next caches (default)
125
+ )
126
+
127
+ # Now iteration doesn't trigger additional queries
128
+ for version in versions:
129
+ print(version.changeset)
130
+ print(version.previous) # Uses cached value, no additional query!
131
+
132
+
133
+ When ``link=True`` (the default), the returned versions will have their ``.previous``
134
+ and ``.next`` properties pre-populated from the fetched results. This means accessing
135
+ these properties won't trigger additional database queries.
136
+
137
+ **Anti-pattern to avoid:**
138
+
139
+ ::
140
+
141
+ # BAD: This triggers N queries for N versions!
142
+ version = article.versions[-1]
143
+ while version:
144
+ process(version)
145
+ version = version.previous # Each call is a separate query
146
+
147
+ **Recommended pattern:**
148
+
149
+ ::
150
+
151
+ # GOOD: Single query, then iterate in memory
152
+ versions = ArticleVersion.all_versions(
153
+ session,
154
+ {'id': article.id}
155
+ )
156
+ for version in versions:
157
+ process(version)
158
+
159
+
160
+ Index Recommendations
161
+ =====================
162
+
163
+ SQLAlchemy-Continuum automatically creates several indexes on version tables.
164
+ Understanding these indexes helps you write efficient queries.
165
+
166
+
167
+ Automatic Indexes
168
+ -----------------
169
+
170
+ The following indexes are created automatically:
171
+
172
+ * ``transaction_id`` - Primary key index (always present)
173
+ * ``end_transaction_id`` - For validity strategy (enables efficient range queries)
174
+ * ``operation_type`` - For filtering INSERT/UPDATE/DELETE operations
175
+
176
+ Starting with version 1.6.0, composite indexes are also created by default:
177
+
178
+ * ``(primary_keys, transaction_id DESC)`` - For efficient entity version lookups
179
+ * ``(primary_keys, transaction_id, end_transaction_id)`` - For validity strategy temporal queries
180
+
181
+ These composite indexes dramatically speed up the most common query patterns.
182
+
183
+
184
+ Disabling Composite Indexes
185
+ ---------------------------
186
+
187
+ If you need to disable automatic composite index creation (e.g., for migration compatibility),
188
+ you can set the ``create_composite_index`` option to ``False``::
189
+
190
+ make_versioned(options={'create_composite_index': False})
191
+
192
+ Or per-model::
193
+
194
+ class Article(Base):
195
+ __versioned__ = {
196
+ 'create_composite_index': False
197
+ }
198
+
199
+
200
+ Recommended Additional Indexes
201
+ ------------------------------
202
+
203
+ Depending on your query patterns, you may want to add these additional indexes:
204
+
205
+ **For queries filtering by operation type and entity:**
206
+
207
+ ::
208
+
209
+ from sqlalchemy import Index
210
+
211
+ Index(
212
+ 'ix_article_version_id_operation',
213
+ ArticleVersion.id,
214
+ ArticleVersion.operation_type,
215
+ ArticleVersion.transaction_id
216
+ )
217
+
218
+ **For queries joining with Transaction table on issued_at:**
219
+
220
+ If you frequently query versions by timestamp (e.g., "give me the version as of 2023-01-01"),
221
+ ensure you have an index on ``Transaction.issued_at``::
222
+
223
+ Index('ix_transaction_issued_at', Transaction.issued_at.desc())
224
+
225
+ **For PropertyModTrackerPlugin queries:**
226
+
227
+ If you use PropertyModTrackerPlugin and frequently query for versions where specific
228
+ fields changed, consider partial indexes::
229
+
230
+ # PostgreSQL partial index example
231
+ Index(
232
+ 'ix_article_version_name_mod',
233
+ ArticleVersion.id,
234
+ postgresql_where=ArticleVersion.name_mod.is_(True)
235
+ )
236
+
237
+
238
+ Query Performance Tips
239
+ ----------------------
240
+
241
+ 1. **Use the validity strategy** (default) for read-heavy workloads. It enables
242
+ O(log N) version lookups via direct equality conditions instead of correlated subqueries.
243
+
244
+ 2. **Batch fetch versions** using ``all_versions()`` instead of iterating with ``.previous``/``.next``.
245
+
246
+ 3. **Add composite indexes** on ``(entity_pk, transaction_id)`` for your most-queried version tables.
247
+
248
+ 4. **Use LIMIT** when you only need recent versions::
249
+
250
+ ArticleVersion.all_versions(session, {'id': 5}, limit=10)
251
+
252
+ 5. **Avoid relationship traversal** on version objects when possible. Relationship queries
253
+ on versions generate complex subqueries. If you need related data, fetch from the
254
+ parent object first or use explicit joins.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "SQLAlchemy-Continuum"
7
- version = "1.5.1"
7
+ version = "1.6.0"
8
8
  description = "Versioning and auditing extension for SQLAlchemy."
9
9
  readme = "README.rst"
10
10
  license = "BSD-3-Clause"
@@ -25,6 +25,7 @@ classifiers = [
25
25
  "Programming Language :: Python :: 3.11",
26
26
  "Programming Language :: Python :: 3.12",
27
27
  "Programming Language :: Python :: 3.13",
28
+ "Programming Language :: Python :: 3.14",
28
29
  "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
29
30
  "Topic :: Software Development :: Libraries :: Python Modules"
30
31
  ]
@@ -37,7 +37,7 @@ from .utils import (
37
37
  version_class as version_class,
38
38
  )
39
39
 
40
- __version__ = '1.5.1'
40
+ __version__ = '1.6.0'
41
41
 
42
42
 
43
43
  versioning_manager = VersioningManager()