DEAR PEOPLE FROM THE FUTURE: Here's what we've figured out so far...

Welcome! This is a Q&A website for computer programmers and users alike, focused on helping fellow programmers and users. Read more

What are you stuck on? Ask a question and hopefully somebody will be able to help you out!
+3 votes

It seems like not matter what happens, the flask request and instance of Flask (and instance of a Blueprint if using that) always require some kind of global finagling. And when I remove all the globals (except for request) the instance of Flask that typically one names "app" will work with app.run() but then all the routes respond 404.

EDIT: Here's the boilerplate example with global vars:

from flask import Flask

app = Flask("MyAppName")

@app.route("/hello")
def hello_world():
    return "hello world"

if __name__ == "__main__":
    app.run()

EDIT 2: Here's the failing example written as a test. Instead of passing around the global variable, we can instantiate a new one, but then the decorators fail. So I don't know if it can be done.

So the two modules, one called views.py:

from flask import Flask


def get_route_index():
    return "/foobuz"


def get_app():
    return Flask(__name__)


app = get_app()


@app.route(get_route_index())
def index():
    return "Stub foobuz app."

And the second module test_views.py:

import unittest

from flask_testing import TestCase
from werkzeug.test import Client

from views import get_app, get_route_index, # app


class TestFoobuzPackage(TestCase):
    def setUp(self):
        pass

    def create_app(self):
        app = get_app()
        app.config["TESTING"] = True
        self.client = app.test_client()
        return app

    def tearDown(self):
        pass

    def test_index(self):
        response = Client.open(
            self.client,
            path=get_route_index(),
        )
        self.assert_200(response)
by
edited by
0

What globals in particular? FLASK_ENV, or other variables? Can you add a stripped-down example of code that returns 404?

2 Answers

+1 vote
 
Best answer

Your code in views.py is actually correct code, and it works. TestFoobuzPackage.test_index() is failing because the app that you define in TestFoobuzPackage.create_app is not the same app that is defined at the module level. To make the test pass, you can modify def create_app(self): in one of these ways

  • use the global app

      def create_app(self):
          # app = get_app()
            
          global app
            
          app.config["TESTING"] = True
          self.client = app.test_client()
          return app
    
  • use the decorator to register the route to the correct app

      def create_app(self):
          app = get_app()
            
          @app.route(get_route_index())
          def index():
              return "ok"
            
          app.config["TESTING"] = True
          self.client = app.test_client()
          return app
    
  • like above but use add_url_rule instead. You can actually see that the decorator is a function that wraps the add_url_rule function, so you can use the function directly:

      def create_app(self):
          app = get_app()
            
          app.add_url_rule(get_route_index(), 'index', index)
            
          app.config["TESTING"] = True
          self.client = app.test_client()
          return app
    
  • or you can use app.view_functions to assign/modify the views functions after you have created all your endpoints:

      def create_app(self):
          app = get_app()
            
          # Create all the endpoints first
          app.add_url_rule(get_route_index(), 'index')
    
          # Assign the view functions at a later moment (maybe in another function)
          app.view_functions['index'] = index
            
          app.config["TESTING"] = True
          self.client = app.test_client()
          return app
    
by
selected by
0

Yes, just to be clear, that example was supposed to fail to demonstrate that the decorators required an instance of Flask (or alternately Blueprint, not shown). The third option you presented is the way I referred to in the thread with aggsol.

That said, adding the routes wouldn't happen in create_app. They should go into get_app instead. Oh, another way to make it cleaner, def get_app(testing=False): would allow one to avoid the mutation of app outside of get_app.

As for create_app, unfortunately that's a naming convention with the flask_testing. There are ways to test with just the built-in unittest.

+1

This repo has the tested answer.

Here's the working views.py:

from flask import Flask

from no_global_flask_app.business import my_foo, my_index


def get_route_index():
    return "/"


def get_route_foo():
    return "/foo"


def get_app(testing=False):
    app = Flask(__name__)
    if testing:
        app.config["TESTING"] = True
    app.add_url_rule(get_route_index(), "index", index)
    app.add_url_rule(get_route_foo(), "foo", foo)
    return app


def index(local_index=my_index):
    return local_index()


def foo(local_foo=my_foo):
    return local_foo()
0

I've made a few minor changes. In order to access the request and response, flask makes it unavoidable to use global vars.

So it appears the overall, direct answer to my question is "No". Meh...

That said, I've been able to isolate the damage into just the one views.py module. In addition to that, I've used flask-testing (over the flask recommended pytest) as a drop-in replacement for the built-in unittest.

+1 vote

Yes, that is possible.Just wrap it in a function like __main__

Some thing like this pseudocode.py:

from flask import Flask

app = Flask("MyAppName")

def main():
  app.run(port=5000, debug=True)
by
+1
Even like that, the `app` variable is still in the global scope. I've updated the question above with the boilerplate example. Sorry for all the edits... I didn't realize the markdown only works in the questions or answers, not the comments.
0

@definitely_not_me

That is on purpose, because I use PyCharm it is detected as Flask entry and can be run in development mode (which uses env vars) and debugged. In the main function I use a WSGI server for a production like run. That is then configured with command line parameters

0

Yes, I use uWSGI to serve up the endpoint to a reverse proxy. But the thing is how to configure the routes. How do you set them up for your implementation? Maybe.... Is there a way to procedurally define them after instantiating the instance of Flask without using the decorator based on the global?

0

Looks like add_url_rule() can solve the global variable in the routes decorators. I'll see if I can come up with a way to deal with the requests....

https://flask.palletsprojects.com/en/2.0.x/api/#url-route-registrations

0

The only way I know to do it procedurally would with OpenApi and connexion. That open ups another can of worms. I just use the decorator.

Contributions licensed under CC0
...