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