import inspect
import werkzeug
from . import lib
from . import gateway
[docs]class Directory(object):
#: Tuple should be presented as ('/path', Resource, [namespace])
resources = None
host = 'localhost'
def __init__(self, parent=None, **config):
self._parent = parent
self.functions = {}
self.rules = werkzeug.routing.Map()
self.adapter = self.rules.bind(self.host)
for resource in self.resources or ():
self.add(*resource)
self.setup()
[docs] def match(self, path_info, method):
endpoint, kwargs = self.adapter.match(path_info, method)
func = self.functions[endpoint]
return func, kwargs
[docs] def add(self, path, resource, prefix=''):
if inspect.isfunction(resource):
self._add_function(path, resource, prefix)
elif inspect.isclass(resource):
self._add_class(path, resource(), prefix)
else:
self._add_class(path, resource, prefix)
@property
def parent(self):
return self._parent or self
@property
def root(self):
_root = self
while _root._parent:
_root = _root.parent
return _root
[docs] def _add_class(self, path, resource, prefix=''):
members = lib.get_resource_members(resource)
if not members:
raise ValueError(
"There is no endpoint in the given resource: %s" % resource
)
if not prefix:
prefix = getattr(resource, '_name', lib.get_fqname(resource))
for member in members:
opts = lib.get_meta(member)
self._add_function(path+opts['werkzeug_path'], member, prefix)
[docs] def _add_function(self, path, resource, prefix=''):
opts = lib.get_meta(resource)
if opts:
if prefix:
prefix += '#'
name = prefix+opts['name']
self.rules.add(self._make_rule(path, opts['methods'], name))
if 'forward' in opts:
if not path.endswith('/'):
path += '/'
self.rules.add(self._make_rule(
path+'<path:path>', opts['methods'], name
))
self.functions[name] = resource
else:
raise ValueError(
"The given function (%s) is not and endpoint endpoint"
% resource
)
[docs] def _make_rule(self, path, methods, endpoint):
return werkzeug.routing.Rule(path, methods=methods, endpoint=endpoint)
[docs]class Scope(object):
Response = gateway.Response
def __init__(self, request, application):
self.request = request
self.application = application
@property
def response(self):
if not getattr(self, '_response', None):
self._response = self.Response()
self._response.parse_request(self.request)
return self._response
[docs] def forward(self, resource, path='/'):
return self.application.forward(self, path, resource)
[docs]class Dispatcher(Directory):
Response = gateway.Response
[docs] def dispatch(self, request, path=None, scope=None):
if path is None:
path = request.path
if scope is None:
scope = Scope(request=request, application=self)
try:
func, kwargs = self.match(path, request.method)
meta = lib.get_meta(func)
kwargs.update(request.parse(meta))
if meta.get('scope') is True:
kwargs.update(scope=scope)
content = func(**kwargs)
if isinstance(content, gateway.Response):
return content
return scope.response.produce(request, content)
except Exception as ex:
return self.Response.from_exception(ex)
@classmethod
[docs] def forward(cls, scope, path, resource):
if isinstance(resource, Dispatcher):
return resource.dispatch(scope.request, path, scope)
dispatcher = Dispatcher()
dispatcher.add('', resource)
return dispatcher.dispatch(scope.request, path, scope)
[docs]class App(Dispatcher):
@gateway.Request.application
[docs] def wsgi(self, request):
return self.dispatch(request)