1
votes

I'm new with using flask so I'm doing a lot of research to find out what the best way to structure my application. I've been following app factories and using blueprints, but I'm having trouble figuring out how to use application wide libraries and keep the app context. For example here is my app structure:

├── app
│   ├── acuity
│   │   ├── handlers.py
│   │   ├── __init__.py
│   │   └── routes.py
│   ├── config.py
│   ├── __init__.py
│   ├── logs
│   ├── main
│   │   ├── __init__.py
│   │   └── routes.py
│   ├── slackapi.py
│   └── templates
│       └── acuity
│           └── acuity_slack.j2
├── gunicorn_config.py
├── slackbot.py
├── README.md
└── requirements.txt

Main and Acuity are blueprints. Main doesn't do anything and the only endpoints are in acuity. In acuity.handlers I'm importing in app.slackapi but can't use current_app no matter how I slice it. I need to use the app context in order to access the logging and config vars.

What's the best way to put the slackapi and any other common utility functions or common libraries I make into the global flask context so that I can use them inside of any of the blueprints?

EDIT: This is how I'm importing from acuity.handlers

import dateutil.parser, datetime, os, json, requests
from jinja2 import Environment, FileSystemLoader
import app.slackapi as slack
from flask import current_app

If I try to from flask import current_app in app.slackapi it doesn't error. But if I try to use and reference current_app like this:

import time
from slackclient import SlackClient
from flask import current_app

# instantiate Slack client
slack_client = SlackClient(current_app['SLACK_API_TOKEN'])

This error arises

Traceback (most recent call last):
  File "/usr/local/lib/python3.5/dist-packages/gunicorn/arbiter.py", line 583, in spawn_worker
    worker.init_process()
  File "/usr/local/lib/python3.5/dist-packages/gunicorn/workers/base.py", line 129, in init_process
    self.load_wsgi()
  File "/usr/local/lib/python3.5/dist-packages/gunicorn/workers/base.py", line 138, in load_wsgi
    self.wsgi = self.app.wsgi()
  File "/usr/local/lib/python3.5/dist-packages/gunicorn/app/base.py", line 67, in wsgi
    self.callable = self.load()
  File "/usr/local/lib/python3.5/dist-packages/gunicorn/app/wsgiapp.py", line 52, in load
    return self.load_wsgiapp()
  File "/usr/local/lib/python3.5/dist-packages/gunicorn/app/wsgiapp.py", line 41, in load_wsgiapp
    return util.import_app(self.app_uri)
  File "/usr/local/lib/python3.5/dist-packages/gunicorn/util.py", line 350, in import_app
    __import__(module)
  File "/opt/slackbot/slackbot/slackbot.py", line 3, in <module>
    app = create_app()
  File "/opt/slackbot/slackbot/app/__init__.py", line 22, in create_app
    from app.acuity import bp as acuity_bp
  File "/opt/slackbot/slackbot/app/acuity/__init__.py", line 5, in <module>
    from app.acuity import routes
  File "/opt/slackbot/slackbot/app/acuity/routes.py", line 4, in <module>
    from app.acuity import handlers
  File "/opt/slackbot/slackbot/app/acuity/handlers.py", line 3, in <module>
    import app.slackapi as slack
  File "/opt/slackbot/slackbot/app/slackapi.py", line 10, in <module>
    slack_client = SlackClient(current_app['SLACK_API_TOKEN'])
  File "/usr/local/lib/python3.5/dist-packages/werkzeug/local.py", line 377, in <lambda>
    __getitem__ = lambda x, i: x._get_current_object()[i]
  File "/usr/local/lib/python3.5/dist-packages/werkzeug/local.py", line 306, in _get_current_object
    return self.__local()
  File "/usr/local/lib/python3.5/dist-packages/flask/globals.py", line 51, in _find_app
    raise RuntimeError(_app_ctx_err_msg)
RuntimeError: Working outside of application context.

EDIT TWO: app/__init__.py

  import logging
  from logging.handlers import RotatingFileHandler
  import os
  from flask import Flask, request, current_app
  from config import Config


  def create_app(config_class=Config):
      app = Flask(__name__)
      app.config.from_object(config_class)

      from app.main import bp as main_bp
      app.register_blueprint(main_bp)

      from app.acuity import bp as acuity_bp
      app.register_blueprint(acuity_bp)

      if not app.debug and not app.testing:
          if app.config['LOG_TO_STDOUT'] == "True":
              stream_handler = logging.StreamHandler()
              stream_handler.setLevel(logging.INFO)
              app.logger.addHandler(stream_handler)
          else:
              if not os.path.exists(os.path.dirname(__file__) + '/logs'):
                  os.mkdir(os.path.dirname(__file__) + '/logs')
              file_handler = RotatingFileHandler(os.path.dirname(__file__) + '/logs/slackbot.log',
                                                 maxBytes=10240, backupCount=10)
              file_handler.setFormatter(logging.Formatter(
                  '%(asctime)s %(levelname)s: %(message)s '
                  '[in %(pathname)s:%(lineno)d]'))
              file_handler.setLevel(logging.INFO)
              app.logger.addHandler(file_handler)

          app.logger.setLevel(logging.INFO)
          app.logger.info('Slackbot startup')

      return app

app/acuity/__init__.py

from flask import Blueprint

bp = Blueprint('acuity', __name__)

from app.acuity import routes
2
but can't use current_app this point is quite hard to understand, could you please elaborate it? Why can't you use? Do you get an error?Fine
@Fian I added edits to help explainLogan Best
You can solve your problem by refactoring a slack_client init from the module-import-level (which is quite a bad practice) into a slack-client-factory and call it from your flask-app-factory (that what's app factory for — encapsulate in one place all actions needed to create your app).Fine
Do you have a link to an example of what you're talking about?Logan Best

2 Answers

1
votes

Why can't you add a create_app() function within your __init__.py file for your app package and register your blueprints within this function? Then you can just import the current_app variable in your desired blueprint and reference the current_app variable as necessary. Something along the lines of:

__init__.py:

 # .... other package initialization code

 def create_app(config_class=Config):
    app = Flask(__name__)
    app.config.from_object(Config)

    # initialization of important app components
    # database, login_manager, mail, etc....
    db.init_app(app)

    # register blueprints
    from app.acuity.routes import <acuity_blueprint_variable>
    from app.main.routes import blueprint
    app.register_blueprint(<acuity_blueprint_variable>)
    app.register_blueprint(blueprint)

return app

main/routes.py:

   # import your flask dependencies then just reference current_app where necessary
   from flask import request, redirect, url_for, render_template, current_app, Response

   blueprint = flask.Blueprint('main', __name__)

   @blueprint.route("/api", methods=["GET"])
   def list_routes():
       result = []
       for rt in current_app.url_map.iter_rules():
           result.append({
               "methods": list(rt.methods),
               "route": str(rt)
        })

    return flask.jsonify({"routes": result, "total": len(result)})

In the example above, I showed using current_app within the main blueprint list_routes endpoint which simply shows the routes defined for your application. Note within your create_app function I am importing the blueprint variable I created within main.routes called 'blueprint' and registering this blueprint to the app so references to current_app variable can be made inside this blueprint's routes.

Hopefully that makes sense!

0
votes

It may not be the most elegant solution, but you can place the items inside of your create_app (app factory) function in init.py.

If you can't do that, as per the Flask docs - you should move the code into a view function or CLI command.