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.
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/CHANGES.rst +18 -7
- {sqlalchemy_continuum-1.5.1/SQLAlchemy_Continuum.egg-info → sqlalchemy_continuum-1.6.0}/PKG-INFO +2 -1
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0/SQLAlchemy_Continuum.egg-info}/PKG-INFO +2 -1
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/SQLAlchemy_Continuum.egg-info/SOURCES.txt +2 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/configuration.rst +6 -0
- sqlalchemy_continuum-1.6.0/docs/queries.rst +254 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/pyproject.toml +2 -1
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/__init__.py +1 -1
- sqlalchemy_continuum-1.6.0/sqlalchemy_continuum/fetcher.py +353 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/manager.py +1 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/table_builder.py +60 -1
- sqlalchemy_continuum-1.6.0/sqlalchemy_continuum/version.py +176 -0
- sqlalchemy_continuum-1.6.0/tests/.DS_Store +0 -0
- sqlalchemy_continuum-1.6.0/tests/test_efficient_queries.py +207 -0
- sqlalchemy_continuum-1.5.1/docs/queries.rst +0 -73
- sqlalchemy_continuum-1.5.1/sqlalchemy_continuum/fetcher.py +0 -176
- sqlalchemy_continuum-1.5.1/sqlalchemy_continuum/version.py +0 -73
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/LICENSE +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/MANIFEST.in +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/README.rst +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/SQLAlchemy_Continuum.egg-info/dependency_links.txt +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/SQLAlchemy_Continuum.egg-info/requires.txt +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/SQLAlchemy_Continuum.egg-info/top_level.txt +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/Makefile +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/alembic.rst +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/api.rst +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/conf.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/index.rst +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/intro.rst +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/license.rst +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/make.bat +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/native_versioning.rst +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/plugins.rst +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/revert.rst +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/schema.rst +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/transactions.rst +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/utilities.rst +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/docs/version_objects.rst +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/setup.cfg +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/_compat.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/builder.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/dialects/__init__.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/dialects/postgresql.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/exc.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/expression_reflector.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/factory.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/model_builder.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/operation.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/__init__.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/activity.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/base.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/flask.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/null_delete.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/property_mod_tracker.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/transaction_changes.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/plugins/transaction_meta.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/relationship_builder.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/reverter.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/schema.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/transaction.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/unit_of_work.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/sqlalchemy_continuum/utils.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/__init__.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/builders/__init__.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/builders/test_model_builder.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/builders/test_relationship_builder.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/builders/test_table_builder.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/dialects/__init__.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/dialects/test_triggers.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/inheritance/__init__.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/inheritance/test_common_base_class.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/inheritance/test_concrete_inheritance.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/inheritance/test_join_table_inheritance.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/inheritance/test_multi_level_inheritance.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/inheritance/test_single_table_inheritance.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/__init__.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_activity.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_flask.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_null_delete.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_plugin_collection.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_property_mod_tracker.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_transaction_changes.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/plugins/test_transaction_meta.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/__init__.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_association_table_relations.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_custom_condition_relations.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_dynamic_relationships.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_many_to_many_relations.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_non_versioned_classes.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_one_to_many_relations.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/relationships/test_one_to_one_relations.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/revert/__init__.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/revert/test_deep_relationships.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/revert/test_many_to_many_relationships.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/revert/test_one_to_one_relationship.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/revert/test_one_to_one_with_secondary_table.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/schema/__init__.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/schema/test_update_end_transaction_id.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/schema/test_update_property_mod_flags.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_accessors.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_changeset.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_column_aliases.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_column_inclusion_and_exclusion.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_compatibility.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_composite_primary_key.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_configuration.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_custom_schema.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_custom_version_base_class.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_delete.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_exotic_listener_chaining.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_exotic_operation_combos.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_insert.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_mapper_args.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_raw_sql.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_revert.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_savepoints.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_sessions.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_transaction.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_update.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_vacuum.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_validity_strategy.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_validity_strategy_multithreaded.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/test_versions.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/__init__.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/test_changeset.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/test_count_versions.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/test_is_modified.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/test_parent_class.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/test_transaction_class.py +0 -0
- {sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/tests/utils/test_tx_column_name.py +0 -0
- {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
|
^^^^^^^^^^^^^^^^^^
|
{sqlalchemy_continuum-1.5.1/SQLAlchemy_Continuum.egg-info → sqlalchemy_continuum-1.6.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SQLAlchemy-Continuum
|
|
3
|
-
Version: 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
|
{sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0/SQLAlchemy_Continuum.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SQLAlchemy-Continuum
|
|
3
|
-
Version: 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
|
{sqlalchemy_continuum-1.5.1 → sqlalchemy_continuum-1.6.0}/SQLAlchemy_Continuum.egg-info/SOURCES.txt
RENAMED
|
@@ -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.
|
|
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
|
]
|