from flask import Flask # Flask 모듈을 import하고 Flask 웹 서버를 생성한다.
app = Flask(__name__) # __name__은 현재 파일을 의미함. main.py가 된다. app이라고 하는 Flask 인스턴스를 생성한다. 새로운 웹 앱이 생성된다.
@app.route("/") # default 페이지
def home(): # 사용자가 default 페이지로 접속하면 home()이 실행됨.
return "Hello, World!"
if __name__ == "__main__": # 파이썬 스크립트가 실행될 때 파이썬은 __main__을 스크립트에 할당한다.
app.run(debug=True) # 앱 실행
FLASK_APP=main.py flask run
flask run
명령어는 FLASK_APP
환경 변수가 가리키는 설정으로 앱이 어디에 위치해 있는지 알게 된다.
Werkzeug, request
request의 타입은 werkzeug.local.LocalProxy
WSGI 애플리케이션은 HTTP 요청을 처리하기 위한 함수로 environ
과 start_response
를 컨테이너로부터 받아 요청을 처리한다. Flask는 Flask.wsgi_app()
을 통해 이 부분을 처리한다.
def wsgi_app(self, environ, start_response):
with self.request_context(environ):
try:
response = self.full_dispatch_request()
except Exception, e:
response = self.make_response(self.handle_exception(e))
return response(environ, start_response)
environ
에 담긴 HTTP 요청에 대한 명세를 Request
형태로 바꾸는 역할을 하는 것이 request_context()
. request_context()
는 클라이언트로부터 받은 environ
을 토대로 request에 대한 context를 만들어서 이를 핸들러에서 사용할 수 있게 한다.
def request_context(self, environ):
return RequestContext(self, environ)
class RequestContext(object):
def __init__(self, app, environ):
self.app = app
self.request = app.request_class(environ)
self.url_adapter = app.create_url_adapter(self.request)
self.g = app.request_globals_class()
self.flashes = None
self.session = None
self.preserved = False
self._pushed_application_context = None
self.match_request()
# XXX: Support for deprecated functionality. This is going away with
# Flask 1.0
blueprint = self.request.blueprint
if blueprint is not None:
# better safe than sorry, we don't want to break code that
# already worked
bp = app.blueprints.get(blueprint)
if bp is not None and blueprint_is_module(bp):
self.request._is_old_module = True
def push(self):
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop()
self._pushed_application_context = _push_app_if_necessary(self.app)
_request_ctx_stack.push(self)
self.session = self.app.open_session(self.request)
if self.session is None:
self.session = self.app.make_null_session()
def pop(self, exc=None):
self.preserved = False
if exc is None:
exc = sys.exc_info()[1]
self.app.do_teardown_request(exc)
rv = _request_ctx_stack.pop()
assert rv is self, 'Popped wrong request context. (%r instead of %r)' \
% (rv, self)
rv.request.environ['werkzeug.request'] = None
if self._pushed_application_context:
self._pushed_application_context.pop(exc)
self._pushed_application_context = None
def __enter__(self):
self.push()
return self
def __exit__(self, exc_type, exc_value, tb):
if self.request.environ.get('flask._preserve_context') or \
(tb is not None and self.app.preserve_context_on_exception):
self.preserved = True
else:
self.pop(exc_value)
self._pushed_application_context = _push_app_if_necessary(self.app)
_request_ctx_stack.push(self)
self.session = self.app.open_session(self.request)
if self.session is None:
self.session = self.app.make_null_session()
app의 request_class
를 통해 문맥 안에서 사용할 request
를 초기화 한다. __enter__
를 통해 자기 자신을 _request_ctx_stack
에 넣는다.
_request_ctx_stack
이 스택은 .global
에 정의되어 있다. _request_ctx
는 werkzeug의 LocalStack
의 인스턴스다. request
는 여기에 정의되어 있는데, _request_ctx
중 top .request
를 가져오는 LocalProxy
다.
def _lookup_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError('working outside of request context')
return getattr(top, name)
def _find_app():
top = _app_ctx_stack.top
if top is None:
raise RuntimeError('working outside of application context')
return top.app
# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_object, 'request'))
session = LocalProxy(partial(_lookup_object, 'session'))
g = LocalProxy(partial(_lookup_object, 'g'))
LocalStack, LocalProxy
두 클래스는 werkzeug에 정의 돼 있다.
class Local(object):
__slots__ = ('__storage__', '__ident_func__')
def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
def __iter__(self):
return iter(self.__storage__.items())
def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)
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}
def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
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():
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):
"""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)
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
class LocalProxy(object):
__slots__ = ('__local', '__dict__', '__name__')
def __init__(self, local, name=None):
object.__setattr__(self, '_LocalProxy__local', local)
object.__setattr__(self, '__name__', name)
def _get_current_object(self):
"""Return the current object. This is useful if you want the real
object behind the proxy at a time for performance reasons or because
you want to pass the object into a different context.
"""
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__)
@property
def __dict__(self):
try:
return self._get_current_object().__dict__
except RuntimeError:
raise AttributeError('__dict__')
def __repr__(self):
try:
obj = self._get_current_object()
except RuntimeError:
return '<%s unbound>' % self.__class__.__name__
return repr(obj)
def __nonzero__(self):
try:
return bool(self._get_current_object())
except RuntimeError:
return False
def __unicode__(self):
try:
return unicode(self._get_current_object())
except RuntimeError:
return repr(self)
def __dir__(self):
try:
return dir(self._get_current_object())
except RuntimeError:
return []
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]
def __setslice__(self, i, j, seq):
self._get_current_object()[i:j] = seq
def __delslice__(self, i, j):
del self._get_current_object()[i:j]
__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
__ne__ = lambda x, o: x._get_current_object() != o
__gt__ = lambda x, o: x._get_current_object() > o
__ge__ = lambda x, o: x._get_current_object() >= o
__cmp__ = lambda x, o: cmp(x._get_current_object(), o)
__hash__ = lambda x: hash(x._get_current_object())
__call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
__len__ = lambda x: len(x._get_current_object())
__getitem__ = lambda x, i: x._get_current_object()[i]
__iter__ = lambda x: iter(x._get_current_object())
__contains__ = lambda x, i: i in x._get_current_object()
__getslice__ = lambda x, i, j: x._get_current_object()[i:j]
__add__ = lambda x, o: x._get_current_object() + o
__sub__ = lambda x, o: x._get_current_object() - o
__mul__ = lambda x, o: x._get_current_object() * o
__floordiv__ = lambda x, o: x._get_current_object() // o
__mod__ = lambda x, o: x._get_current_object() % o
__divmod__ = lambda x, o: x._get_current_object().__divmod__(o)
__pow__ = lambda x, o: x._get_current_object() ** o
__lshift__ = lambda x, o: x._get_current_object() << o
__rshift__ = lambda x, o: x._get_current_object() >> o
__and__ = lambda x, o: x._get_current_object() & o
__xor__ = lambda x, o: x._get_current_object() ^ o
__or__ = lambda x, o: x._get_current_object() | o
__div__ = lambda x, o: x._get_current_object().__div__(o)
__truediv__ = lambda x, o: x._get_current_object().__truediv__(o)
__neg__ = lambda x: -(x._get_current_object())
__pos__ = lambda x: +(x._get_current_object())
__abs__ = lambda x: abs(x._get_current_object())
__invert__ = lambda x: ~(x._get_current_object())
__complex__ = lambda x: complex(x._get_current_object())
__int__ = lambda x: int(x._get_current_object())
__long__ = lambda x: long(x._get_current_object())
__float__ = lambda x: float(x._get_current_object())
__oct__ = lambda x: oct(x._get_current_object())
__hex__ = lambda x: hex(x._get_current_object())
__index__ = lambda x: x._get_current_object().__index__()
__coerce__ = lambda x, o: x._get_current_object().__coerce__(x, o)
__enter__ = lambda x: x._get_current_object().__enter__()
__exit__ = lambda x, *a, **kw: x._get_current_object().__exit__(*a, **kw)
get_ident
를 이용해서 데이터를 저장하고 가져온다. get_ident
는 현재 문맥을 나타내는 식별자로 2개의 클래스는 모두 개개의 문맥에서 전역적으로 사용할 수 있는 값으로 저장될 수 있다. 스레드별로 다른 요청을 처리하는 경우 각각 요청 문맥이 분리돼야 하기 때문.
environ
을 통해 넘어온 요청 내용은 request_context()
에 의해 Request
객체로 만들어지고 이 객체는 현재 문맥에서 전역적으로 사용할 수 있도록 _request_ctx_stack
에 저장된다. 이를 핸들러에서 Flask.request
로 접근하여 사용한다.
local 설명 작성
https://hustyichi.github.io/2018/08/22/LocalProxy-in-flask/
Local
local이 필요한 이유
thread local
에 저장된 데이터는 이 스레드에서만 데이터를 볼 수 있지만..
서로 다른 coroutine이 동일 스레드에 있는 경우 coroutine간 데이터 분리가 안될 수 있다.
스레드를 사용하는 경우에도 http 요청이 thread local
을 사용해 데이터가 남아 있을 수도 있으므로 WSGI 앱은 각 http 요청에 대해 다른 스레드가 사용되도록 보장할 수 없다.
Werkzeug
는 자체 로컬 객체를 개발했다.
LocalStack
글로벌 스토리지로 사용할 수 있는 스토리지 공간, Local 객체와 유사하나 자료구조는 스택
LocalProxy
Local, LocalStack 객체를 프록시하는 데 사용됨.
LocalProxy 초기화
Local or LocalStack 객체를 통한
__call__
메소드from werkzeug.local import Local l = Local() # these are proxies request = l('request') user = l('user') from werkzeug.local import LocalStack _response_local = LocalStack() # this is a proxy response = _response_local()
Local, LocalStack 모두
__call__
메서드가 있고 객체를 함수로 호출할 때__call__
메서드가 호출됨.__call__
메서드는 LocalProxy 를 반환함LocalProxy 클래스
l = Local() request = LocalProxy(l, 'request')
LocalProxy 사용 이유
from werkzeug.local import LocalStack, LocalProxy
user_stack = LocalStack()
user_stack.push({'name': 'Bob'})
user_stack.push({'name': 'John'})
# Local 객체를 바로 사용하는 경우, name이라는 key의 값이 덮어씌워짐
def get_user():
print(user_stack())
return user_stack.pop()
user = get_user()
print(user['name'])
print(user['name'])
# {'name': 'John'}
# John
# John
user_stack.push({'name': 'Bob'})
user_stack.push({'name': 'John'})
# LocalProxy를 사용하는 경우, 스레드가 분리되어 Bob, John이 존재하는 듯?
def get_user():
print(user_stack())
return user_stack.pop()
user = LocalProxy(get_user)
print(user['name'])
print(user['name'])
# {'name': 'John'}
# John
# {'name': 'Bob'}
# Bob
'Python > Flask' 카테고리의 다른 글
Flask Application Context (1) | 2020.06.08 |
---|