AnyBlok / AnyBlok_FastAPI

The power of FastAPI integrated with AnyBlok
Other
0 stars 0 forks source link

Improve dev experince, while pydantic access to relationship ? #1

Open petrus-v opened 3 years ago

petrus-v commented 3 years ago

I've notice this kind of errors sometimes happens while a route return an AnyBlok/SLAlchemy instance with relationship that have to be serialyzed by pydantic (through fastapi).

I belive accessing to related field with lazy load is the matter while we get an unexpected SQLA session/transaction state for some obscure reasons.

backend_1   | INFO:     192.168.96.4:38258 - "GET /api/device/relay/BURNER/state HTTP/1.1" 500 Internal Server Error
backend_1   | ERROR:    Exception in ASGI application
backend_1   | Traceback (most recent call last):
backend_1   |   File "/usr/local/lib/python3.8/site-packages/uvicorn/protocols/http/httptools_impl.py", line 391, in run_asgi
backend_1   |     result = await app(self.scope, self.receive, self.send)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
backend_1   |     return await self.app(scope, receive, send)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/fastapi/applications.py", line 179, in __call__
backend_1   |     await super().__call__(scope, receive, send)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/starlette/applications.py", line 111, in __call__
backend_1   |     await self.middleware_stack(scope, receive, send)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/starlette/middleware/errors.py", line 181, in __call__
backend_1   |     raise exc from None
backend_1   |   File "/usr/local/lib/python3.8/site-packages/starlette/middleware/errors.py", line 159, in __call__
backend_1   |     await self.app(scope, receive, _send)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/starlette/exceptions.py", line 82, in __call__
backend_1   |     raise exc from None
backend_1   |   File "/usr/local/lib/python3.8/site-packages/starlette/exceptions.py", line 71, in __call__
backend_1   |     await self.app(scope, receive, sender)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/starlette/routing.py", line 566, in __call__
backend_1   |     await route.handle(scope, receive, send)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/starlette/routing.py", line 227, in handle
backend_1   |     await self.app(scope, receive, send)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/starlette/routing.py", line 41, in app
backend_1   |     response = await func(request)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/fastapi/routing.py", line 190, in app
backend_1   |     response_data = await serialize_response(
backend_1   |   File "/usr/local/lib/python3.8/site-packages/fastapi/routing.py", line 103, in serialize_response
backend_1   |     value, errors_ = await run_in_threadpool(
backend_1   |   File "/usr/local/lib/python3.8/site-packages/starlette/concurrency.py", line 34, in run_in_threadpool
backend_1   |     return await loop.run_in_executor(None, func, *args)
backend_1   |   File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run
backend_1   |     result = self.fn(*self.args, **self.kwargs)
backend_1   |   File "pydantic/fields.py", line 579, in pydantic.fields.ModelField.validate
backend_1   |   File "pydantic/fields.py", line 738, in pydantic.fields.ModelField._validate_singleton
backend_1   |   File "pydantic/fields.py", line 745, in pydantic.fields.ModelField._apply_validators
backend_1   |   File "pydantic/class_validators.py", line 310, in pydantic.class_validators._generic_validator_basic.lambda12
backend_1   |   File "pydantic/main.py", line 662, in pydantic.main.BaseModel.validate
backend_1   |   File "pydantic/main.py", line 566, in pydantic.main.BaseModel.from_orm
backend_1   |   File "pydantic/main.py", line 960, in pydantic.main.validate_model
backend_1   |   File "pydantic/utils.py", line 412, in pydantic.utils.GetterDict.get
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/ext/hybrid.py", line 899, in __get__
backend_1   |     return self.fget(instance)
backend_1   |   File "/src/anyblok/anyblok/field.py", line 104, in getter_column
backend_1   |     return self.getter_format_value(getattr(model_self, attr_name))
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/attributes.py", line 287, in __get__
backend_1   |     return self.impl.get(instance_state(instance), dict_)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/attributes.py", line 723, in get
backend_1   |     value = self.callable_(state, passive)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/strategies.py", line 726, in _load_for_state
backend_1   |     primary_key_identity = self._get_ident_for_use_get(
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/strategies.py", line 773, in _get_ident_for_use_get
backend_1   |     return [
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/strategies.py", line 774, in <listcomp>
backend_1   |     get_attr(state, dict_, self._equated_columns[pk], passive=passive)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/mapper.py", line 2845, in _get_state_attr_by_column
backend_1   |     return state.manager[prop.key].impl.get(state, dict_, passive=passive)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/attributes.py", line 718, in get
backend_1   |     value = state._load_expired(state, passive)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/state.py", line 652, in _load_expired
backend_1   |     self.manager.deferred_scalar_loader(self, toload)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/loading.py", line 1006, in load_scalar_attributes
backend_1   |     result = load_on_ident(
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/loading.py", line 200, in load_on_ident
backend_1   |     return load_on_pk_identity(
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/loading.py", line 286, in load_on_pk_identity
backend_1   |     return q.one()
backend_1   |   File "/src/anyblok/anyblok/bloks/anyblok_core/core/query.py", line 31, in one
backend_1   |     return super(Query, self).one()
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3490, in one
backend_1   |     ret = self.one_or_none()
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3459, in one_or_none
backend_1   |     ret = list(self)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3535, in __iter__
backend_1   |     return self._execute_and_instances(context)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3556, in _execute_and_instances
backend_1   |     conn = self._get_bind_args(
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3571, in _get_bind_args
backend_1   |     return fn(
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 3550, in _connection_from_session
backend_1   |     conn = self.session.connection(**kw)
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1138, in connection
backend_1   |     return self._connection_for_bind(
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1146, in _connection_for_bind
backend_1   |     return self.transaction._connection_for_bind(
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 409, in _connection_for_bind
backend_1   |     self._assert_active()
backend_1   |   File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 282, in _assert_active
backend_1   |     raise sa_exc.InvalidRequestError(
backend_1   | sqlalchemy.exc.InvalidRequestError: This session is in 'prepared' state; no further SQL can be emitted within this transaction.
petrus-v commented 3 years ago

one solution is to avoid lazy load (using .options(contains_eager(State.device)))

https://docs.sqlalchemy.org/en/13/orm/tutorial.html?highlight=eager#explicit-join-eagerload

petrus-v commented 3 years ago

one solution is to avoid lazy load (using .options(contains_eager(State.device)))

https://docs.sqlalchemy.org/en/13/orm/tutorial.html?highlight=eager#explicit-join-eagerload

not sure it's a workaround

petrus-v commented 3 years ago

or make the work twice by returning schema instance MySchema.from_orm()

doing work twice ?!