13

I was talking today to a colleague of mine about Python web frameworks and our impressions about them. I told him I think Flask having a global request smells badly and is an anti-pattern.

The docs say about request context:

In contrast, during request handling, a couple of other rules exist:

  • while a request is active, the context local objects (flask.request and others) point to the current request.
  • any code can get hold of these objects at any time.

I think I understand the idea behind this design decision -- to make the application simpler. It's just a compromise, like in the case of Thread Locals:

Yes it is usually not such a bright idea to use thread locals. They cause troubles for servers that are not based on the concept of threads and make large applications harder to maintain. However Flask is just not designed for large applications or asynchronous servers. Flask wants to make it quick and easy to write a traditional web application.

Is patching a global object with the current request information an anti-pattern?

I believe it is, because it is in the view of static code analyzer a global state, though it's not. And I as a programmer will not understand how it works without reading the docs carefully. And this has consequences on tests.

Isn't it a good practice to pass the request as an argument to views? I think it's more readable, explicit and easier to debug. And avoids global state.

3
  • 2
    You haven't really stated what the specific negative effects of such an antipattern might be. I distrust sweeping generalities that have no factual basis. Mar 11, 2014 at 6:08
  • 2
    Good question, but sadly not many quality answers
    – SleepyCal
    Jan 18, 2016 at 11:40
  • See Bob Martin's "the web is an IO device". 99% of your code should have no idea there even was such a thing as a web request.
    – Alexander
    Jan 5, 2021 at 20:46

4 Answers 4

4

Many web frameworks have this same structure: a global request. In a sense, it's the right thing to do because hey, there really IS only one request at a time.

So is there any point in passing the request around as a parameter? No. The request is the request, and parameters are for passing in different things at different times.

The real problem comes as you start to consider lower levels of a larger application. With a global request there is the temptation to write code all over the place that accesses the request globally. That is a very bad thing. It produces coupling between different parts of the code, makes it hard to change things and makes it hard to test things.

So my answer is: keep the global request and live with it. However, wherever an individual module or function does not need the whole request, pass only the data it needs in as a parameter. Pass just the referrer, or the url, or the command tail and whatever bits you need into your functions. This will help keep the code modular, reduce coupling and improve testability.

For tiny programs it scarcely matters, but for bigger ones this can be a real lifesaver.

3

(I'm going to go bold and make this an answer, although I might get some downvotes.)

Flask is a micro-framework; you benefit from the simplicity while giving up on frills. While on a gut level I agree with you, I do know that I used flask + gunicorn at one shop to give me the multi-threading that I needed. It worked really well. Each instance of the script just handed one request (i.e. one thread), and gunicorn handled the "fan out" among multiple threads. It was great at that.

So the perceived downside you're feeling -- that multiple threads could contend for global state -- just isn't an issue, because it's one script per thread.

(Here's where I may get into trouble) Threading and concurrency is just different in the Python world, and if you come to it with a Java frame of mind, it's hard to squeeze it in. My experience was that concurrency issues that I took for granted in Java, or that are handled transparently by the application container, are a lot closer to the surface in Python.

It was strange to me that one thread would handle one invocation of my script, but after I had a few dozen running on a box at the same time, I felt better about it.

2
  • 5
    I am not worried about thread safety and such. I believe Flask works well in these cases. My question is about application design and architecture. Isn't it a good practice to pass the request as an argument to views? I think it's more readable, explicit and easier to debug.
    – warvariuc
    Mar 11, 2014 at 7:08
  • 2
    youtu.be/S0No2zSJmks?t=1728
    – warvariuc
    Nov 30, 2020 at 9:46
3

While I agree with the other answers that it is a common pattern for web frameworks, I don't like it very much, for the following reason. Consider this piece of code from a Flask app:

from flask import Flask, request, session

app = Flask(__name__)

@app.route("/game/<game_id>/play")
def play(game_id):
    """ Player places card on the stack.
        @param game_id (str) Game UUID.
    """
    player = session.get("player")
    if not player:
        return error("not-logged-in")
    card = request.args.get("card", type=str)

What feels wrong about this?

This function/endpoint really takes three parameters: game_id, player, and card. But only one of them, game_id, is passed as an argument to the function, because it is part of the URL path. card is also part of the URL, but as an URL parameter, so Flask passes it in a completely different manner (via a threadlocal global variable - and that term alone, "threadlocal global", should raise some red flags). The player has to be logged in, and I do have some sympathy for not mixing the session with the request.

So these are my main pain points: different parts of the request URL are passed differently, and if something "thread-local global" doesn't bother you, I don't know what to say to you.

So I don't consider this pattern simple at all. If I had to go for but one adjective, I'd use "lazy".

I would very much prefer something like this:

@app.route("/game/<game_id>/play?<card>")
def play(game_id, card, session):
    #...

with session being automatically filled by the route decorator if an endpoint has an arg with that name. Same would work for a request arg if I really need the request object, not just an URL param:

@app.route("/game/<game_id/whatever")
def whatever(game_id, request, session):
    #...

This is literaly just two more words per endpoint, maximum, and gets rid of this weird construct. It frees up the framework from this (unnecessary, really) assumption of one request at a time.

1

In Python you have the print command(function since v3) that prints to the standard output. You don't specify explicitly that you want to print to STDOUT - it's done for you implicitly behind the scenes.

Implicitly. In Python. And no one has a problem with that. Why?

print is part of the Python language, and one requirement of programming in Python is... well... knowing Python. And if you know Python, you know that print targets STDOUT. No surprises there.

Python - as a language - can define it's own convention and assume that the programmers are aware of them.

Frameworks also enjoy that privilege - that's one of the key differences between a framework and a library. You don't have to learn a library in order to use it - you just need to find the part of the API that you need, and assume it follows the conventions of the language(or framework). That's why you don't see recruiters looking for people with knowledge in GSON or Apache Commons. But you do see recruiters looking for people with experience with JQuery or Ruby on Rails or ASP.NET MVC - because those are frameworks that define their own conventions that you need to learn and be aware of.

Flask, as a framework, can define a convention for storing the context in a thread-local global - and it shouldn't surprise anyone, so it's not an anti-pattern.

3
  • 2
    Note that "stdout" means whatever file descriptor is pointed to by sys.stdout. If you change that, print goes elsewhere.
    – Phoshi
    Mar 11, 2014 at 9:40
  • 2
    Also, you can override the output stream using >> operator or passing file argument to print function in Python3. So, sys.stdout is just a default value which can be overridden.
    – warvariuc
    Mar 11, 2014 at 10:25
  • This is a terrible analogy. print defaults to STDOUT, but you can override: docs.python.org/3/library/functions.html#print.
    – Gerrat
    Apr 23, 2021 at 21:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.