On 12/14/2015 10:47 AM, Drachenfels wrote:
> 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'


there should not be a keyerror here but unfortunately there is not
enough clear information here in order for us to reproduce the issue.
 Can you please produce a simple test case following the guidelines at
http://stackoverflow.com/help/mcve (which includes but is not limited
to: single file, no imports outside of SQLA, no extraneous methods or
code, sample data is included, etc) and post as a bug report at
https://bitbucket.org/zzzeek/sqlalchemy/issues ?  thanks.





> 
> 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]
> <mailto:[email protected]>.
> To post to this group, send email to [email protected]
> <mailto:[email protected]>.
> Visit this group at https://groups.google.com/group/sqlalchemy.
> For more options, visit https://groups.google.com/d/optout.

-- 
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.

Reply via email to