Qual è lo scopo delle pile di contesti di Flask?


158

Sto usando il contesto richiesta / applicazione da un po 'di tempo senza comprendere appieno come funziona o perché è stato progettato così com'era. Qual è lo scopo dello "stack" quando si tratta della richiesta o del contesto dell'applicazione? Questi due stack separati o fanno entrambi parte di uno stack? Il contesto della richiesta è inserito in uno stack o è uno stack stesso? Sono in grado di spingere / pop più contesti uno sopra l'altro? Se è così, perché dovrei voler farlo?

Ci scusiamo per tutte le domande, ma sono ancora confuso dopo aver letto la documentazione per il contesto richiesta e il contesto applicazione.


5
kronosapiens.github.io/blog/2014/08/14/… IMO, questo post sul blog mi dà la descrizione più comprensibile del contesto del pallone.
mission.liao,

Risposte:


243

App multiple

Il contesto dell'applicazione (e il suo scopo) è davvero confuso fino a quando non ti rendi conto che Flask può avere più app. Immagina la situazione in cui desideri che un singolo interprete Python WSGI esegua più applicazioni Flask. Non stiamo parlando di progetti qui, stiamo parlando di applicazioni Flask completamente diverse.

È possibile configurarlo in modo simile alla sezione della documentazione Flask sull'esempio "Dispacciamento applicazione" :

from werkzeug.wsgi import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend

application = DispatcherMiddleware(frontend, {
    '/backend':     backend
})

Si noti che vengono create due applicazioni Flask completamente diverse "frontend" e "backend". In altre parole, il Flask(...)costruttore dell'applicazione è stato chiamato due volte, creando due istanze di un'applicazione Flask.

contesti

Quando lavori con Flask, spesso finisci per usare le variabili globali per accedere a varie funzionalità. Ad esempio, probabilmente hai un codice che legge ...

from flask import request

Quindi, durante una visualizzazione, è possibile utilizzare requestper accedere alle informazioni della richiesta corrente. Ovviamente, requestnon è una normale variabile globale; in realtà, è un valore locale di contesto . In altre parole, c'è un po 'di magia dietro le quinte che dice "quando chiamo request.path, ottengo l' pathattributo requestdall'oggetto della richiesta CORRENTE". Due richieste diverse avranno risultati diversi per request.path.

In effetti, anche se esegui Flask con più thread, Flask è abbastanza intelligente da mantenere gli oggetti richiesta isolati. In tal modo, diventa possibile per due thread, ciascuno che gestisce una richiesta diversa, chiamare contemporaneamente request.pathe ottenere le informazioni corrette per le rispettive richieste.

Mettendolo insieme

Quindi abbiamo già visto che Flask è in grado di gestire più applicazioni nello stesso interprete e anche che, a causa del modo in cui Flask ti consente di utilizzare i globi "contestuali locali", ci deve essere un meccanismo per determinare quale sia la richiesta "corrente" ( per fare cose come request.path).

Mettendo insieme queste idee, dovrebbe anche avere senso che Flask debba avere un modo per determinare qual è l'applicazione "attuale"!

Probabilmente hai anche un codice simile al seguente:

from flask import url_for

Come il nostro requestesempio, la url_forfunzione ha una logica che dipende dall'ambiente attuale. In questo caso, tuttavia, è chiaro che la logica dipende fortemente da quale app è considerata l'app "corrente". Nell'esempio di frontend / backend mostrato sopra, entrambe le app "frontend" e "backend" potrebbero avere un percorso "/ login" e quindi url_for('/login')dovrebbero restituire qualcosa di diverso a seconda che la vista stia gestendo la richiesta per l'app frontend o backend.

Per rispondere alle tue domande ...

Qual è lo scopo dello "stack" quando si tratta della richiesta o del contesto dell'applicazione?

Dai documenti contestuali della richiesta:

Poiché il contesto della richiesta è gestito internamente come uno stack, è possibile eseguire il push e il pop più volte. Questo è molto utile per implementare cose come i reindirizzamenti interni.

In altre parole, anche se in genere avrai 0 o 1 elementi su questo stack di richieste "attuali" o applicazioni "attuali", è possibile che tu ne possa avere di più.

L'esempio fornito è dove la tua richiesta restituirà i risultati di un "reindirizzamento interno". Supponiamo che un utente richieda A, ma desideri tornare all'utente B. Nella maggior parte dei casi, invii un reindirizzamento all'utente e indichi l'utente alla risorsa B, il che significa che l'utente eseguirà una seconda richiesta per recuperare B. A un modo leggermente diverso di gestirlo sarebbe quello di eseguire un reindirizzamento interno, il che significa che durante l'elaborazione di A, Flask farà una nuova richiesta a sé stesso per la risorsa B e utilizzerà i risultati di questa seconda richiesta come risultati della richiesta originale dell'utente.

Questi due stack separati o fanno entrambi parte di uno stack?

Sono due pile separate . Tuttavia, questo è un dettaglio di implementazione. La cosa più importante non è tanto che esiste uno stack, ma il fatto che in qualsiasi momento è possibile ottenere l'app o la richiesta "corrente" (parte superiore dello stack).

Il contesto della richiesta è inserito in uno stack o è uno stack stesso?

Un "contesto di richiesta" è un elemento dello "stack di contesto di richiesta". Allo stesso modo con "contesto app" e "stack contesto app".

Sono in grado di spingere / pop più contesti uno sopra l'altro? Se è così, perché dovrei voler farlo?

In un'applicazione Flask, in genere non lo si farebbe. Un esempio di dove potresti voler fare è un reindirizzamento interno (descritto sopra). Anche in quel caso, tuttavia, probabilmente finiresti con Flask per gestire una nuova richiesta, e quindi Flask farebbe tutto il push / popping per te.

Tuttavia, ci sono alcuni casi in cui vorresti manipolare lo stack da solo.

Esecuzione di codice al di fuori di una richiesta

Un tipico problema che le persone hanno è che usano l'estensione Flask-SQLAlchemy per impostare un database SQL e la definizione del modello usando un codice simile a quello mostrato di seguito ...

app = Flask(__name__)
db = SQLAlchemy() # Initialize the Flask-SQLAlchemy extension object
db.init_app(app)

Quindi usano i valori appe dbin uno script che dovrebbe essere eseguito dalla shell. Ad esempio, uno script "setup_tables.py" ...

from myapp import app, db

# Set up models
db.create_all()

In questo caso, l'estensione Flask-SQLAlchemy è a conoscenza appdell'applicazione, ma durante create_all()esso genererà un errore lamentandosi che non esiste un contesto dell'applicazione. Questo errore è giustificato; non hai mai detto a Flask quale applicazione dovrebbe avere a che fare durante l'esecuzione del create_allmetodo.

Potresti chiederti perché non finisci per aver bisogno di questa with app.app_context()chiamata quando esegui funzioni simili nelle tue viste. Il motivo è che Flask gestisce già la gestione del contesto dell'applicazione per te quando gestisce le richieste Web effettive. Il problema si pone in realtà solo al di fuori di queste funzioni di visualizzazione (o di altri callback simili), ad esempio quando si utilizzano i modelli in uno script unico.

La risoluzione è di spingere tu stesso il contesto dell'applicazione, cosa che puoi fare facendo ...

from myapp import app, db

# Set up models
with app.app_context():
    db.create_all()

Questo spingerà un nuovo contesto di applicazione (usando l'applicazione di app, ricorda che potrebbe esserci più di un'applicazione).

analisi

Un altro caso in cui si desidera manipolare lo stack è per i test. È possibile creare un unit test che gestisce una richiesta e controllare i risultati:

import unittest
from flask import request

class MyTest(unittest.TestCase):
    def test_thing(self):
        with app.test_request_context('/?next=http://example.com/') as ctx:
            # You can now view attributes on request context stack by using `request`.

        # Now the request context stack is empty

3
Questo mi confonde ancora! Perché non avere un unico contesto di richiesta e sostituirlo se si desidera eseguire un reindirizzamento interno. Mi sembra un design chiaro.
Maarten,

@Maarten Se durante la gestione della richiesta A si effettua la richiesta B e la richiesta B sostituisce la richiesta A in pila, la gestione per la richiesta A non può terminare. Tuttavia, anche se hai adottato la strategia di sostituzione come suggerito e non disponevi di uno stack (il che significa che i reindirizzamenti interni sarebbero più difficili) ciò non cambia il fatto che sono necessari contesti di app e richieste per isolare la gestione delle richieste.
Mark Hildreth,

Buona spiegazione Ma sono ancora un po 'confuso riguardo a: "Il contesto dell'applicazione viene creato e distrutto come necessario. Non si sposta mai tra i thread e non verrà condiviso tra le richieste." Nel documento di pallone. Perché un "contesto applicativo" non è persistente insieme all'app?
Jay

1
Un esempio di reindirizzamento interno in Flask sarebbe utile, cercare su Google non si presenta molto. Se non fosse per questo, un request = Local()design più semplice non sarebbe sufficiente per global.py? Probabilmente ci sono casi d'uso a cui non sto pensando.
Quadrupla:

Va bene inserire il contesto dell'app all'interno del metodo factory durante l'importazione delle viste? Poiché le viste contengono percorsi che si riferiscono a current_app, ho bisogno del contesto.
variabile dal

48

Le risposte precedenti offrono già una buona panoramica di ciò che accade sullo sfondo di Flask durante una richiesta. Se non l'hai ancora letto, raccomando la risposta di @ MarkHildreth prima di leggere questo. In breve, viene creato un nuovo contesto (thread) per ogni richiesta http, motivo per cui è necessario disporre di una Localfunzione thread che consenta oggetti come requestegessere accessibili a livello globale tra i thread, pur mantenendo il contesto specifico della loro richiesta. Inoltre, durante l'elaborazione di una richiesta http Flask può emulare richieste aggiuntive dall'interno, quindi la necessità di memorizzare il rispettivo contesto su uno stack. Inoltre, Flask consente a più applicazioni wsgi di essere eseguite l'una accanto all'altra all'interno di un singolo processo e più di una può essere chiamata all'azione durante una richiesta (ogni richiesta crea un nuovo contesto dell'applicazione), quindi la necessità di uno stack di contesto per le applicazioni. Questo è un riassunto di ciò che è stato trattato nelle risposte precedenti.

Il mio obiettivo ora è quello di integrare la nostra attuale comprensione spiegando come Flask e Werkzeug fanno quello che fanno con queste persone del contesto. Ho semplificato il codice per migliorare la comprensione della sua logica, ma se ottieni questo, dovresti essere in grado di cogliere facilmente la maggior parte di ciò che è nella fonte reale ( werkzeug.locale flask.globals).

Per prima cosa capiamo come Werkzeug implementa Thread Locals.

Locale

Quando arriva una richiesta http, viene elaborata nel contesto di un singolo thread. Come mezzo alternativo per generare un nuovo contesto durante una richiesta http, Werkzeug consente anche l'uso di greenlet (una sorta di "micro-thread" più leggeri) anziché normali thread. Se non hai installato greenlet, tornerà a utilizzare i thread. Ognuno di questi thread (o greenlet) è identificabile da un ID univoco, che è possibile recuperare con la get_ident()funzione del modulo . Tale funzione è il punto di partenza per la magia dietro con request, current_app, url_for, g, e altre oggetti globali legato al contesto.

try:
    from greenlet import get_ident
except ImportError:
    from thread import get_ident

Ora che abbiamo la nostra funzione di identità, possiamo sapere su quale thread ci troviamo in un dato momento e possiamo creare quello che viene chiamato thread Local, un oggetto contestuale a cui è possibile accedere a livello globale, ma quando accedi ai suoi attributi si risolvono al loro valore per quel filo specifico. per esempio

# globally
local = Local()

# ...

# on thread 1
local.first_name = 'John'

# ...

# on thread 2
local.first_name = 'Debbie'

Entrambi i valori sono presenti Localcontemporaneamente sull'oggetto accessibile globalmente , ma l'accesso local.first_namenel contesto del thread 1 ti darà 'John', mentre tornerà 'Debbie'sul thread 2.

Come è possibile? Diamo un'occhiata ad alcuni codici (semplificati):

class Local(object)
    def __init__(self):
        self.storage = {}

    def __getattr__(self, name):
        context_id = get_ident() # we get the current thread's or greenlet's id
        contextual_storage = self.storage.setdefault(context_id, {})
        try:
            return contextual_storage[name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        context_id = get_ident()
        contextual_storage = self.storage.setdefault(context_id, {})
        contextual_storage[name] = value

    def __release_local__(self):
        context_id = get_ident()
        self.storage.pop(context_id, None)

local = Local()

Dal codice sopra possiamo vedere che la magia si riduce a get_ident()cui identifica il greenlet o il thread corrente. L' Localarchiviazione quindi lo utilizza come chiave per archiviare qualsiasi dato contestuale al thread corrente.

È possibile avere più Localoggetti per ogni processo e request, g, current_appe altri potrebbe semplicemente essere stato creato in quel modo. Ma non è così che si fa in Flask in cui questi non sono tecnicamente Local oggetti, ma LocalProxyoggetti più accurati . Che cos'è un LocalProxy?

LocalProxy

Un LocalProxy è un oggetto che richiede a Localdi trovare un altro oggetto di interesse (ovvero l'oggetto a cui viene inoltrato). Diamo un'occhiata per capire:

class LocalProxy(object):
    def __init__(self, local, name):
        # `local` here is either an actual `Local` object, that can be used
        # to find the object of interest, here identified by `name`, or it's
        # a callable that can resolve to that proxied object
        self.local = local
        # `name` is an identifier that will be passed to the local to find the
        # object of interest.
        self.name = name

    def _get_current_object(self):
        # if `self.local` is truly a `Local` it means that it implements
        # the `__release_local__()` method which, as its name implies, is
        # normally used to release the local. We simply look for it here
        # to identify which is actually a Local and which is rather just
        # a callable:
        if hasattr(self.local, '__release_local__'):
            try:
                return getattr(self.local, self.name)
            except AttributeError:
                raise RuntimeError('no object bound to %s' % self.name)

        # if self.local is not actually a Local it must be a callable that 
        # would resolve to the object of interest.
        return self.local(self.name)

    # Now for the LocalProxy to perform its intended duties i.e. proxying 
    # to an underlying object located somewhere in a Local, we turn all magic
    # methods into proxies for the same methods in the object of interest.
    @property
    def __dict__(self):
        try:
            return self._get_current_object().__dict__
        except RuntimeError:
            raise AttributeError('__dict__')

    def __repr__(self):
        try:
            return repr(self._get_current_object())
        except RuntimeError:
            return '<%s unbound>' % self.__class__.__name__

    def __bool__(self):
        try:
            return bool(self._get_current_object())
        except RuntimeError:
            return False

    # ... etc etc ... 

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

    def __setitem__(self, key, value):
        self._get_current_object()[key] = value

    def __delitem__(self, key):
        del self._get_current_object()[key]

    # ... and so on ...

    __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
    __delattr__ = lambda x, n: delattr(x._get_current_object(), n)
    __str__ = lambda x: str(x._get_current_object())
    __lt__ = lambda x, o: x._get_current_object() < o
    __le__ = lambda x, o: x._get_current_object() <= o
    __eq__ = lambda x, o: x._get_current_object() == o

    # ... and so forth ...

Ora, per creare proxy accessibili a livello globale, lo faresti

# this would happen some time near application start-up
local = Local()
request = LocalProxy(local, 'request')
g = LocalProxy(local, 'g')

e ora qualche tempo prima nel corso di una richiesta, memorizzereste alcuni oggetti all'interno del locale a cui i proxy precedentemente creati possono accedere, indipendentemente dal thread su cui ci troviamo

# this would happen early during processing of an http request
local.request = RequestContext(http_environment)
local.g = SomeGeneralPurposeContainer()

Il vantaggio di utilizzare LocalProxycome oggetti accessibili a livello globale piuttosto che crearli da Localssoli è che semplifica la loro gestione. Hai solo bisogno di un singolo Localoggetto per creare molti proxy accessibili a livello globale. Alla fine della richiesta, durante la pulizia, è sufficiente rilasciare quello Local(ovvero estrarre il context_id dalla sua memoria) e non preoccuparsi dei proxy, sono ancora accessibili a livello globale e continuano a rinviare a quello Localper trovare il loro oggetto di interesse per successive richieste http.

# this would happen some time near the end of request processing
release(local) # aka local.__release_local__()

Per semplificare la creazione di un LocalProxyquando ne abbiamo già uno Local, Werkzeug implementa il Local.__call__()metodo magico come segue:

class Local(object):
    # ... 
    # ... all same stuff as before go here ...
    # ... 

    def __call__(self, name):
        return LocalProxy(self, name)

# now you can do
local = Local()
request = local('request')
g = local('g')

Tuttavia, se si guarda nella fonte Flask (flask.globals), che non è ancora come request, g, current_appe sessionvengono creati. Come abbiamo stabilito, Flask può generare più richieste "false" (da una singola richiesta http reale) e nel processo anche spingere più contesti applicativi. Questo non è un caso d'uso comune, ma è una capacità del framework. Dal momento che queste richieste e app "simultanee" sono ancora limitate per essere eseguite con solo uno che ha il "focus" in qualsiasi momento, ha senso usare uno stack per il rispettivo contesto. Ogni volta che viene generata una nuova richiesta o viene chiamata una delle applicazioni, spingono il loro contesto in cima al rispettivo stack. Flask utilizza LocalStackoggetti per questo scopo. Quando concludono la loro attività, espellono il contesto dallo stack.

LocalStack

Ecco LocalStackcome appare (di nuovo il codice è semplificato per facilitare la comprensione della sua logica).

class LocalStack(object):

    def __init__(self):
        self.local = Local()

    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):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self.local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self.local) # this simply releases the local
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self.local.stack[-1]
        except (AttributeError, IndexError):
            return None

Si noti da quanto sopra che a LocalStackè uno stack archiviato in un locale, non un gruppo di locali memorizzati in uno stack. Ciò implica che sebbene lo stack sia accessibile a livello globale è uno stack diverso in ogni thread.

Flask non dispone request, current_app, g, e sessiongli oggetti per risolvere direttamente ad una LocalStack, piuttosto utilizza LocalProxyoggetti che avvolgono una funzione di ricerca (invece di un Localoggetto) che troverà l'oggetto sottostante dal LocalStack:

_request_ctx_stack = LocalStack()
def _find_request():
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return top.request
request = LocalProxy(_find_request)

def _find_session():
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return top.session
session = LocalProxy(_find_session)

_app_ctx_stack = LocalStack()
def _find_g():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return top.g
g = LocalProxy(_find_g)

def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return top.app
current_app = LocalProxy(_find_app)

Tutti questi sono dichiarati all'avvio dell'applicazione, ma in realtà non risolvono nulla fino a quando un contesto di richiesta o contesto di applicazione non viene inserito nel rispettivo stack.

Se sei curioso di vedere come un contesto è effettivamente inserito nello stack (e successivamente spuntato), guarda in flask.app.Flask.wsgi_app()quale punto di entrata dell'app wsgi (ovvero ciò che chiama il web server e passa l'ambiente http a quando un richiesta entra) e segui la creazione RequestContextdell'oggetto per tutto il suo successivo push()in _request_ctx_stack. Una volta spinto nella parte superiore dello stack, è accessibile tramite _request_ctx_stack.top. Ecco un codice abbreviato per dimostrare il flusso:

Quindi avvii un'app e la rendi disponibile al server WSGI ...

app = Flask(*config, **kwconfig)

# ...

Successivamente arriva una richiesta http e il server WSGI chiama l'app con i soliti parametri ...

app(environ, start_response) # aka app.__call__(environ, start_response)

Questo è all'incirca quello che succede nell'app ...

def Flask(object):

    # ...

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

    def wsgi_app(self, environ, start_response):
        ctx = RequestContext(self, environ)
        ctx.push()
        try:
            # process the request here
            # raise error if any
            # return Response
        finally:
            ctx.pop()

    # ...

e questo è più o meno quello che succede con RequestContext ...

class RequestContext(object):

    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.session = self.app.open_session(self.request)
        if self.session is None:
            self.session = self.app.make_null_session()
        self.flashes = None

    def push(self):
        _request_ctx_stack.push(self)

    def pop(self):
        _request_ctx_stack.pop()

Supponi che una richiesta abbia terminato l'inizializzazione, la ricerca request.pathda una delle tue funzioni di visualizzazione dovrebbe quindi procedere come segue:

  • partire dalla globalmente accessibile LocalProxyoggetto request.
  • per trovare il suo oggetto di interesse sottostante (l'oggetto a cui sta eseguendo il proxy) chiama la sua funzione di ricerca _find_request()(la funzione che ha registrato come suo self.local).
  • quella funzione interroga l' LocalStackoggetto _request_ctx_stackper il contesto principale nello stack.
  • per trovare il contesto principale, l' LocalStackoggetto prima interroga il suo Localattributo interno ( self.local) per la stackproprietà precedentemente memorizzata lì.
  • da stackesso ottiene il contesto superiore
  • e top.requestviene quindi risolto come oggetto di interesse sottostante.
  • da quell'oggetto otteniamo l' pathattributo

Quindi abbiamo visto come Local, LocalProxye LocalStackfunziona, ora pensiamo per un momento alle implicazioni e alle sfumature nel recuperare il pathda:

  • un requestoggetto che sarebbe un semplice oggetto accessibile a livello globale.
  • un requestoggetto che sarebbe un locale.
  • un requestoggetto memorizzato come attributo di un locale.
  • un requestoggetto che è un proxy per un oggetto archiviato in un locale.
  • un requestoggetto memorizzato su uno stack, che a sua volta è archiviato in un locale.
  • un requestoggetto che è un proxy per un oggetto su uno stack archiviato in un locale. <- questo è ciò che fa Flask.

4
Ottima carrellata, ho studiato il codice in flask / globals.py e werkzeug / local.py e questo mi aiuta a chiarire la mia comprensione. Il mio senso spidey mi dice che questo è un design troppo complicato, ma ammetto di non capire tutti i casi d'uso a cui è destinato. I "reindirizzamenti interni" sono l'unica giustificazione che ho visto nelle descrizioni sopra e googling "reindirizzamento interno del pallone" non si presenta molto, quindi sono ancora un po 'perplesso. Una delle cose che mi piacciono di Flask è che generalmente non è una cosa tipo zuppa di oggetti Java piena di AbstractProviderContextBaseFactories e simili.
Quadrupla:

1
@QuadrupleA Una volta capito come questi Local, LocalStacke LocalProxyil lavoro, mi suggeriscono di rivedere questi articoli della DOC: flask.pocoo.org/docs/0.11/appcontext , flask.pocoo.org/docs/0.11/extensiondev e flask.pocoo .org / docs / 0.11 / reqcontext . La tua nuova comprensione può farti vedere con una nuova luce e può fornire maggiori informazioni.
Michael Ekoka,

Leggi questi link - hanno principalmente senso, ma il design mi sembra ancora troppo complicato e forse troppo intelligente per il suo bene. Ma non sono un grande fan di OOP in generale, e roba implicita di controllo del flusso (override di __call __ (), __getattr __ (), invio di eventi dinamici rispetto a semplici chiamate di funzione, avvolgimento di cose in accessor di proprietà piuttosto che usare semplicemente un attributo regolare, ecc. .) quindi forse è solo una differenza nella filosofia. Nemmeno un professionista TDD, che molti di questi macchinari in più sembrano destinati a supportare.
Quadrupla:

1
Grazie per averlo condiviso, apprezzato. Il threading è il punto debole di linguaggi come Python: finisci con schemi come sopra che si insinuano nei framework applicativi e che non si adattano nemmeno. Java è un altro esempio in una situazione simile per quanto riguarda. threadlocals, semaphors ecc. Notoriamente difficile da ottenere o mantenere. È qui che linguaggi come Erlang / Elixir (usando BEAM) o approcci a loop di eventi (ad es. Nginx vs apache ecc.) In genere offrono un approccio più potente, scalabile e meno complesso.
arcseldon,

13

Piccola aggiunta alla risposta di Mark Hildreth .

Assomiglia allo stack di contesto {thread.get_ident(): []}, dove []chiamato "stack" perché utilizzato solo append( push) pope [-1]( __getitem__(-1)) operazioni. Quindi lo stack di contesto manterrà i dati effettivi per il thread o il thread greenlet.

current_app, g, request, sessionEd ecc è LocalProxyoggetto appena overrided metodi speciali __getattr__, __getitem__, __call__, __eq__e così via e valore restituito dall'alto stack di contesto ( [-1]) per nome di argomento ( current_app, requestper esempio). LocalProxynecessario importare questi oggetti una volta e non mancheranno la realtà. Quindi meglio importare requestovunque tu sia nel codice, invece gioca con l'invio di argomenti di richiesta fino a te funzioni e metodi. Puoi facilmente scrivere le proprie estensioni con esso, ma non dimenticare che un utilizzo frivolo può rendere il codice più difficile da comprendere.

Trascorri del tempo per capire https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/local.py .

Quindi, come popolate entrambe le pile? Su richiesta Flask:

  1. crea request_contextper ambiente (init map_adapter, match match)
  2. inserisci o invia questa richiesta:
    1. cancella precedente request_context
    2. crea app_contextse ha perso e trasferito nello stack di contesto dell'applicazione
    3. questa richiesta è stata inviata per richiedere lo stack di contesto
    4. init session se fallisce
  3. richiesta di spedizione
  4. cancella la richiesta e aprila dallo stack

2

Facciamo un esempio, supponiamo di voler impostare un usercontext (usando il costrutto flask di Local e LocalProxy).

Definire una classe utente:

class User(object):
    def __init__(self):
        self.userid = None

definire una funzione per recuperare l'oggetto utente all'interno del thread o greenlet corrente

def get_user(_local):
    try:
        # get user object in current thread or greenlet
        return _local.user
    except AttributeError:
        # if user object is not set in current thread ,set empty user object 
       _local.user = User()
    return _local.user

Ora definisci un LocalProxy

usercontext = LocalProxy(partial(get_user, Local()))

Ora per ottenere userid dell'utente nel thread corrente usercontext.userid

spiegazione :

1.Local ha un soggetto di identità e oggetto, l'identità è threadid o id greenlet, in questo esempio _local.user = User () è equivalente a _local .___ storage __ [id thread corrente] ["user"] = User ()

  1. LocalProxy delega l' operazione all'oggetto Local concluso oppure è possibile fornire una funzione che restituisce l'oggetto target. Nell'esempio sopra la funzione get_user fornisce l'oggetto utente corrente a LocalProxy e quando si richiede userid dell'utente corrente da usercontext.userid, la funzione __getattr__ di LocalProxy prima chiama get_user per ottenere l'oggetto utente (utente) e quindi chiama getattr (utente, "userid"). per impostare userid su User (nel thread o greenlet corrente) devi semplicemente fare: usercontext.userid = "user_123"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.