How to structure Flask API source code for testing? - python

I am trying to structure a flask API application to separate routes and create tests for each of these routes in the same application. Ultimately, my objective is to have a gitlab runner file automate this eventually. This is currently what the structure looks like:
my-flask-application:
- venv:
- gitlab-ci.yml
- requirements.txt
- app:
|- app.py
|- routes
|- home.py
|- square.py
|- ...
- test:
|- test_home.py
|- test_square.py
|- ...
Here are the files:
(1) app.py
#app.py
from flask import Flask
from app.routes.home import home_bp
from app.routes.square import square_bp
app = Flask(__name__)
API_PREFIX = "/api/v1"
app.register_blueprint(home_bp, url_prefix=API_PREFIX)
app.register_blueprint(square_bp, url_prefix=API_PREFIX)
if __name__ == '__main__':
app.run(debug=True)
(2) home.py
#home.py
from flask import Blueprint, jsonify
home_bp = Blueprint('home', __name__)
#home_bp.route('/home')
def home():
return jsonify({'message': 'Welcome to the home page!'}), 200
(3) test_home.py
import unittest
from flask import Flask, jsonify
# Need help importing the app or routes
class TestCase(unittest.TestCase):
test_app = Flask(app)
# Need help with an example of a unit test
if __name__ == '__main__':
unittest.main()
Ultimately, I need help (1) figuring out how to import the application into the test file, and (2) learn how to structure the test files effectively so that I can ultimately run it automatically using a runner file (this can come later).
Thanks!

Related

Flask can't route to certain url with blueprint

I am trying to organize my flask project, but there's something wrong.
I have this directory:
app/
__init__.py
views/
pages.py
On my __init__.py file I've imported the pages object and
registered it as a blue print.
This is the code on my pages.py file.
from flask import Blueprint, render_template
pages = Blueprint('pages', __name__) #no prefix
#pages.route('/')
def index():
return '<h1>in index.html</h1>'
#pages.route('/home')
def home():
return '<h1>in home.html</h1>'
If I run the flask app, open the browser, and go to localhost:5000,
I will see the headline 'in index.html'.
But if I go to localhost:5000/home, I will get the message 404 Not Found message.
Does anyone know the reason for this behavior?
Ok, first the folder structure:
app/
__init__.py
main.py
views/
__init__.py
test.py
Contents of main.py:
from flask import Flask
from views.test import pages
app = Flask(__name__)
app.register_blueprint(pages) <-- blueprint registration
Contents of test.py:
from flask import Blueprint
pages = Blueprint('pages', __name__) #no prefix
#pages.route('/')
def index():
return '<h1>in index.html</h1>'
#pages.route('/home')
def home():
return '<h1>in home.html</h1>'
I believe the register_blueprint was the only thing missing.
When stuff like that happen, just turn off everything, reset your computer.
Sometimes the bug is not yours.

Import statement flow

My app layout
my_app
__init__.py
my_app
__init__.py
startup
__init__.py
create_app.py
create_users.py
common_settings.py
core
__init__.py
models.py
views.py
errors
__init__.py
errors.py
Inner __init__.py
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__) # The WSGI compliant web application object
db = SQLAlchemy(app) # Setup Flask-SQLAlchemy
manager = Manager(app) # Setup Flask-Script
from my_app.startup.create_app import create_app
create_app()
create_app.py
def create_app(extra_config_settings={}):
# Load all blueprints with their manager commands, models and views
from my_app import core
return app
core/__init__.py
# from . import views
views.py
from my_app import app, db
from flask import Flask, request
#app.errorhandler(Error)
def handle_invalid_usage(error):
response = jsonify(data=error.to_dict())
response.status_code = error.status_code
return response
I based this code on a tutorial I found. Everything works fine as long as I leave the __init__.py in the core folder empty.
When I don't, I get a NameError: name Error is not defined in my views.py. Error comes from errors.py.
I have three questions:
1) Why does this happen only when I leave the import statement in core/__init__.py.
2)
create_app.py
app.config.from_envvar('ENV_SETTINGS_FILE')
# Other app.config commands here
from my_app import core
return app
What happens when from my_app import core runs?
3) Finally when I return app, is this to ensure that Inner __init__.py file contains the updated app object?
Any explanations would be greatly appreciated!
Trying to build and configure an app with dynamic imports is really bad news and confusing for the reasons you are discovering. A much better and understandable pattern would be a more typical factory:
def create_app():
app = Flask(__name__)
configure_app(app, config)
register_db(app)
add_views(app)
add_manager(app)
return app
if __name__ == '__main__':
app = create_app()
app.run()
But since you're asking, your problem is here:
from my_app import app, db
from flask import Flask, request
#app.errorhandler(Error) # Error is not imported
def handle_invalid_usage(error):
response = jsonify(data=error.to_dict())
response.status_code = error.status_code
return response
The error occurs because views.py is imported, the code compiler comes across Error and cannot find a reference to it.
For your second question: from my_app import core causes core.__init.__ to run, which (presumably) adds the views onto the app object.

Binding routes when using an app factory

How are routes suppose to be handled in flask when using an app factory? Given a package blog that contains everything needed for the app and a management script that creates the app then how are you suppose to reference the app in the routes?
├── blog
├── manage.py
└── blog
├── __init__.py
├── config.py
└── routes.py
manage.py
#!/usr/bin/env python
from flask.ext.script import Manager
manager = Manager(create_app)
# <manager commands>
# ...
# ...
manager.add_option('-c', '--config', dest='config', required=False)
manager.run()
blog/__init__.py
from flask import flask
from .config import Default
def create_app(config=None):
app = Flask(__name__)
app.config.from_object(Default)
if config is not None:
app.config.from_pyfile(config)
return app
blog/routes.py
#app.route() # <-- erm, this won't work now!?
def index():
return "Hello"
The problem is the app is created outside the package so how are the routes suppose to be handled with a setup like this?
Usually I use application factories with blueprint.
blog/__init__.py
from flask import flask
from .config import Default
def create_app(config=None):
app = Flask(__name__)
if config is not None:
app.config.from_pyfile(config)
else:
app.config.from_object(Default)
from blog.routes import route_blueprint
app.register_blueprint(route_blueprint)
return app
blog/routes.py
from flask import Blueprint
route_blueprint = Blueprint('route_blueprint', __name__)
#route_blueprint.route()
def index():
return "Hello"
docs: Application Factories

Flask structure -- can't import application from __init__.py

I'm a beginner with python and I've been having a lot of trouble setting up the structure of my applications using __init__.py even after searching through several tutorials.
At the moment, my current directory structure looks like the following
/parent
/myapp
__init__.py
views.py
/virtualenv
Previously, I had (if it makes any difference)
/parent
/myapp
/bin
/include
/lib
The contents of __init__.py are below:
from flask import Flask
app = Flask(__name__)
and my views.py
from myapp import app
#app.route('/')
def test():
return 'This is a new test'
if __name__ == '__main__':
app.run(debug=True)
If myapp is being initialized with the init file, why can't I call it into the views? I get an error stating 'I cannot import app and I have no module named myapp'. If I remove the init file and copy the contents into the top of the views.py file, everything works fine.
You are using views as the main script. You cannot put a script inside a package; the directory it is in cannot be treated as such. Python adds the parent/myapp directory to the Python path, not the parent path.
Add a separate script at the top level (next to myapp):
from myapp import app
if __name__ == '__main__':
app.run(debug=True)
and add an import to __init__.py to import your views:
from flask import Flask
app = Flask(__name__)
import views
and remove the if __name__ == '__main__': block from views.py.

How does python import order affect names?

I'm doing a flask tutorial (http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world) and I came upon a behaviour that I couldn't explain.
The main directory structure of the tutorial is :
microblog
|
|---- app
| |---- __init__.py
| |---- views.py
|
|---- flask
|---- run.py
and the contents of the files are :
microblog/run.py
#!flask/bin/python
from app import app
app.run(debug=True)
microblog/app/init.py
from flask import Flask
app = Flask(__name__)
from app import views
microblog/app/views.py
from app import app
#app.route("/")
#app.route("/index")
def index():
return "Hello World!"
everything works but if I transpose these two lines:
app = Flask(__name__)
from app import views
in views.py and then I execute run.py I get:
ImportError: cannot import name app
Why does that happen?
Because you're trying to import from the newly created variable app. If you want to import variable modules, then use importlib package:
my_module = importlib.import_module(app, 'view')
Contrary to what the other answer says, this is a circular import problem. app.__init__ tries to import app.views, which tries to import the app.app Flask created in app.__init__. If the Flask is created before app.__init__ imports app.views, app.views finds app.app. If the Flask is created after the import, it isn't there yet when app.views tries to find it.
Circular imports cause all sorts of horrible problems. It might be difficult, but the best way to handle them is generally to reorganize your code so there aren't circular imports.

Categories

Resources