I am currently working on upgrading a very large API project from smorest 0.21 (and matching Flask 1, SQLAlchemy 2 etc) to the current smorest with the following relevant package versions:
apispec==6.3.0
Flask==2.2.5
flask-marshmallow==0.15.0
flask-smorest==0.42.0
Flask-SQLAlchemy==3.0.5
marshmallow==3.20.1
marshmallow-sqlalchemy==0.29.0
SQLAlchemy==2.0.19
webargs==8.3.0
Werkzeug==2.3.6
My schemas look like this:
class CountrySchema(BaseSchema):
"""" Country """
class Meta(BaseSchema.Meta): # pylint: disable = too-few-public-methods
"""Meta"""
model = Country
fields = ["id", "isdeleted", "iso", "title", "continent", "eumember", "winegrower",
"salestax", "creationdate", "lastupdate"]
dump_only = ["id", "isdeleted", "creationdate", "lastupdate"]
My old baseschema was inheriting from ModelSchema
class BaseSchema(ModelSchema):
which I had to change to SQLAlchemySchema - if I'm not mistaken
With 0.21 and the old schema (based on ModelSchema) everything worked fine.
After the upgrade, the POST operations fail (GET works) with the following error:
unit_tests/test_0021_api_full_country.py:12 (test_api_country_full)
self = <sqlalchemy.orm.session.Session object at 0x10df991b0>
instance = {'continent': 'string', 'eumember': True, 'iso': 'stringimbz', 'title': 'Test Country imbz', ...}
_warn = True
def add(self, instance: object, _warn: bool = True) -> None:
"""Place an object into this :class:`_orm.Session`.
Objects that are in the :term:`transient` state when passed to the
:meth:`_orm.Session.add` method will move to the
:term:`pending` state, until the next flush, at which point they
will move to the :term:`persistent` state.
Objects that are in the :term:`detached` state when passed to the
:meth:`_orm.Session.add` method will move to the :term:`persistent`
state directly.
If the transaction used by the :class:`_orm.Session` is rolled back,
objects which were transient when they were passed to
:meth:`_orm.Session.add` will be moved back to the
:term:`transient` state, and will no longer be present within this
:class:`_orm.Session`.
.. seealso::
:meth:`_orm.Session.add_all`
:ref:`session_adding` - at :ref:`session_basics`
"""
if _warn and self._warn_on_events:
self._flush_warning("Session.add()")
try:
> state = attributes.instance_state(instance)
E AttributeError: 'dict' object has no attribute '_sa_instance_state'
../venv/lib/python3.10/site-packages/sqlalchemy/orm/session.py:3335: AttributeError
The above exception was the direct cause of the following exception:
client = <FlaskClient <Flask 'Core API'>>
@pytest.mark.options(debug=True)
def test_api_country_full(client):
""" Full test of country """
randomtitle = random_string(4)
# Create country
> response_country_post = client.post(BASEURL + "/i18n/country",
headers=PYTEST_CLIENT_DEFAULTHEADER,
data=json.dumps(
{
"continent": "string",
"eumember": True,
"iso": "string" + randomtitle,
"winegrower": True,
"title": "Test Country " + randomtitle
}
))
unit_tests/test_0021_api_full_country.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../venv/lib/python3.10/site-packages/werkzeug/test.py:1247: in post
return self.open(*args, **kw)
../venv/lib/python3.10/site-packages/flask/testing.py:238: in open
response = super().open(
../venv/lib/python3.10/site-packages/werkzeug/test.py:1196: in open
response = self.run_wsgi_app(request.environ, buffered=buffered)
../venv/lib/python3.10/site-packages/werkzeug/test.py:1068: in run_wsgi_app
rv = run_wsgi_app(self.application, environ, buffered=buffered)
../venv/lib/python3.10/site-packages/werkzeug/test.py:1344: in run_wsgi_app
app_rv = app(environ, start_response)
../venv/lib/python3.10/site-packages/flask/app.py:2552: in __call__
return self.wsgi_app(environ, start_response)
../venv/lib/python3.10/site-packages/werkzeug/middleware/proxy_fix.py:182: in __call__
return self.app(environ, start_response)
../venv/lib/python3.10/site-packages/flask/app.py:2532: in wsgi_app
response = self.handle_exception(e)
../venv/lib/python3.10/site-packages/flask/app.py:2529: in wsgi_app
response = self.full_dispatch_request()
../venv/lib/python3.10/site-packages/flask/app.py:1825: in full_dispatch_request
rv = self.handle_user_exception(e)
../venv/lib/python3.10/site-packages/flask/app.py:1823: in full_dispatch_request
rv = self.dispatch_request()
../venv/lib/python3.10/site-packages/flask/app.py:1799: in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
../venv/lib/python3.10/site-packages/flask/views.py:107: in view
return current_app.ensure_sync(self.dispatch_request)(**kwargs)
../venv/lib/python3.10/site-packages/flask/views.py:188: in dispatch_request
return current_app.ensure_sync(meth)(**kwargs)
../venv/lib/python3.10/site-packages/webargs/core.py:649: in wrapper
return func(*args, **kwargs)
../venv/lib/python3.10/site-packages/flask_smorest/arguments.py:82: in wrapper
return func(*f_args, **f_kwargs)
../venv/lib/python3.10/site-packages/flask_smorest/response.py:89: in wrapper
func(*args, **kwargs)
../controller/i18n.py:78: in post
DB.session.add(item)
../venv/lib/python3.10/site-packages/sqlalchemy/orm/scoping.py:375: in add
return self._proxied.add(instance, _warn=_warn)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <sqlalchemy.orm.session.Session object at 0x10df991b0>
instance = {'continent': 'string', 'eumember': True, 'iso': 'stringimbz', 'title': 'Test Country imbz', ...}
_warn = True
def add(self, instance: object, _warn: bool = True) -> None:
"""Place an object into this :class:`_orm.Session`.
Objects that are in the :term:`transient` state when passed to the
:meth:`_orm.Session.add` method will move to the
:term:`pending` state, until the next flush, at which point they
will move to the :term:`persistent` state.
Objects that are in the :term:`detached` state when passed to the
:meth:`_orm.Session.add` method will move to the :term:`persistent`
state directly.
If the transaction used by the :class:`_orm.Session` is rolled back,
objects which were transient when they were passed to
:meth:`_orm.Session.add` will be moved back to the
:term:`transient` state, and will no longer be present within this
:class:`_orm.Session`.
.. seealso::
:meth:`_orm.Session.add_all`
:ref:`session_adding` - at :ref:`session_basics`
"""
if _warn and self._warn_on_events:
self._flush_warning("Session.add()")
try:
state = attributes.instance_state(instance)
except exc.NO_STATE as err:
> raise exc.UnmappedInstanceError(instance) from err
E sqlalchemy.orm.exc.UnmappedInstanceError: Class 'builtins.dict' is not mapped
../venv/lib/python3.10/site-packages/sqlalchemy/orm/session.py:3337: UnmappedInstanceError
What I observe:
The type of item fed to post() is <dict>. In the old software it was: <class 'models.country.Country'>
and I think this is the reason why the add() fails.
What do I have to add, to (automatically) make "Country" objects while deserializing?
Can I keep my schemas as they are, given that my baseschema inherits the SQLAlchemySchema?
Hi!
I am currently working on upgrading a very large API project from smorest 0.21 (and matching Flask 1, SQLAlchemy 2 etc) to the current smorest with the following relevant package versions:
My schemas look like this:
My old baseschema was inheriting from ModelSchema
which I had to change to SQLAlchemySchema - if I'm not mistaken
My controllers are like this:
With 0.21 and the old schema (based on ModelSchema) everything worked fine. After the upgrade, the POST operations fail (GET works) with the following error:
What I observe:
The type of
item
fed to post() is<dict>
. In the old software it was:<class 'models.country.Country'>
and I think this is the reason why the add() fails.What do I have to add, to (automatically) make "Country" objects while deserializing?
Can I keep my schemas as they are, given that my baseschema inherits the SQLAlchemySchema?