Flask-restful: Blueprint-related error in non-blueprint app, in test suite

Created on 4 Dec 2014  路  8Comments  路  Source: flask-restful/flask-restful

Code as follows (ignore random variable names):

from colombia.views import CatAPI
api.add_resource(CatAPI, "/cats/<int:cat_id>")
api.init_app(app)

gives me:

Traceback (most recent call last):
  File "/Users/makmana/colombia/env/lib/python3.4/site-packages/flask_restful/__init__.py", line 112, in init_app
    app.record(self._deferred_blueprint_init)
AttributeError: 'Flask' object has no attribute 'record'

When I check the code in this repo, it looks like that exception should have been catched:

https://github.com/flask-restful/flask-restful/blob/51648c8bd7249478fa29c91ef5e33ff81a9e8fb0/flask_restful/__init__.py#L115

If I initialize the app as:

api.blueprint = app

instead of calling init_app, it seems to work fine. At first I thought maybe the code I have is old, but then I checked the versions and reinstalled packages:

Flask-RESTful==0.3.0
Flask==0.10.1

There's definitely something funky going on here - especially since I'm not using blueprints - but I'm not quite sure what. It also seems to happen only when I trigger it through test cases through flask-testing. Hope this helps.

Most helpful comment

For what it's worth, here is how I got this working inside of an app factory and bypassing init_app() issue. I basically monkey patch the api.app attribute with the current app. When I patch it the api instance isn't attached to any flask instance yet.

Inside extensions.py

from flask_restful import Api
api = Api()

Inside app.py
```python
from extensions import api
from settings import ProductionConfig

def create_app(config=ProductionConfig):
""" flask application factory """
app = Flask(__name__)

# attach the api routes

register_api(app)

def register_api(app):
""" attaches api routes to the flask app """
api.app = app
api.add_resource(FirstResource, '/api/first_resource')
api.add_resource(SecondResource, '/api/second_resource')
api.add_resource(ThirdResource, '/api/third_resource')
"""

All 8 comments

Since you only see the behavior when testing, It may be helpful if you can provide an example of a test case where this exception surfaces.

Actually, I think this error was a red herring - it was being raised while another exception was being raised - probably because the exception handling code expected init_app to have run completely, but it hadn't yet. The other exception was related to duplicate routes (AssertionError: View function mapping is overwriting an existing endpoint function) - turns out that the flask-restful Api() was a global, and when the test case was being torn down, the state in Api() remained the same, so when it was being set up again in a new test case, the route was already there. The solution was to create a class similar to this (untested code):

class ext(object):

    db = SQLAlchemy()
    api = restful.Api()

    @classmethod
    def reset(cls):
        cls.db = SQLAlchemy()
        cls.api = restful.Api()

And call reset() on teardown etc. An alternative would be to associate the extensions with the current app object if you need to avoid the convenience of global state completely. There might be a neater solution here to do with the flask application context - but that's a story for another github issue.

Thanks!

I'm experiencing the same problem. It's telling me that I'm overwriting an existing endpoint on my second test. I'm not sure how to implement the workaround mentioned above. My current tearDown method is:

    def tearDown(self):
        super(MyAppTestCase, self).tearDown()
        db.drop_all()
        self.app_context.pop()

The answer was simple. I moved api = Api(...) into my create_app factory instead of keeping it as a global.

I realize that this issue was closed but I'm running into this issue. Basically the init_app() doesn't work in testing and not using it isn't a solution, it's a work around. The fact that it doesn't work in testing makes me think that it won't work in production either and I don't want to be dealing with bugs.

Does anyone have insight on how to get init_app to work?

For what it's worth, here is how I got this working inside of an app factory and bypassing init_app() issue. I basically monkey patch the api.app attribute with the current app. When I patch it the api instance isn't attached to any flask instance yet.

Inside extensions.py

from flask_restful import Api
api = Api()

Inside app.py
```python
from extensions import api
from settings import ProductionConfig

def create_app(config=ProductionConfig):
""" flask application factory """
app = Flask(__name__)

# attach the api routes

register_api(app)

def register_api(app):
""" attaches api routes to the flask app """
api.app = app
api.add_resource(FirstResource, '/api/first_resource')
api.add_resource(SecondResource, '/api/second_resource')
api.add_resource(ThirdResource, '/api/third_resource')
"""

Although the issue was closed, I also run into the issue.Here is how I deal with it in my unit test:

from app import create_app, db, api, admin


class TestURLS(unittest.TestCase):
    def setUp(self):
        # Bug workarounds
        admin._views = []
        api.resources = []

        app = create_app('test')
        self.client = app.test_client()

        # Bug workarounds
        db.app = app

        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()

As it shown above, api.resources=[] is the point.And if you use flask_admin, you shall run into the same issue as well as flask_restful.Of course, the similar solution is admin._views=[].

I ran into this issue as well, and solved it by introducing a static variable in my create_app function:

def create_app():
# ...
    if not (create_app.resources_added):
        # register api resources
        for path, resourceObj in resources.resources_dict.items():
            api.add_resource(resourceObj, path)
        create_app.resources_added = True

    # initialize api
    api.init_app(app)

    return app

# initialize creat_app.resources_added static variable
create_app.resources_added = False

Below is the entire file (app/__init__.py) for reference:

from flask import Flask
from config import app_config
from flask_restful import Api
from flask_cors import CORS
from app.database import Database

api = Api()
cors = CORS()
db = Database()


def create_app(config_name: str) -> Flask:
    # imports
    from app import resources

    # configure app
    app = Flask(__name__)
    app.config.from_object(app_config[config_name])

    with app.app_context():
        # initialize database
        db.init_app(app)

        # register cors
        cors.init_app(app, resources={r"*": {"origins": "*"}})

        # FlaskRestful does not integrate nicely with Flask app_context functionality.
        # Since its useful for testing purposes to be able to create multiple apps within one testing suite
        # we are using a static variable to check whether the resources have been added to the API in this process.
        # This stops attempting to register the resources twice with the API object, which causes an error.
        if not (create_app.resources_added):
            # register api resources
            for path, resourceObj in resources.resources_dict.items():
                api.add_resource(resourceObj, path)
            create_app.resources_added = True

        # initialize api
        api.init_app(app)

    return app


# initialize creat_app.resources_added static variable
create_app.resources_added = False
Was this page helpful?
0 / 5 - 0 ratings

Related issues

Shanks512 picture Shanks512  路  8Comments

svvitale picture svvitale  路  8Comments

lipis picture lipis  路  3Comments

lra picture lra  路  4Comments

SubhabrataBanerji picture SubhabrataBanerji  路  5Comments