Flask'ın içerik yığınlarının amacı nedir?


158

Nasıl çalıştığını veya neden bu şekilde tasarlandığını tam olarak anlamadan bir süredir istek / uygulama bağlamını kullanıyorum. İstek veya uygulama bağlamında "yığının" amacı nedir? Bu iki ayrı yığın mı, yoksa her ikisi de bir yığının parçası mı? İstek içeriği bir yığına mı aktarılıyor yoksa bir yığının kendisi mi? Birden fazla bağlamı birbirinin üzerine itebilir miyim? Öyleyse, neden bunu yapmak isteyeyim ki?

Tüm sorular için özür dilerim, ancak İstek İçeriği ve Uygulama İçeriği belgelerini okuduktan sonra hala kafam karıştı.


5
kronosapiens.github.io/blog/2014/08/14/… IMO, bu blog yazısı bana şişe bağlamının en anlaşılır tanımını veriyor.
mission.liao

Yanıtlar:


243

Birden Çok Uygulama

Uygulama içeriği (ve amacı), Flask'ın birden fazla uygulamaya sahip olabileceğini anlayana kadar gerçekten kafa karıştırıcıdır. Tek bir WSGI Python yorumlayıcısının birden fazla Flask uygulaması çalıştırmasını istediğiniz durumu düşünün. Burada Blueprints'ten bahsetmiyoruz, tamamen farklı Flask uygulamalarından bahsediyoruz.

Bunu , "Uygulama Gönderme" örneğindeki Flask belgeleri bölümüne benzer şekilde ayarlayabilirsiniz :

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

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

"Ön uç" ve "arka uç" olmak üzere iki farklı Flask uygulaması oluşturulduğuna dikkat edin. Başka bir deyişle,Flask(...) uygulama yapıcı iki kez çağrıldı ve Flask uygulamasının iki örneği oluşturuldu.

Bağlam

Flask ile çalışırken, genellikle çeşitli işlevlere erişmek için genel değişkenleri kullanırsınız. Örneğin, muhtemelen ...

from flask import request

Sonra, bir görünüm sırasında request, geçerli isteğin bilgilerine erişmek için kullanabilirsiniz . Açıkçası, requestnormal bir küresel değişken değildir; gerçekte bir bağlam yerel değeridir. Diğer bir deyişle, perde arkasında "Aradığımda request.path, pathözniteliği requestAKIM isteğinin nesnesinden al " yazan bir sihir var . İki farklı istek için farklı sonuçlar alınacaktırrequest.path .

Aslında, Flask'ı birden çok iş parçacığıyla çalıştırsanız bile, Flask, istek nesnelerini yalıtılmış tutacak kadar akıllıdır. Bunu yaparken, her biri farklı bir isteği ele alan iki iş parçacığının aynı anda çağırması mümkün olurrequest.path kendi istekleri için doğru bilgileri ve alması .

Bir Araya Getirme

Bu nedenle, Flask'ın aynı yorumlayıcıda birden çok uygulamayı işleyebileceğini ve ayrıca Flask'ın "bağlam yerel" globalleri kullanmanıza izin vermesi nedeniyle "mevcut" isteğin ne olduğunu belirlemek için bir mekanizma olması gerektiğini gördük ( gibi şeyler yapmak içinrequest.path ).

Bu fikirleri bir araya getirirken, Flask'ın "mevcut" uygulamanın ne olduğunu belirlemek için bir yolu olması gerekir!

Muhtemelen aşağıdakine benzer bir kodunuz da vardır:

from flask import url_for

Bizim gibi requestörneğin, url_forfonksiyon mevcut ortamda bağlıdır mantığı vardır. Ancak bu durumda, mantığın hangi uygulamanın "geçerli" uygulama olarak kabul edildiğine büyük ölçüde bağlı olduğunu görmek açıktır. Yukarıda gösterilen ön uç / arka uç örneğinde, hem "ön uç" hem de "arka uç" uygulamalarının "/ giriş" yolu olabilir ve bu nedenleurl_for('/login') , görünümün ön uç veya arka uç uygulaması isteğini işleyip işlemediğine bağlı olarak farklı bir şey dönmelidir.

Sorularınızı cevaplamak için ...

İstek veya uygulama bağlamında "yığının" amacı nedir?

İstek İçeriği belgelerinden:

İstek içeriği dahili olarak bir yığın olarak tutulduğundan, birden çok kez itip pop yapabilirsiniz. Bu, dahili yönlendirmeler gibi şeyleri uygulamak için çok kullanışlıdır.

Başka bir deyişle, bu "geçerli" istekler veya "geçerli" uygulamalar yığınında genellikle 0 veya 1 öğeniz olsa da, daha fazlasına sahip olabilirsiniz.

Verilen örnek, isteğinizin bir "dahili yönlendirme" sonucunu döndürmesini istediğiniz yerdir. Bir kullanıcının A istediğini varsayalım, ancak B kullanıcısına geri dönmek istiyorsunuz. Çoğu durumda, kullanıcıya bir yönlendirme yayınlar ve kullanıcıyı B kaynağına yönlendirirsiniz, yani kullanıcı B'yi getirmek için ikinci bir istek çalıştırır. Bunu işlemenin biraz farklı bir yolu dahili bir yönlendirme yapmaktır, yani A işlenirken, Flask kaynak B için yeni bir istekte bulunur ve bu ikinci isteğin sonuçlarını kullanıcının orijinal isteğinin sonuçları olarak kullanır.

Bu iki ayrı yığın mı, yoksa her ikisi de bir yığının parçası mı?

Bunlar iki ayrı istiftir . Ancak, bu bir uygulama detayıdır. Daha da önemlisi, bir yığın olduğu kadar çok değil, ancak herhangi bir zamanda "geçerli" uygulamayı veya isteği (yığının üstünde) alabilmenizdir.

İstek içeriği bir yığına mı aktarılıyor yoksa bir yığının kendisi mi?

"İstek içeriği", "istek içeriği yığını" nın bir öğesidir. "Uygulama içeriği" ve "uygulama içeriği yığını" ile benzer şekilde.

Birden fazla bağlamı birbirinin üzerine itebilir miyim? Öyleyse, neden bunu yapmak isteyeyim ki?

Bir Flask uygulamasında, genellikle bunu yapmazsınız. İsteyebileceğiniz yerlere bir örnek, dahili bir yönlendirme içindir (yukarıda açıklanmıştır). Bununla birlikte, bu durumda bile, muhtemelen Flask'ın yeni bir isteği ele almasını sağlarsınız ve böylece Flask sizin için tüm itme / patlatma işlemlerini yapacaktır.

Bununla birlikte, yığını kendiniz değiştirmek istediğiniz bazı durumlar vardır.

Kod istek dışında çalışıyor

İnsanların sahip olduğu tipik bir sorun, aşağıda gösterilen gibi bir kod kullanarak bir SQL veritabanı ve model tanımı oluşturmak için Flask-SQLAlchemy uzantısını kullanmalarıdır ...

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

Daha sonra kabuktan çalıştırılması gereken bir komut dosyasında appve dbdeğerlerini kullanırlar . Örneğin, bir "setup_tables.py" komut dosyası ...

from myapp import app, db

# Set up models
db.create_all()

Bu durumda, Flask-SQLAlchemy uzantısı appuygulamayı bilir , ancak uygulama sırasında create_all()bir uygulama bağlamı olmadığından şikayetçi bir hata verir. Bu hata haklıdır; Flask'a create_allyöntemi çalıştırırken hangi uygulamayla uğraşması gerektiğini asla söylemediniz .

with app.app_context()Görünümlerinizde benzer işlevleri çalıştırdığınızda neden bu çağrıya ihtiyaç duymadığınızı merak ediyor olabilirsiniz . Bunun nedeni, Flask'ın gerçek web isteklerini işlerken uygulama bağlamının yönetimini zaten sizin için gerçekleştirmesidir. Sorun, yalnızca modellerinizi tek seferlik bir komut dosyasında kullanırken olduğu gibi bu görünüm işlevlerinin (veya diğer geri aramaların) dışında ortaya çıkar.

Çözüm, uygulama bağlamını kendiniz yapmaktır, bu da ...

from myapp import app, db

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

Bu, yeni bir uygulama bağlamını zorlar ( app birden fazla uygulama olabileceğini unutmayın).

Test yapmak

Yığını değiştirmek istediğiniz başka bir durum test içindir. Bir isteği işleyen bir birim testi oluşturabilir ve sonuçları kontrol edebilirsiniz:

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
Bu hala benim için kafa karıştırıcı! Dahili bir yönlendirme yapmak istiyorsanız neden tek bir istek bağlamınız olmasın ve değiştirin. Bana net bir tasarım gibi geliyor.
Maarten

@Maarten A isteğini işlerken B isteğini yaparsanız ve B isteği yığındaki A isteğinin yerini alırsa, A isteğinin işleme tamamlanamaz. Ancak, değiştirdiğiniz stratejiyi önerdiğiniz gibi yapsanız ve bir yığına sahip olmasanız bile (dahili yönlendirmelerin daha zor olacağı anlamına gelir), bu, isteklerin işlenmesini izole etmek için uygulama ve istek bağlamlarının gerekli olduğu gerçeğini gerçekten değiştirmez.
Mark Hildreth

İyi açıklama! Ama yine de biraz kafa karıştırıcıyım: "Uygulama içeriği gerektiği şekilde oluşturulur ve yok edilir. Asla iş parçacıkları arasında hareket etmez ve istekler arasında paylaşılmaz." Şişenin belgesinde. Neden bir "uygulama bağlamı" uygulama ile birlikte devam etmiyor?
jayven

1
Flask'ta dahili bir yönlendirme örneği yararlı olabilir, googling çok fazla ortaya çıkmaz. Değilse request = Local(), global.py için daha basit bir tasarım yeterli olmaz mı? Muhtemelen düşünmediğim kullanım örnekleri vardır.
QuadrupleA

Görünümleri içe aktarırken uygulama bağlamını fabrika yönteminin içine itmek iyi mi? Görünümler current_app atıfta bulunan yollar içerdiğinden içeriğe ihtiyacım var.
değişken

48

Önceki cevaplar zaten bir istek sırasında Flask'ın arka planında neler olduğuna dair güzel bir genel bakış sunuyor. Henüz okumadıysanız, bunu okumadan önce @ MarkHildreth'in cevabını öneriyorum. Kısacası, her http isteği için yeni bir bağlam (iş parçacığı) oluşturulur. Bu nedenle, aşağıdaki Localgibi nesnelere izin veren bir iş parçacığı olanağına sahip olmak gerekir.request vegtalebe özgü bağlamlarını korurken, iş parçacıkları arasında küresel olarak erişilebilir olmak. Ayrıca, bir http isteği işlenirken, Flask içeriden ek istekler taklit edebilir, dolayısıyla ilgili bağlamlarını bir yığın üzerinde saklama zorunluluğu vardır. Ayrıca, Flask, birden çok wsgi uygulamasının tek bir işlem içinde birbiriyle birlikte çalışmasına izin verir ve bir istek sırasında (her istek yeni bir uygulama bağlamı oluşturur) birden fazla eylem için çağrılabilir, bu nedenle uygulamalar için bir bağlam yığını gerekir. Bu, önceki cevaplarda nelerin ele alındığının bir özetidir.

Şimdi amacım, Flask ve Werkzeug'un bu yerel halkla yaptıklarını nasıl yaptığını açıklayarak mevcut anlayışımızı tamamlamak . Mantığını anlamak için kodu basitleştirdim, ancak bunu alırsanız, gerçek kaynaktaki ( werkzeug.localve flask.globals) çoğu şeyi kolayca kavrayabilmelisiniz .

İlk olarak Werkzeug'un yerel ayarları nasıl uyguladığını anlayalım.

Yerel

Bir http isteği geldiğinde, tek bir iş parçacığı bağlamında işlenir. Bir http isteği sırasında yeni bir bağlam oluşturmak için alternatif bir yol olarak, Werkzeug ayrıca normal dişler yerine yeşil otların (bir çeşit daha hafif "mikro-iplikler") kullanılmasına izin verir. Yüklü greenlet'leriniz yoksa, bunun yerine evreleri kullanmaya dönecektir. Bu iş parçacıklarının (veya yeşil alanların) her biri, modülün get_ident()işleviyle alabileceğiniz benzersiz bir kimlikle tanımlanabilir . Bu işlev sahip arkasındaki sihirli başlıyor noktasıdır request, current_app, url_for, g, ve diğer bu tür bağlama bağlı küresel nesneler.

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

Artık kimlik fonksiyonumuza sahip olduğumuza göre, herhangi bir zamanda hangi evrede olduğumuzu biliyoruz ve Localküresel olarak erişilebilen bağlamsal bir nesne olan bir iş parçacığı yaratabiliyoruz, ancak özniteliklerine eriştiğinizde, belirli bir iş parçacığı. Örneğin

# globally
local = Local()

# ...

# on thread 1
local.first_name = 'John'

# ...

# on thread 2
local.first_name = 'Debbie'

Her iki değer de küresel olarak erişilebilir Localnesne üzerinde aynı anda bulunur, ancak local.first_nameiş parçacığı 1 bağlamında erişim size 'John'verirken 'Debbie', iş parçacığı 2'ye geri döner .

Bu nasıl mümkün olabilir? Bazı (basitleştirilmiş) kodlara bakalım:

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()

Yukarıdaki get_ident()koddan, mevcut broşürü veya iş parçacığını tanımlayan büyünün kaynadığını görebiliriz . LocalDepolama sonra sadece geçerli iş parçacığının herhangi bir veri içeriksel saklamak için bir anahtar olarak bunu kullanır.

Birden olabilir Localsüreci başına nesneleri ve request, g, current_appve diğerleri sadece bunun gibi oluşturulmuş olabilir. Ama bu teknik olarak Local nesneler değil , daha doğru LocalProxynesneler olan Flask'ta böyle yapılmıyor . Nedir LocalProxy?

LocalProxy

LocalProxy, a Localilgili başka bir nesneyi (yani vekil sunucuya) bulmak için a sorgulayan bir nesnedir. Anlamak için bir göz atalım:

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 ...

Şimdi dünya çapında erişilebilir proxy'ler oluşturmak için

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

ve şimdi bir istek boyunca bir süre önce, hangi iş parçacığında olursak olalım, önceden oluşturulmuş proxy'lerin erişebileceği bazı nesneleri yerel olarak depolarsınız

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

Kendileri LocalProxyyapmaktan ziyade küresel olarak erişilebilir nesneler olarak kullanmanın avantajı, Localsyönetimini basitleştirmesidir. LocalGlobal olarak erişilebilir birçok proxy oluşturmak için sadece tek bir nesneye ihtiyacınız vardır . İsteğin sonunda, temizleme sırasında, yalnızca bir tanesini serbest bırakırsınız Local(yani, bağlam_kodunu depodan çıkarırsınız) ve proxy'lerle uğraşmazsınız, hala küresel olarak erişilebilirler ve hala Localnesnelerini bulmak için birine ertelerler sonraki http istekleri için ilgi.

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

LocalProxyZaten sahip olduğumuz bir zamanın oluşturulmasını basitleştirmek için Local, Werkzeug Local.__call__()sihirli yöntemi aşağıdaki gibi uygular :

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')

Ancak, ne kadar hala değil Flask kaynağı (flask.globals) bakarsanız request, g, current_appve sessionoluşturulur. Oluşturduğumuz gibi, Flask birden fazla "sahte" istek oluşturabilir (tek bir gerçek http isteğinden) ve süreçte de birden fazla uygulama bağlamını zorlayabilir. Bu yaygın bir kullanım örneği değildir, ancak çerçevenin bir yeteneğidir. Bu "eşzamanlı" istekler ve uygulamalar, herhangi bir zamanda yalnızca "odak" a sahip olmakla sınırlı kaldığından, ilgili bağlamları için bir yığın kullanmak mantıklıdır. Yeni bir istek oluşturulduğunda veya uygulamalardan biri çağrıldığında, bağlamlarını ilgili yığının en üstüne taşırlar. Flask LocalStackbu amaçla nesneleri kullanır . İşlerini tamamladıklarında bağlamı yığının dışına çıkarırlar.

LocalStack

Bu bir LocalStackgörünüştür (yine mantığın anlaşılmasını kolaylaştırmak için kod basitleştirilmiştir).

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

Yukarıdakilerden, LocalStacka'nın, bir yığın üzerinde depolanan bir grup yerlisi değil, yerel olarak depolanan bir yığın olduğunu unutmayın. Bu, yığın genel olarak erişilebilir olmasına rağmen, her bir iş parçacığında farklı bir yığın olduğunu gösterir.

Flask onun yok request, current_app, gve sessionbir doğrudan çözme nesnelerin LocalStack, oldukça kullandığı LocalProxynesneleri saran bir arama fonksiyonu (yerine ait Localitibaren altta yatan nesne bulacaksınız nesne) 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)

Tüm bunlar uygulama başlangıcında bildirilir, ancak bir istek bağlamı veya uygulama bağlamı kendi yığınlarına iletilene kadar hiçbir şeyi çözmez.

Bir bağlamın gerçekten yığına nasıl eklendiğini (ve daha sonra dışarı flask.app.Flask.wsgi_app()attığınızı) merak ediyorsanız, wsgi uygulamasının hangi giriş noktasının olduğuna bakın (örn. Web sunucusu ne çağırır ve http ortamını istek) gelir ve oluşturulmasını izleyin RequestContextbütün müteakip yoluyla nesnenin push()INTO _request_ctx_stack. Yığının üst kısmına itildiğinde, üzerinden erişilebilir _request_ctx_stack.top. Akışı göstermek için bazı kısaltılmış kodlar:

Böylece bir uygulamayı başlatıp WSGI sunucusuna sunabilirsiniz ...

app = Flask(*config, **kwconfig)

# ...

Daha sonra bir http isteği gelir ve WSGI sunucusu uygulamayı her zamanki parametrelerle çağırır ...

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

Bu kabaca app olur ne olduğunu ...

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()

    # ...

ve kabaca RequestContext ile olan şey ...

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()

Bir isteğin başlatılmasının bittiğini, bu request.pathnedenle görünüm işlevlerinizden birinin aranması şu şekilde gerçekleşeceğini varsayalım:

  • küresel olarak erişilebilir LocalProxynesneden başlayın request.
  • temel ilgi nesnesini (proxy yaptığı nesne) bulmak için arama işlevini _find_request()(kaydettiği işlev) çağırır self.local.
  • bu işlev LocalStacknesneyi _request_ctx_stackyığındaki en üst bağlam için sorgular .
  • üst bağlamı bulmak için, LocalStacknesne önce kendi iç Localözniteliğini ( self.local) stackdaha önce orada saklanan özellik için sorgular .
  • dan stacküst bağlamı alır
  • ve top.requestböylece temel ilgi konusu nesne olarak çözümlenir.
  • o nesneden pathözniteliği alırız

Nasıl gördüğümüz Yani Local, LocalProxyve LocalStackişi, şimdi alınırken etkileri ve nüansları bir an için düşünmek pathitibaren:

  • requestküresel olarak erişilebilir basit bir nesne olabilecek bir nesne.
  • requestyerel olacak bir nesne.
  • requestyerel bir öznitelik olarak depolanan bir nesne.
  • Bir requestyerel saklanan bir nesneye bir proxy nesne.
  • bir requestyığın üzerinde depolanan, yani bir yerelde depolanan bir nesne.
  • bir requestyerel depolanmış bir yığında bir nesneye bir proxy nesne. <- Flask bunu yapar.

4
Mükemmel yıkık, flask / globals.py ve werkzeug / local.py kodunu inceledim ve bu benim anlayışımı netleştirmeye yardımcı oluyor. Benim spidey duygum bana bunun çok karmaşık bir tasarım olduğunu söylüyor, ama itiraf ediyorum amaçlanan tüm kullanım durumlarını anlamadım. "Dahili yönlendirmeler" yukarıdaki açıklamalarda gördüğüm tek gerekçedir ve "şişe iç yönlendirmesi" için arama yapmak pek bir şey yapmaz. Şişe hakkında sevdiğim şeylerden biri, genellikle AbstractProviderContextBaseFactories ve benzeri dolu bir java nesne çorbası türü şey değil.
QuadrupleA

1
@QuadrupleA bu gördükten sonra Local, LocalStackve LocalProxyiş, ben bu dokümanın makaleleri tekrar önermek: flask.pocoo.org/docs/0.11/appcontext , flask.pocoo.org/docs/0.11/extensiondev ve flask.pocoo .org / docs / 0.11 / reqcontext . Yeni tutmanız onları yeni bir ışıkla görmenizi sağlayabilir ve daha fazla bilgi sağlayabilir.
Michael Ekoka

Bu bağlantıları okuyun - çoğunlukla mantıklıdırlar, ancak tasarım hala aşırı karmaşık olarak beni vuruyor ve belki de kendi iyiliği için çok zeki. Ama genel olarak büyük bir OOP hayranı değilim ve örtük akış kontrol şeyleri (__call __ (), __getattr __ (), basit işlev çağrılarına karşı gönderme dinamik olay, sadece normal bir özellik kullanmak yerine özellik erişimcilerinde şeyleri sarma vb. .) öyleyse belki de bu sadece felsefede bir farklılıktır. Bu ekstra makinelerin çoğunun desteklediği düşünülen bir TDD uygulayıcısı da değil.
QuadrupleA

1
Bunu paylaştığın için teşekkürler, takdir ettim. Diş çekme, python gibi dillerdeki zayıflıktır - uygulama çerçevelerine sızan ve gerçekte ölçeklenmeyen yukarıdaki gibi desenlerle sarılırsınız. Java benzer bir durumda başka bir örnektir. threadlocals, semaforlar vs. Erlang / Elixir (BEAM kullanarak) veya olay döngüsü yaklaşımları (ör. Nginx vs apache vb.) Gibi diller genellikle daha güçlü, ölçeklenebilir ve daha az karmaşık bir yaklaşım sunar.
arcseldon

13

Küçük ek @ Mark Hildreth'in cevabı.

Bağlam yığını {thread.get_ident(): []}, []yalnızca append( push) popve [-1]( __getitem__(-1)) işlemleri kullanıldığından "yığın" olarak adlandırılır . Böylece bağlam yığını iş parçacığı veya yeşil iş parçacığı için gerçek verileri tutacaktır.

current_app, g, request, sessionVb olup LocalProxy, sadece özel yöntemler overrided amacı __getattr__, __getitem__, __call__, __eq__vb ve bağlam yığını üstüne (dönüş değeri [-1](argüman adıyla) current_app, requestörneğin). LocalProxybu nesneleri bir kez içe aktarmak için gerekli ve gerçekliği kaçırmayacaklar. Yani daha iyi sadece requestkod nerede olursanız olun ithalat yerine fonksiyonları ve yöntemleri aşağı istek argümanı gönderme ile oynamak. Kendi uzantılarını onunla kolayca yazabilirsiniz, ancak anlamsız kullanımın kodun anlaşılmasını zorlaştırabileceğini unutmayın.

Anlamak için zaman ayırın https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/local.py .

Peki her iki istif nasıl doldurulur? İstek üzerine Flask:

  1. request_contextortama göre oluştur (başlangıç map_adapter, eşleşme yolu)
  2. bu isteği girin veya iletin:
    1. öncekini temizle request_context
    2. app_contextkaçırır ve uygulama bağlamı yığınına aktarırsa oluştur
    3. bu istek içerik yığını istemeye zorlandı
    4. özlediyse oturumu başlat
  3. gönderme talebi
  4. isteği temizle ve istiften çıkar

2

Bir örnek verelim, bir kullanıcı bağlamı ayarlamak istediğinizi varsayalım (Local ve LocalProxy'nin flask yapısını kullanarak).

Bir Kullanıcı sınıfı tanımlayın:

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

geçerli iş parçacığı veya greenlet içindeki kullanıcı nesnesini geri almak için bir işlev tanımlayın

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

Şimdi bir LocalProxy tanımlayın

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

Şimdi geçerli iş parçacığında userid kullanıcı kimliğini almak için usercontext.userid

açıklama:

1.Local'ın bir kimlik ve objet algısı vardır, kimlik threadid veya greenlet kimliğidir, bu örnekte _local.user = User () _local .___ storage __ [current thread's id] ["user"] = User () ile eşdeğerdir

  1. LocalProxy , sarılmış yerel nesneyi işlem için yetkilendirir veya hedef nesneyi döndüren bir işlev sağlayabilirsiniz. Yukarıdaki örnekte get_user işlevi, geçerli kullanıcı nesnesini LocalProxy'ye sağlar ve geçerli kullanıcının userid'ini usercontext.userid ile sorduğunuzda, LocalProxy'nin __getattr__ işlevi önce Kullanıcı nesnesini (kullanıcı) almak için get_user öğesini çağırır ve sonra getattr'i (kullanıcı, "userid") çağırır. Kullanıcı kimliğini (geçerli iş parçacığında veya greenlet'te) ayarlamak için yapmanız gerekenler: usercontext.userid = "user_123"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.