I have following method in my framework:
@models.managed_commit
def create_article(self, user_uid, title, **kwargs):
user = user_manager.UserManager().get_user_by_uid(user_uid)
# create empty article
article = models.Article(user=user, title=title)
# if profiles were supplied, update the model
self._process_profiles(kwargs)
self._attach_new_tags(kwargs.pop('tags', []), article)
# we can now simply update
article.update_from_dict(kwargs)
if not article.slug:
article.slug = utils.strings.slugify(title)
models.Session.add(article)
return article
def _attach_new_tags(self, tags, article):
if not tags:
return
tman = tag_manager.TagManager()
for tag in tags:
try:
models.Session.query(models.ArticleTag).filter(
(models.ArticleTag.article_uid == article.uid) &
(models.ArticleTag.tag_uid == tag.uid)).one()
except sqlalchemy.orm.exc.NoResultFound:
tag = tman.get_tag_by_uid(tag.uid)
obj = models.ArticleTag(tag=tag)
with models.Session.no_autoflush:
article.article_tags.append(obj)
@no_autoflush
def _process_profiles(self, kwargs):
if 'profiles' not in kwargs:
return
profiles = []
# rewrite profile if any
if kwargs['profiles']:
for profile in kwargs['profiles']:
profiles.append(self._get_profile(profile.uid))
kwargs['profiles'] = profiles
Models:
class Article(base.Base, mixin.PublishableMixin):
user_uid = sqlalchemy.Column(
sqlalchemy.Unicode(32), sqlalchemy.ForeignKey('user.uid'),
index=True)
title = sqlalchemy.Column(sqlalchemy.Unicode(255), nullable=False)
meta_description = sqlalchemy.Column(sqlalchemy.Unicode(255))
meta_title = sqlalchemy.Column(sqlalchemy.Unicode(255))
class PublishableMixin(base_mixin.BaseMixin):
'''Adds publishable columns to a model'''
published_at = sqlalchemy.Column(
sqlalchemy.DateTime(timezone=True), default=None, index=True)
published_at._creation_order = 9996 # pylint:
disable=protected-access
class BaseModel(object):
def update_from_dict(self, data):
_props = [
prop for prop in sqlalchemy.orm.object_mapper(
self).iterate_properties]
props = [prop.key for prop in _props if isinstance(
prop, sqlalchemy.orm.ColumnProperty)]
for key in data.keys():
if key in props:
try:
setattr(self, key, data.pop(key))
except AttributeError:
raise AttributeError('Cant set attribute: %s' % key)
elif hasattr(self, key):
try:
setattr(self, key, data.pop(key))
except AttributeError:
raise AttributeError('Cant set attribute: %s' % key)
else:
raise AttributeError(
'Key ({}) is not mapped property of {}'.format(
key, self.__class__.__name__))
This code in scenario when user creates article with tags that are m2m
objects will cause very strange exception KeyError 'published_at', where
published_at is a column on Article model that should be set to null in my
case (as default value):
[2015-12-14 13:55:14] 'published_at'
Traceback (most recent call last):
File "/home/user/project/proj_x/server.py", line 44, in _call_view
response = view(**data)
File "/home/user/project/proj_x/decorators.py", line 58, in inner
return fun(*args, **kwargs)
File "/home/user/project/proj_x/decorators.py", line 69, in inner
return fun(*args, **kwargs)
File "/home/user/project/proj_x/decorators.py", line 137, in
inner_inner
return f(*args, **kwargs)
File "/home/user/project/proj_x/versions/v0_0/article_views.py", line
548, in create_article
resp = cndcore.iapi.article.create_article(user=request.user,
**form_data)
File "/home/user/Projects/cndcore/cndcore/iapi/article.py", line 223,
in create_article
return obj.to_response()
File
"/home/user/Projects/cndcore/cndcore/models/mixin_exportable.py", line 298,
in to_response
setattr(obj, name, self.get_value(expand, name, cols))
File
"/home/user/Projects/cndcore/cndcore/models/mixin_exportable.py", line 335,
in get_value
getattr(self, name), expand=expand)
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py",
line 237, in __get__
return self.impl.get(instance_state(instance), dict_)
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py",
line 583, in get
value = self.callable_(state, passive)
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/strategies.py",
line 532, in _load_for_state
return self._emit_lazyload(session, state, ident_key, passive)
File "<string>", line 1, in <lambda>
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/strategies.py",
line 602, in _emit_lazyload
result = q.all()
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/query.py",
line 2423, in all
return list(self)
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/query.py",
line 2570, in __iter__
self.session._autoflush()
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py",
line 1293, in _autoflush
self.flush()
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py",
line 2015, in flush
self._flush(objects)
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py",
line 2133, in _flush
transaction.rollback(_capture_exception=True)
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py",
line 60, in __exit__
compat.reraise(exc_type, exc_value, exc_tb)
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/session.py",
line 2097, in _flush
flush_context.execute()
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py",
line 373, in execute
rec.execute(self)
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py",
line 532, in execute
uow
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/persistence.py",
line 170, in save_obj
mapper, table, update)
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/persistence.py",
line 635, in _emit_update_statements
lambda rec: (
File
"/home/user/Envs/api/local/lib/python2.7/site-packages/sqlalchemy/orm/persistence.py",
line 454, in _collect_update_commands
value = state_dict[propkey]
KeyError: 'published_at'
I found two different solutions to this problem.
First one is to wrap more code with no_autoflush:
def _attach_new_tags(self, tags, article):
if not tags:
return
tman = tag_manager.TagManager()
with models.Session.no_autoflush:
for tag in tags:
try:
models.Session.query(models.ArticleTag).filter(
(models.ArticleTag.article_uid == article.uid) &
(models.ArticleTag.tag_uid == tag.uid)).one()
except sqlalchemy.orm.exc.NoResultFound:
tag = tman.get_tag_by_uid(tag.uid)
obj = models.ArticleTag(tag=tag)
article.article_tags.append(obj)
However what bothers me in it, next time something similar will happen,
sqlalchemy will throw exception that will be again obfuscating real problem
(Query-invoked autoflush).
Second solution is small patch for sqlalchemy itself:
diff --git a/lib/sqlalchemy/orm/persistence.py
b/lib/sqlalchemy/orm/persistence.py
index 768c114..3808ab4 100644
--- a/lib/sqlalchemy/orm/persistence.py
+++ b/lib/sqlalchemy/orm/persistence.py
@@ -452,7 +452,7 @@ def _collect_update_commands(
params = {}
for propkey in set(propkey_to_col).intersection(
state.committed_state):
- value = state_dict[propkey]
+ value = state_dict.get(propkey)
col = propkey_to_col[propkey]
if isinstance(value, sql.ClauseElement):
--
2.5.0
Or perhaps better:
diff --git a/lib/sqlalchemy/orm/persistence.py
b/lib/sqlalchemy/orm/persistence.py
index 768c114..ce6a1f7 100644
--- a/lib/sqlalchemy/orm/persistence.py
+++ b/lib/sqlalchemy/orm/persistence.py
@@ -452,6 +452,9 @@ def _collect_update_commands(
params = {}
for propkey in set(propkey_to_col).intersection(
state.committed_state):
+ if propkey not in state_dict:
+ continue
+
value = state_dict[propkey]
col = propkey_to_col[propkey]
--
2.5.0
I checked both changes vs noserun.py on sqlalchmy 1.1b1, they do not cause
any errors.
However what makes me uneasy is that not everything is very clear to me
what is happening under the hood. Seems like ORM things that state_dict
should have published_at because it was set/modified while it's not the
case. Fact is that if I specify published_at to be some value in my test,
another field will be reported as KeyError and so on, and so on, till I
have all fields set.
Please let me know if my patch and my findings are any value for you.
--
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.