marshmallow-sqlalchemy
marshmallow-sqlalchemy copied to clipboard
Loading nested object with association_proxy occasionally works
I have the following code that defines some geographic entities, City
, State
and Country
. City
is foreign keyed to a State
, and a State
is foreign keyed to a Country
. In the City
object, we have a relationship to a Country
that is an association_proxy
which traverses the State
relationship.
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker, relationship, backref
from sqlalchemy.ext.associationproxy import association_proxy
engine = sa.create_engine("sqlite:///:memory:")
session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
class Country(Base):
__tablename__ = "Country"
__table_args__ = ({"extend_existing": True},)
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String(128), index=True, unique=True)
code = sa.Column(sa.String(4), index=True, unique=True)
class State(Base):
__tablename__ = "State"
__table_args__ = (sa.UniqueConstraint("name", "country_id", name="__uix_state_country"),)
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String(128), index=True)
country_id = sa.Column(sa.Integer, sa.ForeignKey("Country.id"), index=True)
country = relationship(Country, backref="states")
class City(Base):
__tablename__ = "City"
__table_args__ = (sa.UniqueConstraint("name", "state_id", name="__uix_city_state"),)
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String(128), index=True)
state_id = sa.Column(sa.Integer, sa.ForeignKey("State.id"), index=True)
state = relationship(State, backref="cities")
country = association_proxy("state", "country")
Base.metadata.create_all(engine)
I have the following schemas defined.
from marshmallow import fields
from marshmallow_sqlalchemy import ModelSchema
class CitySchema(ModelSchema):
class Meta:
model = City
sqla_session = session
exclude = ("city_region",)
state = fields.Nested("StateSchema", many=False, only=("id", "name"))
country = fields.Nested("CountrySchema", many=False, only=("id", "name"))
class StateSchema(ModelSchema):
class Meta:
model = State
sqla_session = session
exclude = ("state_region",)
cities = fields.Nested(CitySchema, only=("id", "name"), many=True)
country = fields.Nested("CountrySchema", many=False, only=("id", "name"))
class CountrySchema(ModelSchema):
class Meta:
model = Country
sqla_session = session
exclude = ("country_region",)
states = fields.Nested(StateSchema, only=("id", "name"), many=True)
Now when I attempt to load a City
object using the following, there's a small chance that it succeeds but most of the time it fails.
data = {
"name": "Cambridge", # new city
"state": {"name": "Massachusetts"}, # A new state!
"country": {"name": "United States"}, # A new country!
}
city_schema = CitySchema()
city = City()
# Sometimes this line works, but most of the time it fails
city_schema.load(data, instance=city, partial=True)
I'm getting a TypeError exception. TypeError: __init__() takes 1 positional argument but 2 were given
. The full stack trace is as follows:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-17-a0d3fd8aacc8> in <module>
7 city_schema = CitySchema()
8 city = City()
----> 9 city_schema.load(data, instance=city, partial=True)
~/anaconda3_501/lib/python3.6/site-packages/marshmallow_sqlalchemy/schema.py in load(self, data, session, instance, transient, *args, **kwargs)
214 self.instance = instance or self.instance
215 try:
--> 216 return super(ModelSchema, self).load(data, *args, **kwargs)
217 finally:
218 self.instance = None
~/anaconda3_501/lib/python3.6/site-packages/marshmallow/schema.py in load(self, data, many, partial)
586 .. versionadded:: 1.0.0
587 """
--> 588 result, errors = self._do_load(data, many, partial=partial, postprocess=True)
589 return UnmarshalResult(data=result, errors=errors)
590
~/anaconda3_501/lib/python3.6/site-packages/marshmallow/schema.py in _do_load(self, data, many, partial, postprocess)
693 result,
694 many,
--> 695 original_data=data)
696 except ValidationError as err:
697 errors = err.normalized_messages()
~/anaconda3_501/lib/python3.6/site-packages/marshmallow/schema.py in _invoke_load_processors(self, tag_name, data, many, original_data)
858 data=data, many=many, original_data=original_data)
859 data = self._invoke_processors(tag_name, pass_many=False,
--> 860 data=data, many=many, original_data=original_data)
861 return data
862
~/anaconda3_501/lib/python3.6/site-packages/marshmallow/schema.py in _invoke_processors(self, tag_name, pass_many, data, many, original_data)
961 data = utils.if_none(processor(data, original_data), data)
962 else:
--> 963 data = utils.if_none(processor(data), data)
964 return data
965
~/anaconda3_501/lib/python3.6/site-packages/marshmallow_sqlalchemy/schema.py in make_instance(self, data, **kwargs)
193 if instance is not None:
194 for key, value in iteritems(data):
--> 195 setattr(instance, key, value)
196 return instance
197 kwargs, association_attrs = self._split_model_kwargs_association(data)
~/anaconda3_501/lib/python3.6/site-packages/sqlalchemy/ext/associationproxy.py in __set__(self, obj, values)
309 target = getattr(obj, self.target_collection)
310 if target is None:
--> 311 setattr(obj, self.target_collection, creator(values))
312 else:
313 self._scalar_set(target, values)
TypeError: __init__() takes 1 positional argument but 2 were given
A couple of questions.
- Is loading an
association_proxy
supported?- Is there a bug in my code?
- Is there an alternative that I can try?
- Why does this occasionally work? This bit has me really puzzled.
A link to the Jupyter Notebook with this code can be found at this link.
Thanks!
It looks like this was addressed in a StackOverflow question: https://stackoverflow.com/questions/41222412/sqlalchemy-init-takes-1-positional-argument-but-2-were-given-many-to-man