> request objects aren't stored in python's thread local storage. If they were, a thread re-use will leak context local.
No more so than werkzeug since it works the same way.
> Werkzeug uses the thread/greenlet id as key in its storage
Which is exactly how Python's threadlocals work.
> and cleans up when the request completes.
You do realize that nothing stops you from "cleaning up" your threadlocal "when the request completes" right? Yes werkzeug's locals and localmanagers conveniently provide hooks for that cleanup, but that's what it is: convenient. The primary purpose and the one explicitly mentioned in werkzeug's own documentation is having a single system for both greenlets and threads (the stdlib's threading.local obviously doesn't work with greenlets, just as werkzeug's locals don't work with non-greenlet coroutines)
> In a single threaded server, the context locals won't leak from one request to another. If they were global, they would.
Not if you cleaned them up after the request has executed. And apparently that's all you need to make a global variable not-global-after-all.
> I don't see how request object has anything to do with global variables.
It's an object available from all scopes. That's pretty much the definition of a global variable.