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

`KeyError: <class 'sqlalchemy.ext.automap.test_table'>` Exception when `inlclude_links=True` #24

Closed mosheduminer closed 3 years ago

mosheduminer commented 3 years ago

When creating an APIManager with include_links=True, an exception is thrown trying to access the endpoint:

Reproduction test case:

from flask_restless.serialization import DefaultSerializer
from sqlalchemy.ext.automap import automap_base
from datetime import datetime
import unittest
from flask import Flask
from flask_restless import APIManager
from sqlalchemy import create_engine, MetaData, Table, Column, String
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session

TEST_TABLE = Table(
    "test_table",
    MetaData(),
    Column("id", String, primary_key=True, unique=True),
    Column("title", String),
)

class TestReproduceBug(unittest.TestCase):
    def setUp(self):
        """Creates the Flask application and the APIManager."""
        # create the Flask application
        app = Flask(__name__)
        app.config["DEBUG"] = True
        app.config["TESTING"] = True
        # This is required by `manager.url_for()` in order to construct
        # absolute URLs.
        app.config["SERVER_NAME"] = "localhost"
        app.logger.disabled = True
        self.flaskapp = app

        # create the test client
        self.app = app.test_client()

        engine = create_engine("sqlite:///test.db")
        self.Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
        self.session = scoped_session(self.Session)
        self.Base = declarative_base()
        self.Base.metadata.bind = engine

        # create the APIManager with `include_links=True`
        self.manager = APIManager(
            self.flaskapp, session=self.session, include_links=True
        )

    def test_links_bug(self):
        """
        Reproduce links bug.
        """
        # get the ORM object.
        base = automap_base(metadata=TEST_TABLE.metadata)
        base.prepare()
        database_table_orm_object = base.classes.get(TEST_TABLE.fullname)
        # initialize the default serializer, to compare with.
        default_serializer = DefaultSerializer(
            model=database_table_orm_object,
            type_name=TEST_TABLE.fullname,
            api_manager=self.manager,
            primary_key=TEST_TABLE.primary_key.columns.keys()[0],
        )
        # the instance we will be serializing
        instance = database_table_orm_object(
            id="abc1234",
            title="a title",
        )
        # this throws an exception
        default_serializer.serialize(instance)

if __name__ == "__main__":
    unittest.main()
mrevutskyi commented 3 years ago

I should change it to throw a more meaningful error message, but you have to register all models in the API manager. Otherwise there are no endpoints created, and if there are no endpoints, there is nothing to point the link at

mosheduminer commented 3 years ago

You're right of course.

What actually happened was that I was getting an error about there being no attribute "id" on my entity, and when I tried to reproduce, I didn't realized the error changed.

In short, the APIManager.create_api_blueprint method assumes that the primary key is "id", and needs to be told otherwise explicitly, which I did not do. I think I only got the error when using include_links, because passed primarykey to the serializer, so the line `id = str(getattr(instance, self._primary_key))did not error, but the lineinstance_id = getattr(instance, self._api_manager.primary_key_for(self._model))`.

I think everything is working now. Thank you for your time :+1:.