Binding routes when using an app factory - python

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

Related

Access app.logger in submodule in Flask application

I am following the official flask tutorial.
I have already set up blueprints and they are working just fine.
I can use app.logger.debug('route "/hello" called') from __init__.py to log something–my issue is that I am struggling to use the app.logger from routes/routes.py though...
I have set up a structure like this:
fzwk_access
├── __init__.py
├── db.py
├── routes
│   ├── routes.py
│   ├── ...
...
So here's the question:
how can I use the app.logger from the file routes.py?
I already tried the following:
use from flask import app which did not work
use from fzwk_access import __init__ which does not work as I don't have app as a global variable there (I guess?)
My __init__.py looks like this:
import os
from flask import Flask
def create_app(test_config=None):
# create and configure the app
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY='dev',
DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite'),
)
if test_config is None:
# load the instance config, if it exists, when not testing
app.config.from_pyfile('config.py', silent=True)
else:
# load the test config if passed in
app.config.from_mapping(test_config)
# ensure the instance folder exists
try:
os.makedirs(app.instance_path)
except OSError:
pass
# a simple page that says hello
#app.route('/hello')
def hello():
app.logger.debug('route "/hello" called')
return 'Hello, World!'
from . import db
db.init_app(app)
from .routes import routes, routes_admin, routes_logs
app.register_blueprint(routes.home_bp)
app.register_blueprint(routes_admin.admin_bp)
# app.register_blueprint(routes_logs.logs_bp)
return app

Flask: get error 404 when I try to access index

when I run the Flask Server with flask run, I get error 404 in the Index Page.
* Serving Flask app "sf.py"
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [18/Feb/2021 10:25:56] "GET / HTTP/1.1" 404 -
Not Found
The requested URL was not found on the server. If you entered the URL
manually please check your spelling and try again.
Project Structure
.
├── app
│   ├── models.py
│   ├── routes.py
│   └── __init__.py
├── clients
│   └── client.py
├── migrations
├── tests
│   ├── conftest.py
│   ├── test_models.py
│   ├── test_client.py
│   └── __init__.py
├── publisher.py
├── manage.py
├── run_client.py
├── requirements.txt
└── sf.py
/sf.py
from app import create_app
create_app()
/app/__init__.py
from flask import Flask
from . models import db
POSTGRES = {
'user': 'sf',
'pw': 'sf',
'db': 'sf',
'host': 'localhost',
'port': '5432',
}
def create_app():
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://%(user)s:%(pw)s#%(host)s:%(port)s/%(db)s' % POSTGRES
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
return app
from app import routes
/app/routes.py
from app import create_app
from app.models import Area, Sensor, Monitoring
from flask import request, jsonify
from flask.views import MethodView
app = create_app()
#app.route('/')
def hello_world():
return 'Hello, World!'
...
I need to use the create_app() because I need an that the /clients/client.py use the app.
/clients/client.py
from paho.mqtt.client import Client
import json
from app import create_app
from app.models import db
from app.models import Monitoring
app = create_app()
class CustomClient(Client):
def add_reading(self, reading):
with app.app_context():
db.session.add(reading)
db.session.commit()
def on_connect(self, client, userdata, flags, rc):
print(
"Connected:",
str(client._host) + ":" + str(client._port)
)
def on_subscribe(self, mqttc, obj, mid, granted_qos):
print(
"Subscribed:",
str(mid), str(granted_qos)
)
def on_message(self, client, userdata, message):
msg = message.payload.decode()
print(message.topic, msg)
data = json.loads(msg)
reading = Monitoring(**data)
self.add_reading(reading)
def run(self):
self.connect("localhost", 1883, 60)
self.subscribe("Main/#", 0)
self.loop_forever()
But in this way I get the 404 error. And I'm not sure that I'm using the app properly. It would be fine to have an app and a db session separate, to test models and client without care the app configuration (probably I need to create a separate config for test?). What I've missed?
You are creating three instances of the Flask() object. One is created in sf.py, the others in routes.py and client.py. The first one is used to serve the site, and so doesn't have your route, because the route is registered with the instance created in routes.py. The 3rd instance, in client.py is independent and isn't further altered, so is not an issue here; more on that below.
Don't create multiple copies, at least not and alter the registrations on one and expect those to be available on the other. Instead, use blueprints to register your views, and then register the blueprint with the Flask() object in your create_app() function. That way you can decouple registration of your routes from creating the Flask() object, and still get your routes registered centrally.
In your routes.py, use:
from app.models import Area, Sensor, Monitoring
from flask import Blueprint, request, jsonify
from flask.views import MethodView
bp = Blueprint('main', __name__)
#bp.route('/')
def hello_world():
return 'Hello, World!'
# ...
and then import that blueprint in create_app():
def create_app():
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://%(user)s:%(pw)s#%(host)s:%(port)s/%(db)s' % POSTGRES
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
from . import routes
app.register_blueprint(routes.bp)
return app
The reason you want to do the import in create_app() is that in most Flask applications you'll also be using one or more Flask extensions that are generally created outside of create_app() so your views can import them. You'd get a circular import if you tried to import one of those objects in your routes module if your routes module was imported into app.py at the top level.
With this change (to using a blueprint), you avoid creating a separate Flask() instance with registrations that the main instance, used for serving your site, won't see. Even your client.py process will be able to access those routes now, should there be a need (e.g. if you need to generate URLs with url_for()).
Here is an example from an in-production Flask project I built for a client recently, the app.py module contains, in part, the following code:
from flask import Flask
from flask_babel import Babel
from flask_marshmallow import Marshmallow
from flask_migrate import Migrate
from flask_security import Security, SQLAlchemyUserDatastore
from flask_sqlalchemy import SQLAlchemy
babel = Babel()
db = SQLAlchemy()
ma = Marshmallow()
migrate = Migrate()
security = Security()
_app_init_hooks = []
app_init_hook = _app_init_hooks.append
def create_app():
app = Flask(__name__)
for f in _app_init_hooks:
f(app)
return app
#app_init_hook
def _configure(app):
"""Load Flask configurations"""
app.config.from_object(f"{__package__}.config")
# optional local overrides
app.config.from_pyfile("settings.cfg", silent=True)
app.config.from_envvar("PROJECT_NAME_SETTINGS", silent=True)
#app_init_hook
def _init_extensions(app):
"""Initialise Flask extensions"""
if app.env != "production":
# Only load and enable when in debug mode
from flask_debugtoolbar import DebugToolbarExtension
DebugToolbarExtension(app)
# Python-level i18n
babel.init_app(app)
# Database management (models, migrations, users)
from .models import Role, User
db.init_app(app)
migrate.init_app(app, db)
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security.init_app(app, user_datastore)
# Marshmallow integration (must run after db.init_app())
ma.init_app(app)
#app_init_hook
def _setup_blueprints(app):
"""Import and initialise blueprints"""
from . import users
from .sections import BLUEPRINTS
for blueprint in (*BLUEPRINTS, users.bp):
app.register_blueprint(blueprint)
return app
I've broken up the various components into separate functions to ease readability and maintainability, there are separate blueprints used for distinct site functions (which drives some automation in the UI).
At the top of the module are several Flask extensions that various routes and other modules need access to without having to worry about circular imports, so the blueprints are imported separately inside of the _setup_blueprints() hook function that is called from create_app().
Your use of create_app() in client.py should be fine because it doesn't add any new configuration to the Flask() instance that you'd want to have access to elsewhere, and presumably client.py is used outside of the Flask webserver process. But I would, personally, just make the result of create_app()an instance attribute of your Client instance. You don't need a global there, you only need it to access the database session easily for when add_reading() is called:
class CustomClient(Client):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs):
# Create a Flask context so we can access the SQLAlchemy session
self._app = create_app()
def add_reading(self, reading):
with self._app.app_context():
db.session.add(reading)
db.session.commit()
# ...
If add_reading() is called very frequently, you could consider making app an instance attribute of CustomClient():

Why I can't import class declared in __init__.py?

I'm trying to clean up my flask app and I decided to use a boilerplate structure. I have the auth_bp blueprint, it's defined in auth.py , I have the following folder structure;
-app_root
-application
-__init__.py
- auth.py
- other files like template and static
- config.py (for configuration)
- server.py (to run the app through flask_script )
In __init__.py I have two classes: app and mail and I need to use them in my auth.py file.
But when I write :
from application import app, mail
I get an ImportError: cannot import name app
Here's the code in __init__.py:
from flask import Flask
from flask_mail import Mail
import config
from auth import auth_bp
app = Flask(__name__)
app.config.from_object("config.Config")
mail = Mail(app)
app.register_blueprint(auth_bp)
#app.route("/")
def home():
return "homepage"
Edit : Solved: I had to import auth after creating the app and mail object.

Flask-testing - why the test does fail

I'm trying to write tests for a simple Flask application. Structure of the project is following:
app/
static/
templates/
forms.py
models.py
views.py
migrations/
config.py
manage.py
tests.py
tests.py
import unittest
from app import create_app, db
from flask import current_app
from flask.ext.testing import TestCase
class AppTestCase(TestCase):
def create_app(self):
return create_app('test_config')
def setUp(self):
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
def test_hello(self):
response = self.client.get('/')
self.assert_200(response)
app/init.py
# app/__init__.py
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from config import config
db = SQLAlchemy()
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
db.init_app(app)
return app
app = create_app('default')
from . import views
When I launch the tests, test_hello fails because response.status_code is 404. Tell me, please, how can I fix it? It seems, that app instance doesn't know anything about view functions in the views.py. If it needs the whole code, it can be found here
Your views.py file mount the routes in the app created in your __init__.py file.
You must bind these routes to your created app in create_app test method.
I suggest you to invert the dependency. Instead the views.py import your code, you can make a init_app to be imported and called from your __init__.py or from the test file.
# views.py
def init_app(app):
app.add_url_rule('/', 'index', index)
# repeat to each route
You can do better than that, using a Blueprint.
def init_app(app):
app.register_blueprint(blueprint)
This way, your test file can just import this init_app and bind the blueprint to the test app object.

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.

Categories

Resources