Issue 1:
Migrated issue, originally created by Pablo Marti (@pmarti)

Hi there,

I have a User model class with a profile attribute marked as mutable

class User(db.Model):
...
profile = db.Column(MutableDict.as_mutable(JSONB()))

This works perfectly under python2.7, but now when I issue a query using pypy 2.6 I get this:

Traceback (most recent call last):
File "/Users/pablo/.virtualenvs/drop/src/flask/flask/app.py", line 1537, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/pablo/.virtualenvs/drop/src/flask/flask/app.py", line 1523, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/Users/pablo/Development/drop-server/drop/api/decorators.py", line 73, in decorated_function
.get(user_id)
File "/Users/pablo/.virtualenvs/drop/site-packages/sqlalchemy/orm/query.py", line 818, in get
return self._get_impl(ident, loading.load_on_ident)
File "/Users/pablo/.virtualenvs/drop/site-packages/sqlalchemy/orm/query.py", line 851, in _get_impl
return fallback_fn(self, key)
File "/Users/pablo/.virtualenvs/drop/site-packages/sqlalchemy/orm/loading.py", line 217, in load_on_ident
return q.one()
File "/Users/pablo/.virtualenvs/drop/site-packages/sqlalchemy/orm/query.py", line 2472, in one
ret = list(self)
File "/Users/pablo/.virtualenvs/drop/site-packages/sqlalchemy/orm/loading.py", line 84, in instances
util.raise_from_cause(err)
File "/Users/pablo/.virtualenvs/drop/site-packages/sqlalchemy/util/compat.py", line 199, in raise_from_cause
reraise(type(exception), exception, tb=exc_tb)
File "/Users/pablo/.virtualenvs/drop/site-packages/sqlalchemy/orm/loading.py", line 69, in instances
rows = [proc(row) for row in fetch]
File "/Users/pablo/.virtualenvs/drop/site-packages/sqlalchemy/orm/loading.py", line 430, in _instance
state.manager.dispatch.load(state, context)
File "/Users/pablo/.virtualenvs/drop/site-packages/sqlalchemy/event/attr.py", line 258, in __call__
fn(*args, **kw)
File "/Users/pablo/.virtualenvs/drop/site-packages/sqlalchemy/ext/mutable.py", line 428, in load
val = cls.coerce(key, val)
File "/Users/pablo/.virtualenvs/drop/site-packages/sqlalchemy/ext/mutable.py", line 638, in coerce
return Mutable.coerce(key, value)
File "/Users/pablo/.virtualenvs/drop/site-packages/sqlalchemy/ext/mutable.py", line 403, in coerce
raise ValueError(msg % (key, type(value)))
ValueError: Attribute 'profile' does not accept objects of type <type 'list'>

Dependencies:

SQLAlchemy==1.0.4

psycopg2cffi==2.7.0

Issue 2:
Migrated issue, originally created by Raphaël Slinckx

Here is a test case yielding different results between sqlalchemy 0.9.9 and 1.0+

import json

from sqlalchemy import create_engine, Column
from sqlalchemy.types import TypeDecorator, UnicodeText, Integer
from sqlalchemy.ext.mutable import MutableDict
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session

class JSONDictType(TypeDecorator):
impl = UnicodeText

def process_bind_param(self, value, dialect):
if value is not None:
value = unicode(json.dumps(value))
return value

def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value

def JSON():
return MutableDict.as_mutable(JSONDictType)

Base = declarative_base()

class MyDataClass(Base):
__tablename__ = 'my_data'

id = Column(Integer, primary_key=True)
data = Column(JSON(), default={}, nullable=False)

session = Session(create_engine("sqlite:///:memory:", echo=True))
Base.metadata.create_all(session.bind)

m1 = MyDataClass()
session.add(m1)
session.flush() # Note: without this, the m1.data is None on sqlalchemy 1.0
m1.data["test"] = 1
session.commit()

m2 = MyDataClass()
session.add(m2)
session.commit()

assert m1.data == {'test': 1}
assert m2.data == {}
# With sqlalchemy 1.0 this results in
# m1.data == {}
# m2.data == {'test': 1}

As you can see the default={} seemed to work previously (also the m1.data['test'] = 1 worked without the session.flush() just above)

With the latest version, the dict provided as default seems to be used and re-used resulting in a process-wide globally shared default dict which can lead to catastrophic leaks in a webapp for example. Also the object needs to be flushed before m1.data is populated.

Maybe this usage was not expected or even discouraged, but I didn't find anything in the docs about this. The closest thing I identified is the change discussed here: http://docs.sqlalchemy.org/en/latest/changelog/migration_10.html#changes-to-attribute-events-and-other-operations-regarding-attributes-that-have-no-pre-existing-value and possibly the new default handling.

What is the expected behavior as of 1.0 ? And how do I get the old behavior using the latest version ?