mrevutskyi / flask-restless-ng

A Flask extension for creating simple ReSTful JSON APIs from SQLAlchemy models.
https://flask-restless-ng.readthedocs.io
Other
64 stars 11 forks source link

GET request on API with relationship return error if the relationship is null (even if I allow not null) #12

Closed sharky98 closed 3 years ago

sharky98 commented 3 years ago

Hi,

I have a relationship Many-to-One (entity A can have zero or one B, but B can belongs to many A). I am trying to fetch (GET) item A using the include option in the URL (/api/item_a?include=item_b). When one of the item A doesn't have a relation with an item B, it return a 500 error with the below traceback.

My feeling is that since the item B is empty for one of the item A (a valid database state), Flask-restless-ng tries to request "none" instead of the "itemB" API. Is there somewhere in the code where a conditional should be put to ignore empty relationship?

127.0.0.1 - - [03/Apr/2021 14:18:32] "GET /api/itemA?include=itemB HTTP/1.1" 500 -
Traceback (most recent call last):
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 2464, in __call__
    return self.wsgi_app(environ, start_response)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 2450, in wsgi_app
    response = self.handle_exception(e)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 1867, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\_compat.py", line 39, in reraise
    raise value
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\_compat.py", line 39, in reraise
    raise value
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\dev\deliverables\venv\Lib\site-packages\mimerender.py", line 244, in wrapper
    result = target(*args, **kwargs)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 380, in new_func
    return func(*args, **kw)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 347, in new_func
    return func(*args, **kw)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 233, in new_func
    return func(*args, **kw)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\views.py", line 89, in view
    return self.dispatch_request(*args, **kwargs)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\views.py", line 163, in dispatch_request
    return meth(*args, **kwargs)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 429, in wrapped
    return func(*args, **kw)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\resources.py", line 330, in get
    return self._get_collection()
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\resources.py", line 300, in _get_collection
    return self._get_collection_helper(filters=filters, sort=sort,
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 1610, in _get_collection_helper
    included = self.get_all_inclusions(instances)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 1299, in get_all_inclusions
    return self._serialize_many(to_include)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 1254, in _serialize_many
    _type = collection_name(model)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\helpers.py", line 441, in __call__
    raise ValueError(message)
ValueError: Model <class 'NoneType'> is not known to any APIManager objects; maybe you have not called APIManager.create_api() for this model.

See linked repository in the following comments to see a replicable and functional version of the bug.

Thanks

sharky98 commented 3 years ago

Ok, so after further review of my application, I had to change my database schema so that this use case is not existing anymore. But I think it is still worth checking to accept the use case of including a nullable relationship.

I will try to create a replicable version of the bug.

sharky98 commented 3 years ago

So in previously linked commit, there is a Flask application. The home page shows all three tables with a button that add in Table A a new entry that has a NULL relationship with Item B. After reload the server throw the above traceback.

mrevutskyi commented 3 years ago

Hi, thank you for the detailed report. I think I know what the issue is, will try to push a fix on Monday

mrevutskyi commented 3 years ago

Should be fixed in v1.0.2

sharky98 commented 3 years ago

Hello, thanks for the quick answer and fix for this and my other ticket.

However, I think it is not 100% fixed. It turns out that I have a new use case for this nullable relationship. So, if I include my relationship with /api/a_item?include=b_item all is good. But if I need to go deeper to retrieve relationship of b_item, for instance /api/a_item?include=b_item,b_item.d_item, then if there is a None/Null b_item, it fails with the similar error with the following traceback.

Probably the by-pass for None result is missing when retrieving "child" relationship (like the d_item).

Traceback (most recent call last):
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 2464, in __call__
    return self.wsgi_app(environ, start_response)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 2450, in wsgi_app
    response = self.handle_exception(e)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 1867, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\_compat.py", line 39, in reraise
    raise value
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\_compat.py", line 39, in reraise
    raise value
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\dev\deliverables\venv\Lib\site-packages\mimerender.py", line 244, in wrapper
    result = target(*args, **kwargs)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 380, in new_func
    return func(*args, **kw)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 347, in new_func
    return func(*args, **kw)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 233, in new_func
    return func(*args, **kw)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\views.py", line 89, in view
    return self.dispatch_request(*args, **kwargs)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask\views.py", line 163, in dispatch_request
    return meth(*args, **kwargs)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 429, in wrapped
    return func(*args, **kw)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\resources.py", line 330, in get
    return self._get_collection()
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\resources.py", line 300, in _get_collection
    return self._get_collection_helper(filters=filters, sort=sort,
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 1612, in _get_collection_helper
    included = self.get_all_inclusions(instances)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 1295, in get_all_inclusions
    to_include = set(chain(self.resources_to_include(resource)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 1295, in <genexpr>
    to_include = set(chain(self.resources_to_include(resource)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 1667, in resources_to_include
    return set(chain(resources_from_path(instance, path)
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\views\base.py", line 616, in resources_from_path
    if is_like_list(resource, relation):
  File "C:\dev\deliverables\venv\Lib\site-packages\flask_restless\helpers.py", line 236, in is_like_list
    if relation in instance._sa_class_manager:
AttributeError: 'NoneType' object has no attribute '_sa_class_manager'