Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
sudo: false
language: python
cache: pip
python:
- 3.4
- 3.5
Expand Down
61 changes: 41 additions & 20 deletions constraints.txt
Original file line number Diff line number Diff line change
@@ -1,20 +1,41 @@
beautifulsoup4==4.6.0
certifi==2017.4.17
chardet==3.0.4
colorama==0.3.9
coverage==4.4.1
idna==2.5
mock==2.0.0
pbr==3.1.1
py==1.4.34
pytest==3.1.3
pytest-cov==2.5.1
python-coveralls==2.9.1
PyYAML==3.12
requests==2.18.1
six==1.10.0
testfixtures==5.1.1
urllib3==1.21.1
waitress==1.0.2
WebOb==1.7.3
WebTest==2.0.27
appdirs==1.4.3
astroid==2.1.0
atomicwrites==1.3.0
attrs==18.2.0
beautifulsoup4==4.7.1
black==18.9b0
certifi==2018.11.29
chardet==3.0.4
Click==7.0
coverage==4.5.2
entrypoints==0.3
flake8==3.7.5
idna==2.8
isort==4.3.4
lazy-object-proxy==1.3.1
mccabe==0.6.1
mock==2.0.0
more-itertools==5.0.0
mypy==0.670
mypy-extensions==0.4.1
pbr==5.1.2
pluggy==0.8.1
py==1.7.0
pycodestyle==2.5.0
pyflakes==2.1.0
pylint==2.2.2
pytest==4.2.0
pytest-cov==2.6.1
python-coveralls==2.9.1
PyYAML==3.13
requests==2.21.0
six==1.12.0
soupsieve==1.7.3
testfixtures==6.5.0
toml==0.10.0
typed-ast==1.3.1
urllib3==1.24.1
waitress==1.2.1
WebOb==1.8.5
WebTest==2.0.33
wrapt==1.11.1
5 changes: 4 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ with-doctest = 1
universal = 1

[metadata]
version = attr:webdispatch.__version__
version = attr:webdispatch.__version__

[flake8]
max-line-length = 88
27 changes: 15 additions & 12 deletions webdispatch/base.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
""" base dispatchers
"""
from typing import Dict, Any, Callable, Iterable
from typing import Dict, Any, Callable, Iterable, Optional


class DispatchBase(object):
""" Base class for dispatcher application"""

def __init__(
self,
applications: Dict[str, Callable] = None,
extra_environ: Dict[str, Any] = None) -> None:
self,
applications: Dict[str, Callable] = None,
extra_environ: Dict[str, Any] = None,
) -> None:

if applications is None:
self.applications = {} # type: Dict[str, Callable]
Expand All @@ -23,34 +24,36 @@ def __init__(
def register_app(self, name: str, app: Callable = None) -> Callable:
""" register dispatchable wsgi application"""
if app is None:

def dec(app):
""" inner decorator for register app """
assert app is not None
self.register_app(name, app)
return app

return dec
self.applications[name] = app
return None
return app

def get_extra_environ(self) -> Dict[str, Any]:
""" returns for environ values for wsgi environ"""
return self.extra_environ

def detect_view_name(
self, environ: Dict[str, Any]) -> str: # pragma: nocover
self, environ: Dict[str, Any]
) -> Optional[str]: # pragma: nocover
""" must returns view name for request """
raise NotImplementedError()

def on_view_not_found(
self,
environ: Dict[str, Any],
start_response: Callable) -> Iterable[bytes]: # pragma: nocover
self, environ: Dict[str, Any], start_response: Callable
) -> Iterable[bytes]: # pragma: nocover
""" called when view is not found"""
raise NotImplementedError()

def __call__(self,
environ: Dict[str, Any],
start_response: Callable) -> Iterable[bytes]:
def __call__(
self, environ: Dict[str, Any], start_response: Callable
) -> Iterable[bytes]:
extra_environ = self.get_extra_environ()
environ.update(extra_environ)
view_name = self.detect_view_name(environ)
Expand Down
12 changes: 4 additions & 8 deletions webdispatch/dummyapps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@

def greeting(_, start_response):
""" dummy application returns Hello simply"""
start_response(
'200 OK',
[('Content-type', 'text/plain')])
return [b'Hello']
start_response("200 OK", [("Content-type", "text/plain")])
return [b"Hello"]


def bye(_, start_response):
""" dummy application returns by simply"""
start_response(
'200 OK',
[('Content-type', 'text/plain')])
return [b'bye']
start_response("200 OK", [("Content-type", "text/plain")])
return [b"bye"]
28 changes: 13 additions & 15 deletions webdispatch/methoddispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,22 @@
class MethodDispatcher(DispatchBase):
""" dispatch applications with request method.
"""

def __init__(self, **kwargs) -> None:
super(MethodDispatcher, self).__init__()
for name, app in kwargs.items():
self.register_app(name, app)

def detect_view_name(self, environ: Dict[str, Any]) -> str:
""" convert request method to view name """
return environ['REQUEST_METHOD'].lower()
return environ["REQUEST_METHOD"].lower()

def on_view_not_found(
self, _,
start_response: Callable[[str, List[Tuple[str, str]]], None],
self, _, start_response: Callable[[str, List[Tuple[str, str]]], None]
) -> Iterable[bytes]:
""" called when valid view is not found """

start_response(
"405 Method Not Allowed",
[('Content-type', 'text/plain')])
start_response("405 Method Not Allowed", [("Content-type", "text/plain")])
return [b"Method Not Allowed"]


Expand All @@ -41,35 +39,35 @@ def wsgiapp(environ, start_response):
""" inner app """
handler = handler_cls()
return getattr(handler, action_name)(environ, start_response)

return wsgiapp


class ActionDispatcher(DispatchBase):
""" wsgi application dispatching actions to registered classes"""

def __init__(self, action_var_name: str = 'action') -> None:
def __init__(self, action_var_name: str = "action") -> None:
super(ActionDispatcher, self).__init__()
self.action_var_name = action_var_name

def register_actionhandler(self, action_handler: type) -> None:
""" register class as action handler """
for k in action_handler.__dict__:
if k.startswith('_'):
if k.startswith("_"):
continue
app = action_handler_adapter(action_handler, k)
self.register_app(k, app)

def detect_view_name(self, environ: Dict[str, Any]) -> str:
""" get view name from routing args """
urlvars = environ.get('wsgiorg.routing_args', [(), {}])[1]
urlvars = environ.get("wsgiorg.routing_args", [(), {}])[1]
return urlvars.get(self.action_var_name)

def on_view_not_found(
self, environ: Dict[str, Any],
start_response: Callable[[str, List[Tuple[str, str]]], None],
self,
environ: Dict[str, Any],
start_response: Callable[[str, List[Tuple[str, str]]], None],
) -> Iterable[bytes]:
""" called when action is not found """
start_response(
"404 Not Found",
[('Content-type', 'text/plain')])
return [b"Not Found ", application_uri(environ).encode('utf-8')]
start_response("404 Not Found", [("Content-type", "text/plain")])
return [b"Not Found ", application_uri(environ).encode("utf-8")]
3 changes: 2 additions & 1 deletion webdispatch/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
class URLMapperMixin(object):
""" mixin to add :meth:`generate_url` method.
"""

environ = {} # type: Dict[str, Any]

def generate_url(self, name: str, **kwargs) -> str:
Expand All @@ -16,4 +17,4 @@ def generate_url(self, name: str, **kwargs) -> str:
@property
def urlmapper(self) -> URLGenerator:
""" get urlmapper object from wsgi environ """
return self.environ['webdispatch.urlgenerator']
return self.environ["webdispatch.urlgenerator"]
4 changes: 1 addition & 3 deletions webdispatch/paster.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@

def make_urldispatch_application(_, **settings):
""" paste.app_factory interface for URLDispatcher"""
patterns = [p.split("=", 1)
for p in settings['patterns'].split('\n')
if p]
patterns = [p.split("=", 1) for p in settings["patterns"].split("\n") if p]
application = URLDispatcher()

for pattern, app in patterns:
Expand Down
7 changes: 3 additions & 4 deletions webdispatch/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ def setup_environ(**kwargs):
""" setup basic wsgi environ"""
environ = {}
from wsgiref.util import setup_testing_defaults

setup_testing_defaults(environ)
environ.update(kwargs)
return environ
Expand All @@ -15,9 +16,7 @@ def make_env(path_info, script_name):
""" set up basic wsgi environ"""

from wsgiref.util import setup_testing_defaults
environ = {
"PATH_INFO": path_info,
"SCRIPT_NAME": script_name,
}

environ = {"PATH_INFO": path_info, "SCRIPT_NAME": script_name}
setup_testing_defaults(environ)
return environ
9 changes: 5 additions & 4 deletions webdispatch/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class TestDispatchBase(object):
def _get_target():
""" get class under test """
from webdispatch.base import DispatchBase

return DispatchBase

def _make_one(self, *args, **kwargs):
Expand All @@ -28,19 +29,19 @@ def test_init_apps(self):
""" init with app and no environ"""

testing_app = object()
result = self._make_one(applications={'testing': testing_app})
result = self._make_one(applications={"testing": testing_app})

compare(result.applications, {'testing': testing_app})
compare(result.applications, {"testing": testing_app})
compare(result.extra_environ, {})

def test_init_env(self):
""" init with app and no environ"""

environ = {'test_value': 1}
environ = {"test_value": 1}
result = self._make_one(extra_environ=environ)

compare(result.applications, {})
compare(result.extra_environ, {'test_value': 1})
compare(result.extra_environ, {"test_value": 1})

def test_not_found(self):
""" init with app and no environ"""
Expand Down
Loading