kvesteri / sqlalchemy-continuum Goto Github PK
View Code? Open in Web Editor NEWVersioning extension for SQLAlchemy.
License: BSD 3-Clause "New" or "Revised" License
Versioning extension for SQLAlchemy.
License: BSD 3-Clause "New" or "Revised" License
Hi,
In the docs (http://sqlalchemy-continuum.readthedocs.org/en/latest/intro.html) there is the statement:
from sqlalchemy_continuum import history_class, parent_class
But this gives an import error. Furthermore, doing git grep history_class on the current master branch gives no results outside comments and documentation. Surely this is not the way it's supposed to be. Furthermore, I can't seem to realize how to get the history class for direct querying.
Thanks,
Kári
Hi, trying to introduce continuum to my unittests, but the make_versioned
is giving excessive trouble. I want to be able to call make_versioned
in the test setup as well as undo calling it in the teardown. It looks like make_versioned
is not meant for that, I see that you are also calling it once globally in your own tests.
Calling it twice in a row actually throws SQL alchemy errrors related to already registered events. If I were to unregister all events that are introduced by continuum, would that suffice as an undo_make_versioned
function?
Currently it does not check if given entity was actually changed.
My testcase is this:
def test_version_order(session, order_factory):
order = order_factory.build(store_id=1)
session.add(order)
session.commit()
OrderHistory = Order.__versioned__['class']
assert session.query(OrderHistory).count() == 1
version = order.versions[0]
assert version.index == 0
assert version.operation_type == Operation.INSERT
print version.transaction_id
print version.previous.transaction_id
print version.previous.previous.previous.previous
version.previous()
is never None, as stated in https://github.com/kvesteri/sqlalchemy-continuum/blob/master/sqlalchemy_continuum/version.py#L11
I'm using python 2.6.6, sqlite, sqlalchemy, flask-sqlalchemy and pytest.
So I have a site that would benefit from your amazing plugin!
Is it possible to do the following:
Have a non versioned current_version field on models that selects a version in the past for the live site. This current_version does not change or cause model version increments when it is changed by the application.
When a change is made to the database (in my case a list of residents) these changes are added to a list for the site's administrators to look over. Changes are per user, but this should be fairly self explanatory - there are only two tables a user can change as things stand and they are both changed from one form.
If a version is rejected we could add the version number and the reason to another table.
If the version is approved we increment current version to that one and the live application sees the new information.
This obviously allows us to easily do previewing of changes by simply passing the version number we want to see to our view.
Additionally, are there any problems associated with having master/slave replication and possibly in the far far distant future multiple sharded databases. Data for each Resident would never span shards.
Hope this is the place to ask this sort of question; if not please close and suggest where to ask it! Thanks.
Hi,
i try to autogenerate an Alembic revision with models that contain a many-to-many relationship. Without sqlalchemy-continuum everything works fine. However if i try to include sqlalchemy-continuum Alembic fails with the following error:
Traceback (most recent call last):
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/bin/alembic", line 9, in
load_entry_point('alembic==0.6.0', 'console_scripts', 'alembic')()
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/local/lib/python2.7/site-packages/alembic/config.py", line 294, in main
CommandLine(prog=prog).main(argv=argv)
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/local/lib/python2.7/site-packages/alembic/config.py", line 289, in main
self.run_cmd(cfg, options)
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/local/lib/python2.7/site-packages/alembic/config.py", line 275, in run_cmd
*_dict((k, getattr(options, k)) for k in kwarg)
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/local/lib/python2.7/site-packages/alembic/command.py", line 97, in revision
script.run_env()
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/local/lib/python2.7/site-packages/alembic/script.py", line 193, in run_env
util.load_python_file(self.dir, 'env.py')
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/local/lib/python2.7/site-packages/alembic/util.py", line 177, in load_python_file
module = load_module(module_id, path)
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/local/lib/python2.7/site-packages/alembic/compat.py", line 39, in load_module
return imp.load_source(module_id, path, fp)
File "migrations/env.py", line 25, in
import models
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/models.py", line 60, in
sa.orm.configure_mappers()
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/local/lib/python2.7/site-packages/sqlalchemy/orm/mapper.py", line 2170, in configure_mappers
_call_configured.dispatch.after_configured()
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/local/lib/python2.7/site-packages/sqlalchemy/event.py", line 372, in call
fn(_args, *_kw)
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/local/lib/python2.7/site-packages/sqlalchemy/orm/events.py", line 489, in wrap
wrapped_fn(_arg, **kw)
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/local/lib/python2.7/site-packages/sqlalchemy_continuum/manager.py", line 373, in configure_versioned_classes
self.build_relationships(pending_copy)
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/local/lib/python2.7/site-packages/sqlalchemy_continuum/manager.py", line 275, in build_relationships
builder()
File "/home/khoffrath/projects/testprojects/sa-alembic-continuum-m2m/venv/local/lib/python2.7/site-packages/sqlalchemy_continuum/relationship_builder.py", line 152, in call
self.remote_column.table.name + '_history'
KeyError: u'customer_roles_history'
I've created a small project to demonstrate the problem: https://github.com/khoffrath/sa-alembic-continuum-m2m.
Can you please take a look?
Thanks in advance
Karsten
I just upgraded to the most recent version (this repos HEAD) and now I'm getting an error which seemingly has to do with the user transaction table "automagically" having a user column that has a broken relationship with my User table/model (probably due to the fact that it's not called "users"). I'm wondering if this is expected behaviour and I need to explicitly disable this automatic column generation, or if there is some error somewhere. To avoid this column being generated, I specified:
class Transaction(Base, TransactionBase):
__tablename__ = 'transaction'
__manager__ = VersioningManager
make_versioned(manager=VersioningManager(transaction_cls=Transaction))
instead of the simple make_versioned()
clause I had before.
Here is the original stack trace:
NoForeignKeysError Traceback (most recent call last)
/home/kari/repos/SITE/createdb.py in <module>()
----> 1 from APP import db_create_all
2 db_create_all()
/home/kari/repos/SITE/APP/__init__.pyc in <module>()
120 app.register_module(auth)
121
--> 122 sa.orm.configure_mappers()
/home/kari/.virtualenvs/ENV/lib/python2.7/site-packages/sqlalchemy/orm/mapper.pyc in configure_mappers()
2573 finally:
2574 _CONFIGURE_MUTEX.release()
-> 2575 Mapper.dispatch(Mapper).after_configured()
2576
2577
/home/kari/.virtualenvs/ENV/lib/python2.7/site-packages/sqlalchemy/event/attr.pyc in __call__(self, *args, **kw)
216
217 for fn in self.parent_listeners:
--> 218 fn(*args, **kw)
219
220 def __len__(self):
/home/kari/.virtualenvs/ENV/lib/python2.7/site-packages/sqlalchemy/orm/events.pyc in wrap(*arg, **kw)
529 arg[target_index] = arg[target_index].obj()
530 if not retval:
--> 531 fn(*arg, **kw)
532 return interfaces.EXT_CONTINUE
533 else:
/home/kari/.virtualenvs/ENV/src/sqlalchemy-continuum/sqlalchemy_continuum/builder.pyc in configure_versioned_classes(self)
135 pending_copy = copy(self.manager.pending_classes)
136 self.manager.pending_classes = []
--> 137 self.build_relationships(pending_copy)
138
139 for cls in pending_copy:
/home/kari/.virtualenvs/ENV/src/sqlalchemy-continuum/sqlalchemy_continuum/builder.pyc in build_relationships(self, version_classes)
91 continue
92
---> 93 for prop in sa.inspect(cls).iterate_properties:
94 if prop.key == 'versions':
95 continue
/home/kari/.virtualenvs/ENV/lib/python2.7/site-packages/sqlalchemy/orm/mapper.pyc in iterate_properties(self)
1780 """return an iterator of all MapperProperty objects."""
1781 if Mapper._new_mappers:
-> 1782 configure_mappers()
1783 return iter(self._props.values())
1784
/home/kari/.virtualenvs/ENV/lib/python2.7/site-packages/sqlalchemy/orm/mapper.pyc in configure_mappers()
2558 if not mapper.configured:
2559 try:
-> 2560 mapper._post_configure_properties()
2561 mapper._expire_memoizations()
2562 mapper.dispatch.mapper_configured(
/home/kari/.virtualenvs/ENV/lib/python2.7/site-packages/sqlalchemy/orm/mapper.pyc in _post_configure_properties(self)
1671
1672 if prop.parent is self and not prop._configure_started:
-> 1673 prop.init()
1674
1675 if prop._configure_finished:
/home/kari/.virtualenvs/ENV/lib/python2.7/site-packages/sqlalchemy/orm/interfaces.pyc in init(self)
141 """
142 self._configure_started = True
--> 143 self.do_init()
144 self._configure_finished = True
145
/home/kari/.virtualenvs/ENV/lib/python2.7/site-packages/sqlalchemy/orm/relationships.pyc in do_init(self)
1508 self._check_conflicts()
1509 self._process_dependent_arguments()
-> 1510 self._setup_join_conditions()
1511 self._check_cascade_settings(self._cascade)
1512 self._post_init()
/home/kari/.virtualenvs/ENV/lib/python2.7/site-packages/sqlalchemy/orm/relationships.pyc in _setup_join_conditions(self)
1584 prop=self,
1585 support_sync=not self.viewonly,
-> 1586 can_be_synced_fn=self._columns_are_mapped
1587 )
1588 self.primaryjoin = jc.deannotated_primaryjoin
/home/kari/.virtualenvs/ENV/lib/python2.7/site-packages/sqlalchemy/orm/relationships.pyc in __init__(self, parent_selectable, child_selectable, parent_local_selectable, child_local_selectable, primaryjoin, secondary, secondaryjoin, parent_equivalents, child_equivalents, consider_as_foreign_keys, local_remote_pairs, remote_side, self_referential, prop, support_sync, can_be_synced_fn)
1847 self.support_sync = support_sync
1848 self.can_be_synced_fn = can_be_synced_fn
-> 1849 self._determine_joins()
1850 self._annotate_fks()
1851 self._annotate_remote()
/home/kari/.virtualenvs/ENV/lib/python2.7/site-packages/sqlalchemy/orm/relationships.pyc in _determine_joins(self)
1951 "with a ForeignKey or ForeignKeyConstraint, or "
1952 "specify a 'primaryjoin' expression."
-> 1953 % self.prop)
1954 except sa_exc.AmbiguousForeignKeysError:
1955 if self.secondary is not None:
NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Transaction.user - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
Thank you.
I seem to get a TypeError when using the latest version:
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1836, in __call__
return self.wsgi_app(environ, start_response)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1820, in wsgi_app
response = self.make_response(self.handle_exception(e))
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1403, in handle_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1817, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1477, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1381, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1475, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1461, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "./assetregister/api/__init__.py", line 39, in decorated
return f(*args, **kwargs)
File "./assetregister/api/servers.py", line 114, in server_edit
g.session.commit()
File "/home/paul/dev/assetregister/env/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 765, in commit
self.transaction.commit()
File "/home/paul/dev/assetregister/env/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 370, in commit
self._prepare_impl()
File "/home/paul/dev/assetregister/env/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 350, in _prepare_impl
self.session.flush()
File "/home/paul/dev/assetregister/env/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1879, in flush
self._flush(objects)
File "/home/paul/dev/assetregister/env/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 1906, in _flush
self.dispatch.before_flush(self, flush_context, objects)
File "/home/paul/dev/assetregister/env/local/lib/python2.7/site-packages/sqlalchemy/event/attr.py", line 211, in __call__
fn(*args, **kw)
File "/home/paul/dev/assetregister/env/local/lib/python2.7/site-packages/sqlalchemy_continuum/manager.py", line 274, in before_flush
uow = self.unit_of_work(session)
File "/home/paul/dev/assetregister/env/local/lib/python2.7/site-packages/sqlalchemy_continuum/manager.py", line 261, in unit_of_work
conn = get_bind(obj)
File "/home/paul/dev/assetregister/env/local/lib/python2.7/site-packages/sqlalchemy_continuum/utils.py", line 64, in get_bind
'This method accepts only Session, Connection and declarative '
TypeError: This method accepts only Session, Connection and declarative model objects.```
First off, I'm not sure if this is an issue with Continuum or with SQLAlchemy's dialect engine.
When attempting to create a new object in a versioned table, the following error is thrown before insertion of the object.
Tested on both MySQL 5.6 GPL Edition and MariaDB 5.5.
Module versions:
SQLAlchemy (0.9.2)
SQLAlchemy-Continuum (0.10.1)
SQLAlchemy-Utils (0.23.3)
OperationalError: (OperationalError) (1093, "You can't specify target table 'entity_history' for update in FROM clause") 'UPDATE entity_history SET end_transaction_id=%s WHERE entity_history.transaction_id = (SELECT max(entity_history_1.transaction_id) AS max_1 FROM entity_history AS entity_history_1 WHERE entity_history_1.transaction_id < %s) AND entity_history.id = %s' (2L, 2L, 2L)
Full traceback:
https://gist.github.com/anonymous/d7907460b77513e5e47b
I'm sure I'm missing something, but when I try to use sqlalchemy-continuum I get an error message "ImportError: cannot import name 'declarative_base'. Problem seems to be that line 8 tries to import declarative_base from sqlalchemy_utils.functions, but sqlalchemy-utils doesn't have declarative_base (though it does have get_declarative_base). I'm using sqlalchemy-continuum 0.10.3 and sqlalchemy-utils 0.25.4.
Hi,
I read some reference to alembic migration in changelog but didn't figure out how to get sa-continuum and alembic working together during an alembic migration.
Any hint ?
We could make something similar to:
I tried a minimalist sample.
With MySQL, I got :
AttributeError: 'MySQLTypeCompiler' object has no attribute 'visit_HSTORE'
With SQLite, I got :
AttributeError: 'SqliteTypeCompiler' object has no attribute 'visit_HSTORE'
Did I miss something ?
in your extension for flask you are using @sa.ext.declarative.declared_attr decorator
you imported sqlalchemy as sa
but sa doesn't contain reference to ext inside init.py, so this code will work only if i imported full package before your code execution... (otherwise module object doesn't have 'ext' attribute...)
Pull request #42 (which includes failing tests for this bug) was merged but issue is not fixed and tests are marked for skipping
Since ORM models that go beyond example material also define behavior (methods), it would be very convenient if the version models can inherit the behavior of the parent models. This is very useful in accessor methods (@property
, to_dict()
kind of methods etc.)
This already works partially when someone defines behavior on the Base class, since the version models by default inherit from the same Base.
There are a couple of ways to achieve this
__mapper__
, instrumented attributes, relations, validators etc.) this cannot be done in a straightforward way. I implemented this on my own using a hack in the ModelBuilder
that creates an intermediate model like this:class Model(Base):
#...
class AbstractModel(Model):
__abstract__ = True #at least this does not create a table and mapper, need more work to get rid of other "offensive" attributes
class ModelVersion(AbstractModel, VersionBase):
#...
class _ModelBehavior():
def useful_method(self):
#...
class Model(_ModelBehavior, Base):
__versioned__ = {"base_classes": (_ModelBehavior, VersionBase)}
#..
__getattr__
to access attributes from the parentclass VersionBase():
def __getattr__(self, name):
return getattr(self.version_parent, name)
This is the most hacky one with low reliability (this example does not work when version_parent is None for example)
Thoughts?
Currently the primary key is parent_table_pks + revision, but this structure does not cope well with transactions updating natural primary keys.
With a simple model :
class Article(Base):
__versioned__ = {}
__tablename__ = 'article'
id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
name = sa.Column(sa.Unicode(255))
content = sa.Column(sa.UnicodeText)
It raises :
sqlalchemy.exc.OperationalError: (OperationalError) (1075, 'Incorrect table definition; there can be only one auto column and it must be defined as a key') '
CREATE TABLE article_history (
id INTEGER NOT NULL,
name VARCHAR(255),
content TEXT,
transaction_id BIGINT NOT NULL AUTO_INCREMENT,
operation_type SMALLINT NOT NULL,
PRIMARY KEY (id, transaction_id)
)' ()
Test that currently fails:
diff --git a/tests/test_exotic_operation_combos.py b/tests/test_exotic_operation_combos.py
index 67d0b5f..9e261fa 100644
--- a/tests/test_exotic_operation_combos.py
+++ b/tests/test_exotic_operation_combos.py
@@ -52,3 +52,14 @@ class TestExoticOperationCombos(TestCase):
assert article2.versions.count() == 2
assert article2.versions[0].operation_type == 0
assert article2.versions[1].operation_type == 1
+
+ def test_insert_flushed_object(self):
+ article = self.Article()
+ article.name = u'Some article'
+ article.content = u'Some content'
+ self.session.add(article)
+ self.session.flush()
+ self.session.commit()
+
+ assert article.versions.count() == 1
+ assert article.versions[0].operation_type == 0
SQLAlchemy-Continuum 727b251
Flask==0.10.1
SQLAlchemy==0.9.2
SQLAlchemy-Utils==0.23.5
SQLAlchemy-i18n==0.8.2
flexmock==0.9.7
inflection==0.2.0
pytest==2.5.2
six==1.5.2
toolz==0.5.2
OS: Ubuntu 12.04 64-bit
PostgreSQL version: 9.1.3-2
When user has an existing project and starts using SA-Continuum the following problems are exposed:
For example lets say we have articles and users (each article has one user as owner). The articles and users are already in the database when the developer migrates the whole project to use Continuum. Then user updates article A. When user calls:
article.versions[-1].owner
It returns None, because there is no history yet for this user object.
Solution:
Lets make a migration function which generates history records for all versioned table rows.
Currently it uses txid_current() which is postgresql specific.
Traceback (most recent call last):
File "run.py", line 4, in
from app import app
File "/home/userdev/projects/userproject/app/init.py", line 38, in
from app.users.views import mod as usersModule
File "/home/userdev/projects/userproject/app/users/views.py", line 9, in
from app.users.models import User
File "/home/userdev/projects/userproject/app/users/models.py", line 4, in
from sqlalchemy_continuum import make_versioned
File "/home/userdev/envs/userproject/lib/python2.7/site-packages/sqlalchemy_continuum/init.py", line 2, in
from .manager import VersioningManager
File "/home/userdev/envs/userproject/lib/python2.7/site-packages/sqlalchemy_continuum/manager.py", line 2, in
from inflection import underscore, pluralize
ImportError: No module named inflection
Tested on postgresql, MariaDB.
History objects are saved to the database but the changeset helper property is always empty. Tested both Insert and Update operations.
inflection (0.2.0)
six (1.5.2)
SQLAlchemy (0.9.2)
SQLAlchemy-Continuum (0.10.2)
SQLAlchemy-Utils (0.23.5)
toolz (0.5.2)
When history table structure is changed the correlated triggers need to be changed too.
Accessing a models corresponding history class through ModelClass.versioned['class'] feels very awkward (evokes a feeling of poking around in internals better left alone).
Would you consider making it (and perhaps transaction_changes/_log) accessible via a "nicer" API?
For example: ModelClass.history_class
Orphan deletes should create history records.
This configuration option would determine if entity deletion should save the data in history record (currently the history record that marks delete operation only holds NULL values for all columns other than primary keys).
Say I have two models:
class User(Base):
__tablename__ = 'users'
id = sa.Column(sa.Integer, primary_key=True)
username = sa.Column(sa.Unicode(255))
class Article(Base):
__tablename__ = 'articles'
__versioned__ = {}
id = sa.Column(sa.Integer, primary_key=True)
user_id = sa.Column(sa.Integer, sa.ForeignKey('users.id'), nullable=False)
user = relationship('User', backref=backref('articles'))
I'm running into the problem that when I do something along the lines of:
article.versions[0].user # does not work
article.versions[0].user_id # returns the user id
I see that relationship fields seem to be copied to the history table if the related table is also versioned, and the instructions at http://sqlalchemy-continuum.readthedocs.org/en/latest/version_objects.html#version-relationships work given this assumption. However, I would argue that foreign key relations to non-versioned tables are also useful, in my case for example, it doesn't really make sens to version the users table (it's mostly static and preserving the history of the fields is not useful), but my application doesn't allow for user deletion (only deactivation) so the foreign key would work.
Any tips or ideas? I haven't delved into the codebase so I might be missing something.
Thanks,
Kári
Got this with MySQL :
CompileError: RETURNING is not supported by this dialect's statement compiler.
I am trying to add versioning to an existing project. I use column names that differ from the model attribute name. But that doesn't seem to work at all.
Here is an simple example:
import sqlalchemy as sa
from sqlalchemy_continuum import make_versioned
Base = sa.ext.declarative.declarative_base()
make_versioned()
class ProjectBrand(Base):
__tablename__ = "t_project_brands"
__versioned__ = {}
id = sa.Column(sa.Integer, primary_key=True)
ortho = sa.Column('p_ortho', sa.String, nullable = False, index=True)
sa.orm.configure_mappers()
engine = sa.create_engine('sqlite:///:memory:', echo=False)
Base.metadata.create_all(engine)
Session = sa.orm.sessionmaker(bind=engine)
session = Session()
brand1 = ProjectBrand()
brand1.ortho = "Brand1"
session.add(brand1)
session.commit()
brand1.ortho = "Brand2"
session.commit()
brand1.versions[0].revert()
session.commit()
When I run it I get the following error:
Traceback (most recent call last):
File "E:\Projects\Ford\sync3\workspace\POI_linguist\Aptana Studio 3 Workspace\Brand Linguistics Tool\src\continuum_test.py", line 35, in <module>
brand1.versions[0].revert()
File "C:\Python27\lib\site-packages\sqlalchemy_continuum\version.py", line 67, in revert
return Reverter(self, relations=relations)()
File "C:\Python27\lib\site-packages\sqlalchemy_continuum\reverter.py", line 102, in __call__
self.revert_properties()
File "C:\Python27\lib\site-packages\sqlalchemy_continuum\reverter.py", line 49, in revert_properties
getattr(self.obj, prop.key)
AttributeError: 'ProjectBrandHistory' object has no attribute 'ortho'
It seems it tries to use ortho attribute, but it hasn't mapped this internally to the correct table column name.
I'm unable to find how to find deleted objects through the versioning when using the flask TransactionLogBase
My problem is due to the fact that when a versioned table entry is deleted, the deleted transaction log is completely empty, with only the operation_type Operations.DELETE
If this could be done easily with the tx_meta, that would work for me.
I'm hoping to implement a full undo style feature to allow my users to revert changes or even deletions of their entities
The example in the docs not working.
Mike Bayer:
"you can tell if a statement is an INSERT/DELETE usually by looking at context.isinsert/context.isdelete, and also the table name
you can get from context.statement.table (something like that). Similar things can be done where I see you're regexping the DELETE
later on. Digging into the string is fine but once you're targeting the broad spectrum of scenarios, like users that are adding SQL comments and such to their SQL, backends that don't actually use SQL, you want to stick with inspecting the expression trees as much as possible"
I have a problem with the following use-case:
My Article
has one-to-many to Tag
. The end-user application only considers the Article history state (so the fact that Tags have state too is invisible), and is able to revert or see all snapshots of the Article history. The Article state is considered to be changed when the article itself is changed, when tags are added/removed and when one of its existing tags is modified.
How can I go back to all Article history (and by that I want to include snapshots where only the Tags of the article were modified, not necessarily the article column's)? The revert(relations=['Tags']
produces very unexpected results based on the number of the Article versions and the number of each Tag's versions.
If one of my tags has more versions that the article for example, there is no way to use article.revert(relations=['Tags'])
to go back to all states of that tag. How can I achieve this?
It would be nice to have an option like increment_version_on_relation_change
.
If I have a datetime (or any other column type) column in a table with onupdate= and include this in the history table, the onupdate clause gets erroneously (I'd argue) "copied" to the history table.
Example:
class Article(Base):
__versioned__ = {'include' : ['last_update', ]}
id = sa.Column(sa.Integer, primary_key=True)
body = sa.Column(sa.UnicodeText)
last_update = sa.Column(sa.DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow, nullable=False)
The articles table behaves correctly, in that the last_update column is updated each time a record is changed. However, the _history table, due to the update of end_transaction_id (I presume) fails to preserve the value of the column, so the second latest row effectively has the last_update value of the most recent update.
Thanks for an otherwise wonderful extension!
I didn't see any way of storing the history tables in a seperate database schema, is there some way of handling this already, or if not, feature request?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.