The first official wechat account of this article is Zhanzhuslag

Welcome to your attention.

Related knowledge

WEB development – Python WSGI protocol details

Flask runs a process

background

In Flask, from Flask import request, current_APP, g can be imported and used directly. How does Flask guarantee that the request object is the same as the request object?

Flask officially mentions using local thread objects, and this article explains how it works

Flask Context-safe between threads

The thread safety principle of Falsk is to maintain the request stack and app stack in the process after startup. The stack ensures the thread safety of each request through the thread ID.

The implementation mainly relies on three classes Local, LocalStack and LocalProxy. Let’s take a look at the specific implementation principle

Three classes build local data

1. Local

Flask’s dependency on the Werkzeug library is what defines Local.

# werkzeug\local.py

# get_ident gets the unique identity of the thread and coroutine

try:
    from greenlet import getcurrent as get_ident
except ImportError:
    try:
        from thread import get_ident
    except ImportError:
        from _thread import get_ident

The actual Local class
class Local(object):
    __slots__ = ('__storage__'.'__ident_func__')

    def __init__(self):
        object.__setattr__(self, '__storage__', {})
        object.__setattr__(self, '__ident_func__', get_ident)

    def __release_local__(self):
        self.__storage__.pop(self.__ident_func__(), None)

    def __getattr__(self, name):
        try:
            return self.__storage__[self.__ident_func__()][name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}
Copy the code

You can see that it defines two attributes __storage__ and __ident_func__ and three methods __getattr__, __setattr__, and __release_local__.

  1. The __Storage__ attribute is a multi-level dictionary where the first key is the implied thread ID or coroutine ID and the second key is the actual keyword used

  2. The attribute __ident_func__ can be seen as the get_ident function, which either gets the thread ID of the current execution unit from the Thread library or the coroutine ID of the current execution coroutine from the Greenlet library.

In addition, you can see three methods of the Local class that essentially override Python’s __setattr__ and __getattr__ built-in functions to isolate data between threads or coroutines

  1. Obtain an attribute of local

    For example, the local.age substance triggers the __getattr__ method

    1. Get the current thread ID, the __ident_func__ function, and then find the result set for thread ident in the __storage__ dictionary

    2. Look up the age property from the result

  2. When setting an attribute of local

    For example, local.age = 12 triggers the __setattr__ method

    Get the current thread ID, the __ident_func__ function, and then set the corresponding attribute dictionary in the __storage__ dictionary

Another __release_local__ method is to delete the corresponding thread data.

It’s a little bit more intuitive to draw a schematic.

The main thread generates an object local= local (), and three threads perform the same operation local.no= the number corresponding to each thread. There is a store for each thread, so whoever retrieves or saves finds its own place in the store. The keys are the same, but each access is about its own value.

2. LocalStack

Flask LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack LocalStack

class LocalStack(object):
    def __init__(self):
        self._local = Local()

    def __release_local__(self):
        self._local.__release_local__()

    def _get__ident_func__(self):
        return self._local.__ident_func__

    def _set__ident_func__(self, value):
        object.__setattr__(self._local, "__ident_func__", value)

    __ident_func__ = property(_get__ident_func__, _set__ident_func__)
    del _get__ident_func__, _set__ident_func__

    def __call__(self):
        def _lookup(a):
            rv = self.top
            if rv is None:
                raise RuntimeError("object unbound")
            return rv

        return LocalProxy(_lookup)

    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self._local, "stack".None)
        if rv is None:
            self._local.stack = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        stack = getattr(self._local, "stack".None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self._local)
            return stack[- 1]
        else:
            return stack.pop()

    @property
    def top(self):
        try:
            return self._local.stack[- 1]
        except (AttributeError, IndexError):
            return None
Copy the code

LocalStack essentially operates around Local.

  1. LocalStack defines a Local object
  2. The stack property is set to this object, and this property is a list
  3. LocalStack defines methods to push and unload the list
  4. Provides custom ident_func methods for Local objects in the class

3. LocalProxy

LocalProxy literally means to make a Local proxy, we first from the definition of a request to see the use of LocalProxy, and then combined with the source code to see what is LocalProxy used to do?

# venv/Lib/site-packages/werkzeug/local.py

@implements_bool
class LocalProxy(object):

	__slots__ = ("__local"."__dict__"."__name__"."__wrapped__")

    def __init__(self, local, name=None):
        object.__setattr__(self, "_LocalProxy__local", local)
        object.__setattr__(self, "__name__", name)
        if callable(local) and not hasattr(local, "__release_local__") :# "local" is a callable that is not an instance of Local or
            # LocalManager: mark it as a wrapped function.
            object.__setattr__(self, "__wrapped__", local)

    def _get_current_object(self):
        if not hasattr(self.__local, "__release_local__") :return self.__local()
        try:
            return getattr(self.__local, self.__name__)
        except AttributeError:
            raise RuntimeError("no object bound to %s" % self.__name__)

    def __getattr__(self, name):
        if name == "__members__":
            return dir(self._get_current_object())
        return getattr(self._get_current_object(), name)


    __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
Copy the code

__setattr__(self, “_LocalProxy__local”, local) actually sets a __local attribute to self. This is the definition of a private variable in Python classes. See Python’s official definition for Python private variables.

As you can see, this class overrides the built-in methods of all Python classes, operating on objects returned by the _get_current_object method defined in the class.

The return value of this method is the result of the execution of the given local object when initialized. If a Local object is not specified at creation time, this method is executed directly. If a Local object is given, the corresponding object is found based on the class name.

Now this is a little abstract, what does this agent actually do? Flask defines a global request object. If we want to get what the requested method is, we use request.method.

Below is the source code for the request definition

# flask/globals.py

def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)

request = LocalProxy(partial(_lookup_req_object, "request"))
Copy the code
  1. According to the __getattr__ method rewritten in LocalProxy source code, execute the _get_CURRENT_object method to get the object, and then get the method attribute of the returned object.
  2. The function passed when creating LocalProxy is a partial function of _lookup_req_object, which is actually _lookup_req_object and name=request
  3. __local is a function in LocalProxy, so executing _get_CURRENT_object is the value returned by _lookup_req_object and name=request, and then taking its method property
  4. Then run the _lookup_req_object function to obtain the top request from the _request_ctx_stack

Using Proxy, you can easily and quickly obtain the corresponding value by using Request. method. The core of this method is that the corresponding function is executed each time the value is returned by the function is thread-safe. Make sure the data is correct and elegant. Otherwise we execute one function at a time to get its value and then get its properties.