Common SQLAlchemy/marshmallow problems
Problem: Object initialization order issue.
In python and still more to in flask, the order of import and instantiation of python files and classes matters.
To make it consistent, we need to be very careful.
We can have exception at the startup of the application if we define schemas in the same file as models:
flask --app flask_example run --port 5001 --debug Traceback (most recent call last): ...... File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/flask/cli.py", line 897, in run_command app = info.load_app() File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/flask/cli.py", line 308, in load_app app = locate_app(import_name, name) File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/flask/cli.py", line 218, in locate_app __import__(module_name) File "/home/david/python-workspace/flask_example/flask_example.py", line 5, in <module> from PersonRepository import PersonRepository File "/home/david/python-workspace/flask_example/PersonRepository.py", line 1, in <module> from models.Person import Person File "/home/david/python-workspace/flask_example/models/__init__.py", line 1, in <module> import models.Address File "/home/david/python-workspace/flask_example/models/Address.py", line 30, in <module> class AddressSchema(SQLAlchemyAutoSchema): File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/marshmallow/schema.py", line 121, in __new__ klass._declared_fields = mcs.get_declared_fields( File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/marshmallow_sqlalchemy/schema.py", line 91, in get_declared_fields fields.update(mcs.get_declared_sqla_fields(fields, converter, opts, dict_cls)) File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/marshmallow_sqlalchemy/schema.py", line 130, in get_declared_sqla_fields converter.fields_for_model( File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/marshmallow_sqlalchemy/convert.py", line 136, in fields_for_model for prop in model.__mapper__.attrs: File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/sqlalchemy/util/langhelpers.py", line 1254, in __get__ obj.__dict__[self.__name__] = result = self.fget(obj) File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/sqlalchemy/orm/mapper.py", line 3017, in attrs self._check_configure() File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/sqlalchemy/orm/mapper.py", line 2374, in _check_configure _configure_registries({self.registry}, cascade=True) File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/sqlalchemy/orm/mapper.py", line 4186, in _configure_registries _do_configure_registries(registries, cascade) File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/sqlalchemy/orm/mapper.py", line 4228, in _do_configure_registries mapper._post_configure_properties() File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/sqlalchemy/orm/mapper.py", line 2391, in _post_configure_properties prop.init() File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/sqlalchemy/orm/interfaces.py", line 544, in init self.do_init() File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/sqlalchemy/orm/relationships.py", line 1632, in do_init self._setup_entity() File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/sqlalchemy/orm/relationships.py", line 1851, in _setup_entity self._clsregistry_resolve_name(argument)(), File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/sqlalchemy/orm/clsregistry.py", line 519, in _resolve_name self._raise_for_name(name, err) File "/home/david/python-workspace/flask_example/venv/lib/python3.9/site-packages/sqlalchemy/orm/clsregistry.py", line 500, in _raise_for_name raise exc.InvalidRequestError( sqlalchemy.exc.InvalidRequestError: When initializing mapper Mapper[Address(address)], expression 'Person' failed to locate a name ('Person'). If this is a class name, consider adding this relationship() to the <class 'models.Address.Address'> class after both dependent classes have been defined. |
Solution:
Schemas have to be defined in their own files and these have to be loaded after all models and processing of SQLAlchemy.
Example:
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema from models.Address import Address from models.Person import Person class PersonSchema(SQLAlchemyAutoSchema): class Meta: model = Person include_relationships = True load_instance = True class AddressSchema(SQLAlchemyAutoSchema): class Meta: model = Address include_fk = True load_instance = True |
Problem: Relationships are not serialized with marshmallow schema
The result may look like, instead of having (object)s, we have digits :
curl -X GET localhost:5001/api/person/5?fetch_address { "addresses": [ 1 ], "firstname": "boyoDoe", "id": 1, "lastname": "David" } |
Solution: In the schema definition, we need to add the relationship as a Nested
field (with the same name).
Example:
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema from marshmallow_sqlalchemy import fields from models.Address import Address from models.Person import Person class PersonSchema(SQLAlchemyAutoSchema): class Meta: model = Person include_relationships = True load_instance = True # addresses = fields.Nested(AddressSchema, ) addresses = fields.Nested(AddressSchema, many=True) |
Important:
If the relationship has many instances(like in this example), we need to specify the many
attribute to true
.