Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessing Flask app context results in RuntimeError: Working outside of application context. #75

Closed
mayanksha opened this issue Jul 30, 2020 · 4 comments · Fixed by #76
Labels
enhancement New feature or request

Comments

@mayanksha
Copy link

Hi,

I was trying to deploy some Google Cloud Functions today and found out about this wonderful project to test the functions locally. I can't thank you enough for this project, since it saves a lot of time (deploying is very time consuming, and being able to test it locally is a big relief).

I've created a minimum viable example that can help to reproduce the error. The same example works wonderfully when deployed as a Google Cloud Function, but it fails with an error when run with functions-framework. The error is as follows (I'm using python 3.7 to create a virtual env and testing within that):

(venv) .../python/min-eg  $ functions-framework --port 5000 --target hello
Traceback (most recent call last):
  File "/home/msharma/git/python/min-eg/venv/bin/functions-framework", line 8, in <module>
    sys.exit(_cli())
  File "/home/msharma/git/python/min-eg/venv/lib/python3.7/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/home/msharma/git/python/min-eg/venv/lib/python3.7/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/home/msharma/git/python/min-eg/venv/lib/python3.7/site-packages/click/core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/msharma/git/python/min-eg/venv/lib/python3.7/site-packages/click/core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "/home/msharma/git/python/min-eg/venv/lib/python3.7/site-packages/functions_framework/_cli.py", line 37, in _cli
    app = create_app(target, source, signature_type)
  File "/home/msharma/git/python/min-eg/venv/lib/python3.7/site-packages/functions_framework/__init__.py", line 229, in create_app
    spec.loader.exec_module(source_module)
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/home/msharma/git/python/min-eg/main.py", line 1, in <module>
    from app import db
  File "/home/msharma/git/python/min-eg/app.py", line 29, in <module>
    with app.app_context():
  File "/home/msharma/git/python/min-eg/venv/lib/python3.7/site-packages/werkzeug/local.py", line 347, in __getattr__
    return getattr(self._get_current_object(), name)
  File "/home/msharma/git/python/min-eg/venv/lib/python3.7/site-packages/werkzeug/local.py", line 306, in _get_current_object
    return self.__local()
  File "/home/msharma/git/python/min-eg/venv/lib/python3.7/site-packages/flask/globals.py", line 52, in _find_app
    raise RuntimeError(_app_ctx_err_msg)
RuntimeError: Working outside of application context.

This typically means that you attempted to use functionality that needed
to interface with the current application object in some way. To solve
this, set up an application context with app.app_context().  See the
documentation for more information.

The Example is as follows (2 files):

requirements.txt file

alembic==1.4.2
cffi==1.14.1
click==7.1.2
cloudevents==0.3.0
Flask==1.1.2
Flask-JWT-Extended==3.24.1
Flask-Migrate==2.5.3
Flask-SQLAlchemy==2.4.4
functions-framework==2.0.0
gunicorn==20.0.4
itsdangerous==1.1.0
Jinja2==2.11.2
Mako==1.1.3
MarkupSafe==1.1.1
pathtools==0.1.2
psycopg2==2.8.5
py-bcrypt==0.4
pycparser==2.20
PyJWT==1.7.1
python-dateutil==2.8.1
python-editor==1.0.4
six==1.15.0
SQLAlchemy==1.3.18
watchdog==0.10.3
Werkzeug==1.0.1

main.py file

import os

from flask import current_app as app
from flask_sqlalchemy import SQLAlchemy, sqlalchemy

def setup_context():
    # Set these in the environment variables for the function
    db_user = os.environ["DB_USER"]
    db_password = os.environ["DB_PASSWORD"]
    db_name = os.environ["DB_NAME"]

    db_connection_instance_name = os.environ["DB_CONNECTION_INSTANCE_NAME"]
    db_host = os.environ["DB_HOST"]

    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 

    driver_name = 'postgresql+psycopg2'
    app.config['SQLALCHEMY_DATABASE_URI'] = sqlalchemy.engine.url.URL(
        drivername=driver_name,
        username=db_user,
        password=db_password,
        database=db_name,
        host=db_host
    )

    db = SQLAlchemy(app)
    return db

with app.app_context():
    db = setup_context()

def hello(request):
    stmt = 'select * from users;'
    try:
        result = db.session.execute(stmt)
        for rowproxy in result:
            print(rowproxy.items())
    except Exception as e:
        return 'Error: {}'.format(str(e))
    return 'ok'

The issue seems to be in the __init__.py file where we create an instance of the Flask app (https://github.com/GoogleCloudPlatform/functions-framework-python/blob/master/src/functions_framework/__init__.py#L154-L175). I'm not great at python, but it looks to me as if the module is imported before the Flask app context is even created. So, if I move the app = flask.Flask(target, template_folder=template_folder) before I import the source module, I can successfully run functions-framework without an errors.

In terms of code, something like this (i'm not sure if I'm doing it correctly, so please bear with me):

    app = flask.Flask(target, template_folder=template_folder)
    app.config["MAX_CONTENT_LENGTH"] = MAX_CONTENT_LENGTH

    with app.app_context():
        # Load the source file:
        # 1. Extract the module name from the source path
        realpath = os.path.realpath(source)
        directory, filename = os.path.split(realpath)
        name, extension = os.path.splitext(filename)

        # 2. Create a new module
        spec = importlib.util.spec_from_file_location(name, realpath)
        source_module = importlib.util.module_from_spec(spec)

        # 3. Add the directory of the source to sys.path to allow the function to
        # load modules relative to its location
        sys.path.append(directory)

        # 4. Add the module to sys.modules
        sys.modules[name] = source_module

        # 5. Execute the module
        spec.loader.exec_module(source_module)

Is it a bug, or am I doing something wrong here?

@di
Copy link
Member

di commented Jul 31, 2020

Hi @mayanksha thanks for filing an issue.

I'd say this isn't a bug, but you also aren't really doing anything wrong here: you're just relying on an undefined / unofficial implementation detail. We don't explicitly document that from flask import current_app should work, and it's not officially supported.

That said, given that this works when deployed, I think this is a trivial enough change that we can make an update to the framework to maintain parity.

Can you try installing the branch from #76 locally and confirm that it works for you? You should be able to do that with:

$ pip install git+https://github.com/googlecloudplatform/functions-framework-python.git@fix/75

@mayanksha
Copy link
Author

I tested these changes and I can confirm that it works as expected. Thanks! :-)

@di di added the enhancement New feature or request label Aug 14, 2020
@di di closed this as completed in #76 Aug 17, 2020
@rez0n
Copy link

rez0n commented Mar 19, 2023

HI @mayanksha @di
I'm trying to do same using latest version of functions-framework but having another error on db = SQLAlchemy(app)

from flask import jsonify
import functions_framework

from flask import current_app as app
from flask_sqlalchemy import SQLAlchemy


def setup_context():
    app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///'
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    db = SQLAlchemy(app)
    return db


with app.app_context():
    db = setup_context()


@functions_framework.http
def posts_test(request):
    request_json = request.get_json(silent=True)
    request_args = request.args
    return jsonify({'success': True})
Traceback (most recent call last):
  File "/Users/dv/Library/Caches/pypoetry/virtualenvs/cloudbench-U2d1CbNl-py3.9/bin/functions-framework", line 8, in <module>
    sys.exit(_cli())
  File "/Users/dv/Library/Caches/pypoetry/virtualenvs/cloudbench-U2d1CbNl-py3.9/lib/python3.9/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/Users/dv/Library/Caches/pypoetry/virtualenvs/cloudbench-U2d1CbNl-py3.9/lib/python3.9/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/Users/dv/Library/Caches/pypoetry/virtualenvs/cloudbench-U2d1CbNl-py3.9/lib/python3.9/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/dv/Library/Caches/pypoetry/virtualenvs/cloudbench-U2d1CbNl-py3.9/lib/python3.9/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
  File "/Users/dv/Library/Caches/pypoetry/virtualenvs/cloudbench-U2d1CbNl-py3.9/lib/python3.9/site-packages/functions_framework/_cli.py", line 36, in _cli
    app = create_app(target, source, signature_type)
  File "/Users/dv/Library/Caches/pypoetry/virtualenvs/cloudbench-U2d1CbNl-py3.9/lib/python3.9/site-packages/functions_framework/__init__.py", line 353, in create_app
    spec.loader.exec_module(source_module)
  File "<frozen importlib._bootstrap_external>", line 850, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/Users/dv/Library/CloudStorage/Dropbox/cloudbench/gcloud-functions-python-postgres/main.py", line 39, in <module>
    db = setup_context()
  File "/Users/dv/Library/CloudStorage/Dropbox/cloudbench/gcloud-functions-python-postgres/main.py", line 34, in setup_context
    db = SQLAlchemy(app)
  File "/Users/dv/Library/Caches/pypoetry/virtualenvs/cloudbench-U2d1CbNl-py3.9/lib/python3.9/site-packages/flask_sqlalchemy/extension.py", line 219, in __init__
    self.init_app(app)
  File "/Users/dv/Library/Caches/pypoetry/virtualenvs/cloudbench-U2d1CbNl-py3.9/lib/python3.9/site-packages/flask_sqlalchemy/extension.py", line 311, in init_app
    engines = self._app_engines.setdefault(app, {})
  File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/weakref.py", line 498, in setdefault
    return self.data.setdefault(ref(key, self._remove),default)
TypeError: cannot create weak reference to 'LocalProxy' object

Please share a solution if you have.

@vincerubinetti
Copy link

vincerubinetti commented Apr 22, 2024

I was trying to include flask-compress to gzip all my responses, which needs access to the Flask app. Doing this seems to work:

from flask import current_app as app
from flask_compress import Compress

Compress(app)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants