Logging Flask Application in Heroku - python

I am trying to find a way to structure my code such that I can view logs in the production server hosted on Heroku.
Every tutorial I found seems to do logging like this:
How to show stdout logs in Heroku using Flask?
However, I am using the application factory pattern which utilizes Blueprints. As such here are some sample:
main.py
from app import create_app
app = create_app()
if __name__ == "__main__":
app.run()
app/_ _ init _ _.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
...
db = SQLAlchemy()
...
def create_app():
app = Flask(__name__)
...
db.init_app(app)
from .routes.demo_blueprint import demo_blueprint
...
# Register the routes to the Flask object
app.register_blueprint(demo_blueprint)
...
app/routes/demo_blueprint.py
from app import db
...
demo_blueprint = Blueprint('demo_blueprint', __name__)
#demo_blueprint.route('/demo', methods=['GET'])
...
In order to perform logging at the blueprint level, I would need to import app from main.py. However, this would cause an import error since __init__.py imports the blueprint before app is created. I was wondering if there were any work arounds for this.

Turns out it was a simple fix. To access the application context in the Blueprint, just use current_app. Following the example:
How to show stdout logs in Heroku using Flask?
main.py
from app import create_app
app = create_app()
if __name__ == "__main__":
app.run()
app/_ _ init _ _.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
...
db = SQLAlchemy()
...
def create_app():
app = Flask(__name__)
if __name__ != '__main__':
gunicorn_logger = logging.getLogger('gunicorn.error')
app.logger.handlers = gunicorn_logger.handlers
app.logger.setLevel(gunicorn_logger.level)
...
db.init_app(app)
from .routes.demo_blueprint import demo_blueprint
...
# Register the routes to the Flask object
app.register_blueprint(demo_blueprint)
...
app/routes/demo_blueprint.py
from flask import ***current_user***
from app import db
...
demo_blueprint = Blueprint('demo_blueprint', __name__)
#demo_blueprint.route('/demo', methods=['GET'])
def demo():
current_app.logger.debug('debug message: %s', 'test')
...

Related

Run flask app with python instead of flask run

I'm trying to run my flask app with "python app.py" instead of "flask run" command.
My goal is to launch the app on cpanel server and just about every tutorial requires the applications to be called using the "python" method.
Here is my folder structure:
project
webapp
init.py
templates
static
auth.py
main.py
app.py <-------------- I want this to be called with python instead of flask run command outside the folder
Here is my init_.py file:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
# init SQLAlchemy so we can use it later in our models
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
app.config['SECRET_KEY'] = '9OLWxND4o83j4iuopO'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
db.init_app(app)
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)
from .models import User
#login_manager.user_loader
def load_user(user_id):
# since the user_id is just the primary key of our user table, use it in the query for the user
return User.query.get(int(user_id))
# blueprint for auth routes in our app
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint)
# blueprint for non-auth parts of app
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
And app.py is:
from webapp import app
I'm a newbie in flask, any help is appreciated
Insert the call to create_app at the end of init.py:
if __name__ == '__main__':
create_app().run(host='0.0.0.0', port=5000, debug=True)
The if statement avoid calling the app many times. It can only be called directly. Flask default host is 127.0.0.1 (localhost). Use 0.0.0.0 at production for better traffic monitoring. Default port is also 5000, so it's up to you to include. For better readability, you should explicit it.
Then call it
python webapp/init.py

Where should I implement flask custom commands (cli)

Creating custom commands in flask needs access to the app, which is generally created in app.py like this:
import click
from flask import Flask
app = Flask(__name__)
#app.cli.command("create-user")
#click.argument("name")
def create_user(name):
...
However, in order not to bloat my app.py, I want to put my custom commands in a separate file e.g. commands.py, but this doesn't work because the entrypoint to my project is app.py, so I'll have to import app in commands.pyand import my commands in app.py which results in a circular import error.
How can I create custom commands in separate files ?
One way to achieve this would be using blueprints
I have tested it using Flask 1.1.1, so be sure to check the documentation of the correct version that you have.
Here is the general idea:
Create one or more Blueprints in a different file, let's say it's called commands.py
Then import the new blueprints and register them to your app
==> app.py <==
from flask import Flask
from commands import usersbp
app = Flask(__name__)
# you MUST register the blueprint
app.register_blueprint(usersbp)
==> commands.py <==
import click
from flask import Blueprint
usersbp = Blueprint('users', __name__)
#usersbp.cli.command('create')
#click.argument('name')
def create(name):
""" Creates a user """
print("Create user: {}".format(name))
Upon executing flask users you should get a response like the following:
flask users
Usage: flask users [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
create Creates a user
just import it in your app factory
dir tree
my_app
L app.py
L commands.py
commands.py
#app.cli.command('resetdb')
def resetdb_command():
"""Here info that will be shown in flask --help"""
pass
app.py
def create_app():
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
with app.app_context():
from . import routes
from . import commands # <----- here
return app
$ export FLASK_APP=my_app/app.py
$ flask resetdb
but there have to be better way ;) of which I am unaware right now
If you're using an app factory (you have a create_app() function), then there isn't even an app variable for you to import.
The best way to keep your code organized is to define the function somewhere else, and then register it when building the application instance.
E.g.
my_app/
| main.py
| app/
| | __init__.py
| | commands.py
commands.py
def foo():
print("Running foo()")
init.py
def create_app():
app = Flask(__name__)
...
from .commands import foo
#app.cli.command('foo')
def foo_command():
foo()
...
I have this layout:
baseapp.py
from flask import Flask
app = Flask("CmdAttempt")
app.py
from .baseapp import app
def main():
app.run(
port=5522,
load_dotenv=True,
debug=True
)
if __name__ == '__main__':
main()
commands.py
import click
from .baseapp import app
#app.cli.command("create-super-user")
#click.argument("name")
def create_super_user(name):
print("Now creating user", name)
if __name__ == '__main__':
from .app import main
main()
In the console where you run the commands first define the FLASK_APP to be commands.py, then run the commands that you define.
set FLASK_APP=commands.py
export FLASK_APP=commands.py
flask create-super-user me
You can either use a separate terminal for built-in commands or clear the FLASK_APP variable before issuing them. In Linux is even easier because you can do
FLASK_APP=commands.py flask create-super-user me
What worked for me in case you are not using app factory pattern, similar to #quester:
app.py
import os
from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv("DATABASE_URL")
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
migrate = Migrate(app, db)
with app.app_context():
# needed to make CLI commands work
from commands import *
commands.py
from app import app
#app.cli.command()
def do_something():
print('hello i am so nice I posted this even though I have 100 other things to do')

Pass application context to celery in flask framework

I tried to add celery to my existing flask project. After adding, I got an "working outside of application context" error while running. It seems that the celery worker lacks of my application context. But I am not sure where to pass the applicaiton context to celery worker in this case.
Here is my current structure (I tried to follow a factory pattern with blueprints and api documentions):
-run.py
-app
-module1
-controller.py
-model.py
-service.py
-__init__.py
-config.py
For the init.py
# __init__.py
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from app.config import Config
from flask_restplus import Api
from celery import Celery
cors = CORS()
db = SQLAlchemy()
api = Api()
celery = Celery(__name__, broker=Config.CELERY_BROKER_URL, include=["app.module1.service"])
def create_app(config_class = Config):
app = Flask(__name__, static_url_path='')
app.config.from_object(Config)
cors.init_app(app)
db.init_app(app)
api.init_app(app=app)
celery.conf.update(app.config)
from app.module1.controller import blueprint
from app.module1.controller import ns
app.register_blueprint(blueprint)
api.add_namespace(ns)
return app
For the run.py
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(threaded=True, debug=True)
For the service.py
from app import db, celery
#celery.task(bind=True)
def service1(self):
# do somethigng & return
For the controller.py
from flask import Blueprint
from flask_restplus import Api, Resouce
blueprint = Blueprint('service', __name__)
apis = Api(app = blueprint)
ns = apis.namespace('service', 'service description')
#ns.route("/")
class SomeList(Resource):
def get(self):
service1.apply_async()
# return
I think the confusion is based on the fact that you are trying to "pass" an application context to the Celery worker. In reality the Flask process cannot pass a context to the worker because they are different processes. The Celery worker process needs to create its own Flask application instance by calling create_app() so that it can push its own app contexts when needed.
So for example, in your service1 task:
from app import db, celery, create_app
#celery.task(bind=True)
def service1(self):
app = create_app()
with app.app_context():
# do somethigng & return
To make this a bit more efficient, you can create a single global app that is shared by all your tasks:
from app import db, celery, create_app
app = create_app()
#celery.task(bind=True)
def service1(self):
with app.app_context():
# do somethigng & return

Flask-PyMongo with application factory and blueprints

I am trying to implement Flask-PyMongo with blueprints and an application factory and keep getting AttributeError: 'Flask' object has no attribute 'db'
My directory structure looks like
myapp/
myapp.py
config.py
/app
__init__.py
/v1
__init__.py
endpoints.py
In my python script that starts the Flask app I have:
import os
from app import create_app
app = create_app('dev')
In my top level init.py I have:
mongo = PyMongo()
def create_app(config_name):
app = Flask(__name__)
mongo.init_app(app)
app.config.from_object(config[config_name])
from app.v1 import psapi as psapi_bp
app.register_blueprint(psapi_bp, url_prefix='/api')
if not os.path.exists('logs'):
os.mkdir('logs')
In my endpoints.py I have a route that looks like
#myapp.route('/addentry', methods=['POST'])
def addentry():
username = request.json['username']
userid = current_app.db.user_entry.insert({'username':username})
return jsonify({'userid':userid})
I feel like there is something small that I am missing but I am not seeing it.
You need to call db on your mongo object, not on the app object
to those who may be facing this problem again :
you should first define mongo oustside create_app to have access to it from inside other files.
then init_app with that like the following:
from flask import Flask, current_app
from flask_pymongo import PyMongo
mongo = PyMongo()
def create_app(config_name):
app = Flask(__name__, instance_relative_config=False)
app.config.from_object(app_config[config_name])
# INIT EXTENSIONS ----------------------
mongo.init_app(app)
return app
then in any file you can import mongo from above file. for example:
from ../factory import mongo

Get debug status out of Flask Script Manager

I'm using Flask-Script and I've got a manage.py that looks like this.
from mypackage import create_app
from flask.ext.script import Manager
app = create_app()
manager = Manager(app)
if __name__ == '__main__':
manager.run()
I'll start my app with python manage.py runserver --debug
From within manage.py, how can I find out that runserver was called with --debug?
I've tried app.debug but that returns False and manager.get_options() returns an empty list.
The code you've provided is fine-- here's a mypackage.py file to demonstrate that:
from flask import Flask, Blueprint, current_app
test = Blueprint('user', __name__, url_prefix='/')
#test.route('/')
def home():
return str(current_app.debug)
def create_app():
app = Flask(__name__)
app.register_blueprint(test)
return app
Adding --debug reflects accurately (True) when you access the index page.

Categories