strawberry-graphql / strawberry-sqlalchemy

A SQLAlchemy Integration for strawberry-graphql
MIT License
91 stars 26 forks source link

Mapping fails after a session.commit(), Instance not bound to session #62

Closed Philaeux closed 11 months ago

Philaeux commented 12 months ago

Describe the Bug

After a session.commit(), SQLAlchemy entities can't be turned into GraphQL object because of access on outside session objects. To explain the bug, consider this mutation:

@strawberry.type
class MutationSoftware:

    @strawberry.mutation
    def upsert_software(self, info, id: str, full_name: str, editor: str, description: str,
                        language: str) -> OBugsError | SoftwareGQL:

        with info.context['session_factory']() as session:
            db_software = session.query(Software).where(Software.id == id).one_or_none()
            if db_software is None:
                db_software = Software(id=id)
                session.add(db_software)
            db_software.full_name = full_name
            db_software.editor = editor
            db_software.description = description
            db_software.language = language
            session.commit()
            return db_software
            # return session.query(Software).where(Software.id == id).one_or_none() (1)

This code gives me this error (doesn't matter if the object is added or just updated, both cases raise the error)

Instance <Software at 0x22cdfd99410> is not bound to a Session; attribute refresh operation cannot proceed (Background on this error at: https://sqlalche.me/e/20/bhk3)

GraphQL request:22:3
21 | fragment SoftwareFragment on Software {
22 |   id
   |   ^
23 |   fullName
Traceback (most recent call last):
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\graphql\execution\execute.py", line 521, in execute_field
    result = resolve_fn(source, info, **args)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\strawberry\schema\schema_converter.py", line 541, in _get_basic_result
    return field.get_result(_source, info=None, args=[], kwargs={})
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\strawberry\field.py", line 212, in get_result
    return self.default_resolver(source, self.python_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\sqlalchemy\orm\attributes.py", line 566, in __get__
    return self.impl.get(state, dict_)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\sqlalchemy\orm\attributes.py", line 1086, in get
    value = self._fire_loader_callables(state, key, passive)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\sqlalchemy\orm\attributes.py", line 1116, in _fire_loader_callables
    return state._load_expired(state, passive)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\sqlalchemy\orm\state.py", line 798, in _load_expired
    self.manager.expired_attribute_loader(self, toload, passive)
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\sqlalchemy\orm\loading.py", line 1558, in load_scalar_attributes
    raise orm_exc.DetachedInstanceError(
sqlalchemy.orm.exc.DetachedInstanceError: Instance <Software at 0x22cdfd99410> is not bound to a Session; attribute refresh operation cannot proceed (Background on this error at: https://sqlalche.me/e/20/bhk3)
Stack (most recent call last):
  File "C:\Program Files\Python311\Lib\threading.py", line 995, in _bootstrap
    self._bootstrap_inner()
  File "C:\Program Files\Python311\Lib\threading.py", line 1038, in _bootstrap_inner
    self.run()
  File "C:\Program Files\Python311\Lib\threading.py", line 975, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Program Files\Python311\Lib\concurrent\futures\thread.py", line 83, in _worker
    work_item.run()
  File "C:\Program Files\Python311\Lib\concurrent\futures\thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\asgiref\sync.py", line 285, in _run_event_loop
    loop.run_until_complete(coro)
  File "C:\Program Files\Python311\Lib\asyncio\base_events.py", line 640, in run_until_complete
    self.run_forever()
  File "C:\Program Files\Python311\Lib\asyncio\windows_events.py", line 321, in run_forever
    super().run_forever()
  File "C:\Program Files\Python311\Lib\asyncio\base_events.py", line 607, in run_forever
    self._run_once()
  File "C:\Program Files\Python311\Lib\asyncio\base_events.py", line 1919, in _run_once
    handle._run()
  File "C:\Program Files\Python311\Lib\asyncio\events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\asgiref\sync.py", line 353, in main_wrap
    result = await self.awaitable(*args, **kwargs)
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\strawberry\flask\views.py", line 158, in dispatch_request
    return await self.run(request=request)
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\strawberry\http\async_base_view.py", line 186, in run
    result = await self.execute_operation(
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\strawberry\http\async_base_view.py", line 118, in execute_operation
    return await self.schema.execute(
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\strawberry\schema\schema.py", line 256, in execute
    result = await execute(
  File "C:\dev\obugs-backend\src\.env\Lib\site-packages\strawberry\schema\execute.py", line 156, in execute
    process_errors(result.errors, execution_context)

However, if I use the return line commented with a (1), there is no problem. Am I missing some config on the sessions or the commit?

System Information

PS C:\dev\obugs-backend\src> .\.env\Scripts\pip freeze
alembic==1.12.0
asgiref==3.7.2
blinker==1.6.2
certifi==2023.7.22
charset-normalizer==3.3.0
click==8.1.7
colorama==0.4.6
Flask==3.0.0
Flask-Cors==4.0.0
Flask-JWT-Extended==4.5.3
Flask-Mail==0.9.1
graphql-core==3.2.3
greenlet==3.0.0
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
Mako==1.2.4
MarkupSafe==2.1.3
psycopg2-binary==2.9.9
PyJWT==2.8.0
python-dateutil==2.8.2
requests==2.31.0
sentinel==1.0.0
six==1.16.0
SQLAlchemy==2.0.21
strawberry-graphql==0.209.5
strawberry-sqlalchemy-mapper==0.3.1
tornado==6.3.3
typing_extensions==4.8.0
urllib3==2.0.6
Werkzeug==3.0.0

Upvote & Fund

Fund with Polar

Philaeux commented 11 months ago

I don't have the problem anymore (seems like I can't reproduce). Might have been linked to the SQLite driver (I switched to a docker postgresql in dev, considering how easy it's to set up an environment similar to prod). Also switched the function to async def for good mesure.