From 0352d972058b432396cd5112a1e39721c31fecba Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Tue, 25 Jul 2017 18:21:09 +0900 Subject: [PATCH 01/16] work for #13 --- CHANGES.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.txt b/CHANGES.txt index d475eae..c1b15ff 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,10 @@ Changes ========================= +unreleased +------------------------- + +- support mypy typings 1.3 ------------------------- From 3891b96387a3574f36e3d411a81705b7f7841511 Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Tue, 25 Jul 2017 20:19:30 +0900 Subject: [PATCH 02/16] added mypy --- .gitignore | 3 ++- .travis.yml | 4 +++- setup.py | 1 + tox.ini | 8 +++++++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 883cb9a..fc95e8f 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ env27 *,cover htmlcov venv -.cache \ No newline at end of file +.cache +.mypy_cache \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index b53da57..7d5a32c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ sudo: false language: python python: - - pypy + # - pypy - 3.4 - 3.5 - 3.6 @@ -17,5 +17,7 @@ jobs: env: TOXENV=flake8 - python: 3.6 env: TOXENV=pylint + - python: 3.6 + env: TOXENV=mypy after_success: - if test "$TOXENV" = coverage ; then pip install coveralls; coveralls ; fi diff --git a/setup.py b/setup.py index 9ad8c25..511ae0d 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ "python-coveralls", "flake8", "pylint", + "mypy", ] here = os.path.dirname(__file__) diff --git a/tox.ini b/tox.ini index b503a4b..a14c86f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py34,py35,py36,pypy,coverage,flake8,pylint +envlist = py34,py35,py36,pypy,coverage,flake8,pylint,mypy [testenv] commands = @@ -23,3 +23,9 @@ basepython = python3.6 commands = pip install -e .[testing] -c constraints.txt pylint webdispatch + +[testenv:mypy] +basepython = python3.6 +commands = + pip install -e .[testing] -c constraints.txt + mypy --ignore-missing-imports webdispatch From 086bbbb5251375457a081d26b0a2fc686d61bc32 Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sat, 27 Jan 2018 16:35:52 +0900 Subject: [PATCH 03/16] added newsfragment --- CHANGES.txt | 5 ----- newsfragments/13.feature | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) create mode 100644 newsfragments/13.feature diff --git a/CHANGES.txt b/CHANGES.txt index c1b15ff..8f88b32 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,11 +1,6 @@ Changes ========================= -unreleased -------------------------- - -- support mypy typings - 1.3 ------------------------- diff --git a/newsfragments/13.feature b/newsfragments/13.feature new file mode 100644 index 0000000..26e7d93 --- /dev/null +++ b/newsfragments/13.feature @@ -0,0 +1,2 @@ +support mypy typings + From d0df5fb68824441857473e549f92c43dd2928b0a Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sat, 27 Jan 2018 17:22:47 +0900 Subject: [PATCH 04/16] added type hints --- webdispatch/uritemplate.py | 52 +++++++++++++++++++++++++++--------- webdispatch/urldispatcher.py | 44 +++++++++++++++++++----------- 2 files changed, 68 insertions(+), 28 deletions(-) diff --git a/webdispatch/uritemplate.py b/webdispatch/uritemplate.py index 6533263..7218588 100644 --- a/webdispatch/uritemplate.py +++ b/webdispatch/uritemplate.py @@ -5,32 +5,51 @@ from datetime import datetime import re import string +from typing import ( # noqa pylint: disable=unused-import + Any, + Dict, + Callable, + Tuple, +) +from mypy_extensions import TypedDict VARS_PT = re.compile(r"{(?P[a-zA-Z0-9_]+)" r"(:(?P[a-zA-Z0-9_]+))?}", re.X) -META_CHARS = ("\\", ".", "^", "$", "*", "+", "|", "?", "(", ")", "[", "]") +META_CHARS = ( + "\\", + ".", + "^", + "$", + "*", + "+", + "|", + "?", + "(", + ")", + "[", + "]") # type: Tuple[str, ...] DEFAULT_CONVERTERS = { 'int': int, 'date': lambda s: datetime.strptime(s, '%Y-%m-%d'), 'date_ym': lambda s: datetime.strptime(s, '%Y-%m'), -} +} # type: Dict[str, Callable] -def regex_replacer(matched): +def regex_replacer(matched) -> str: """ replace url placeholder to regex pattern""" values = matched.groupdict() return "(?P<" + values['varname'] + r">[\w-]+)" -def template_replacer(matched): +def template_replacer(matched) -> str: """ replace url placeholder to template interpolation""" values = matched.groupdict() return "${" + values['varname'] + "}" -def pattern_to_regex(pattern): +def pattern_to_regex(pattern: str) -> str: """ convert url patten to regex """ if pattern and pattern[-1] == "*": pattern = pattern[:-1] @@ -43,12 +62,14 @@ def pattern_to_regex(pattern): return "^" + VARS_PT.sub(regex_replacer, pattern) + end -def pattern_to_template(pattern): +def pattern_to_template(pattern: str) -> str: """ convert url pattern to string template""" return VARS_PT.sub(template_replacer, pattern) -def detect_converters(pattern, converter_dict, default=str): +def detect_converters(pattern: str, + converter_dict: Dict[str, Callable], + default: Callable = str): """ detect pairs of varname and converter from pattern""" converters = {} for matched in VARS_PT.finditer(pattern): @@ -63,11 +84,16 @@ class URITemplateFormatException(Exception): """ raised when uri template format error duaring""" +MATCH_RESULT_TYPE = TypedDict( + "MATCH_RESULT_TYPE", + {"matchdict": Dict[str, Any], "matchlength": int}) + + class URITemplate(object): """ parsing and generating url with patterned """ - def __init__(self, tmpl_pattern, - converters=None): + def __init__(self, tmpl_pattern: str, + converters=None) -> None: if tmpl_pattern.endswith('*') and not tmpl_pattern.endswith('/*'): raise URITemplateFormatException('wildcard must be after slash.') @@ -79,11 +105,11 @@ def __init__(self, tmpl_pattern, self.converters = detect_converters( tmpl_pattern, converters) - def match(self, path_info): + def match(self, path_info: str) -> MATCH_RESULT_TYPE: """ parse path_info and detect urlvars of url pattern """ matched = self.regex.match(path_info) if matched is None: - return matched + return None matchlength = len(matched.group(0)) matchdict = matched.groupdict() @@ -95,7 +121,7 @@ def match(self, path_info): return {"matchdict": matchdict, "matchlength": matchlength} - def convert_values(self, matchdict): + def convert_values(self, matchdict: Dict[str, str]) -> Dict[str, Any]: """ convert values of ``matchdict`` with converter this object has.""" @@ -105,6 +131,6 @@ def convert_values(self, matchdict): converted[varname] = converter(value) return converted - def substitute(self, values): + def substitute(self, values: Dict[str, Any]) -> str: """ generate url with url template""" return self.template.substitute(values) diff --git a/webdispatch/urldispatcher.py b/webdispatch/urldispatcher.py index 44c1180..8eb8fc5 100644 --- a/webdispatch/urldispatcher.py +++ b/webdispatch/urldispatcher.py @@ -3,7 +3,14 @@ """ from collections import OrderedDict from wsgiref.util import application_uri - +from typing import ( # noqa + Any, + Callable, + Dict, + List, + Tuple, + Iterable, +) from .uritemplate import URITemplate from .base import DispatchBase @@ -16,13 +23,13 @@ def __init__(self, converters=None): self.patterns = OrderedDict() self.converters = converters - def add(self, name, pattern): + def add(self, name: str, pattern: str) -> None: """ add url pattern for name """ self.patterns[name] = URITemplate( pattern, converters=self.converters) - def lookup(self, path_info): + def lookup(self, path_info: str) -> Dict[str, Any]: """ lookup url match for path_info """ for name, pattern in self.patterns.items(): @@ -31,8 +38,9 @@ def lookup(self, path_info): continue match["name"] = name return match + return None - def generate(self, name, **kwargs): + def generate(self, name: str, **kwargs: Dict[str, str]) -> str: """ generate url for named url pattern with kwargs """ template = self.patterns[name] @@ -64,25 +72,25 @@ class URLDispatcher(DispatchBase): """ def __init__(self, - applications=None, - extra_environ=None, - **kwargs): + applications=None, # type: List[Callable] + extra_environ=None, # type: Dict[str, Any] + **kwargs) -> None: super(URLDispatcher, self).__init__( applications=applications, extra_environ=extra_environ) converters = kwargs.get('converters') if 'urlmapper' in kwargs: - self.urlmapper = kwargs['urlmapper'] + self.urlmapper = kwargs['urlmapper'] # type: URLMapper else: self.urlmapper = URLMapper(converters=converters) - self.prefix = kwargs.get('prefix', '') + self.prefix = kwargs.get('prefix', '') # type: str - def add_url(self, name, pattern, application): + def add_url(self, name: str, pattern: str, application: Callable) -> None: """ add url pattern dispatching to application""" self.urlmapper.add(name, self.prefix + pattern) self.register_app(name, application) - def add_subroute(self, pattern): + def add_subroute(self, pattern: str) -> DispatchBase: """ create new URLDispatcher routed by pattern """ return URLDispatcher( urlmapper=self.urlmapper, @@ -90,7 +98,7 @@ def add_subroute(self, pattern): applications=self.applications, extra_environ=self.extra_environ) - def detect_view_name(self, environ): + def detect_view_name(self, environ: Dict[str, Any]) -> str: """ detect view name from environ """ script_name = environ.get('SCRIPT_NAME', '') path_info = environ.get('PATH_INFO', '') @@ -99,9 +107,11 @@ def detect_view_name(self, environ): return None extra_path_info = path_info[match["matchlength"]:] - pos_args = [] + pos_args = [] # type: List[str] named_args = match["matchdict"] - cur_pos, cur_named = environ.get('wsgiorg.routing_args', ((), {})) + + routing_args = environ.get('wsgiorg.routing_args', ((), {})) + (cur_pos, cur_named) = routing_args new_pos = list(cur_pos) + list(pos_args) new_named = cur_named.copy() new_named.update(named_args) @@ -114,7 +124,11 @@ def detect_view_name(self, environ): return match["name"] - def on_view_not_found(self, environ, start_response): + def on_view_not_found( + self, + environ: Dict[str, Any], + start_response: Callable[[str, List[Tuple[str, str]]], None], + ) -> Iterable[bytes]: """ called when views not found""" start_response('404 Not Found', [('Content-type', 'text/plain')]) return [b'Not found'] From 4e6351e9aee6c8508a331fde2db4cf3f7604791c Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sat, 27 Jan 2018 17:28:45 +0900 Subject: [PATCH 05/16] added mypy_extensions --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 511ae0d..0f80a86 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ "flake8", "pylint", "mypy", + "mypy_extensions", ] here = os.path.dirname(__file__) From df7d35c41f30445b85698f245dfdb2fe1bdb3d16 Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sat, 27 Jan 2018 17:32:34 +0900 Subject: [PATCH 06/16] fix mypy_extension --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 0f80a86..4e777f3 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,6 @@ "flake8", "pylint", "mypy", - "mypy_extensions", ] here = os.path.dirname(__file__) @@ -41,7 +40,9 @@ def _read(name): long_description=readme + "\n" + changes, test_suite="webdispatch", license="MIT", - install_requires=[], + install_requires=[ + "mypy_extensions", + ], tests_require=tests_require, extras_require={ "testing": tests_require, From 713ca1ceb9bf06fcc97c49fa9f3d6f89363910f8 Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sat, 27 Jan 2018 17:55:24 +0900 Subject: [PATCH 07/16] fix MatchResult type --- setup.py | 4 +--- webdispatch/tests/test_uritemplate.py | 20 +++++++++---------- webdispatch/tests/test_urldispatcher.py | 8 +++++--- webdispatch/uritemplate.py | 26 ++++++++++++++++++------- webdispatch/urldispatcher.py | 17 ++++++++-------- 5 files changed, 43 insertions(+), 32 deletions(-) diff --git a/setup.py b/setup.py index 4e777f3..511ae0d 100644 --- a/setup.py +++ b/setup.py @@ -40,9 +40,7 @@ def _read(name): long_description=readme + "\n" + changes, test_suite="webdispatch", license="MIT", - install_requires=[ - "mypy_extensions", - ], + install_requires=[], tests_require=tests_require, extras_require={ "testing": tests_require, diff --git a/webdispatch/tests/test_uritemplate.py b/webdispatch/tests/test_uritemplate.py index f327d3c..59f4381 100644 --- a/webdispatch/tests/test_uritemplate.py +++ b/webdispatch/tests/test_uritemplate.py @@ -127,8 +127,8 @@ def test_match_empty(self): result = target.match(path) - compare(result["matchdict"], dict()) - compare(result["matchlength"], 0) + compare(result.matchdict, dict()) + compare(result.matchlength, 0) def test_wildcard(self): """ test matching pattern including wildcard""" @@ -136,8 +136,8 @@ def test_wildcard(self): target = self._make_one(path) result = target.match("hoge/egg/bacon") - compare(result["matchdict"], dict(var1="egg")) - compare(result["matchlength"], 9) + compare(result.matchdict, dict(var1="egg")) + compare(result.matchlength, 9) def test_match_no_match(self): """ test no mathing""" @@ -153,8 +153,8 @@ def test_match_match_one(self): target = self._make_one(path) result = target.match("a") - compare(result["matchdict"], dict(var1="a")) - compare(result["matchlength"], 1) + compare(result.matchdict, dict(var1="a")) + compare(result.matchlength, 1) def test_match_match_complex_word(self): """ test matching a string""" @@ -162,7 +162,7 @@ def test_match_match_complex_word(self): target = self._make_one(path) result = target.match("abc") - compare(result["matchdict"], dict(var1="abc")) + compare(result.matchdict, dict(var1="abc")) def test_match_match_many(self): """ test matching pattern including two vars """ @@ -170,7 +170,7 @@ def test_match_match_many(self): target = self._make_one(path) result = target.match("a/users/egg") - compare(result["matchdict"], dict(var1="a", var2="egg")) + compare(result.matchdict, dict(var1="a", var2="egg")) def test_match_conveter(self): """ test matching pattern including specified converter """ @@ -178,7 +178,7 @@ def test_match_conveter(self): target = self._make_one(path) result = target.match("1/users/egg") - compare(result["matchdict"], dict(var1=1, var2="egg")) + compare(result.matchdict, dict(var1=1, var2="egg")) def test_match_conveter_error(self): """ test matching pattern including specified converter """ @@ -198,7 +198,7 @@ def test_match_custom_conveter(self): target = self._make_one(path, converters=converters) result = target.match("1/users/20140420") - compare(result["matchdict"], + compare(result.matchdict, dict(var1="1", var2=datetime(2014, 4, 20))) def test_substitue(self): diff --git a/webdispatch/tests/test_urldispatcher.py b/webdispatch/tests/test_urldispatcher.py index 1b48322..5eac2be 100644 --- a/webdispatch/tests/test_urldispatcher.py +++ b/webdispatch/tests/test_urldispatcher.py @@ -60,12 +60,14 @@ def test_lookup_none(self): def test_lookup(self): """ test looking up basic usage """ + from webdispatch.uritemplate import MatchResult target = self._make_one() target.add('testing-route', 'a') result = target.lookup('a') - compare(result, {'name': 'testing-route', - 'matchdict': {}, - 'matchlength': 1}) + compare(result, C(MatchResult, + name='testing-route', + matchdict={}, + matchlength=1)) def test_generate(self): """ test generating url """ diff --git a/webdispatch/uritemplate.py b/webdispatch/uritemplate.py index 7218588..1f1c3bf 100644 --- a/webdispatch/uritemplate.py +++ b/webdispatch/uritemplate.py @@ -11,7 +11,6 @@ Callable, Tuple, ) -from mypy_extensions import TypedDict VARS_PT = re.compile(r"{(?P[a-zA-Z0-9_]+)" r"(:(?P[a-zA-Z0-9_]+))?}", @@ -84,9 +83,22 @@ class URITemplateFormatException(Exception): """ raised when uri template format error duaring""" -MATCH_RESULT_TYPE = TypedDict( - "MATCH_RESULT_TYPE", - {"matchdict": Dict[str, Any], "matchlength": int}) +class MatchResult: + """ result of parsing url """ + def __init__(self, matchdict: Dict[str, Any], matchlength: int) -> None: + self.name = None # type: str + self.matchdict = matchdict + self.matchlength = matchlength + + def new_named_args(self, cur_named_args: Dict[str, Any]) -> Dict[str, Any]: + """ create new named args updating current name args""" + named_args = cur_named_args.copy() + named_args.update(self.matchdict) + return named_args + + def split_path_info(self, path_info: str) -> Tuple[str, str]: + """ split path_info to new script_name and new path_info""" + return path_info[:self.matchlength], path_info[self.matchlength:] class URITemplate(object): @@ -105,7 +117,7 @@ def __init__(self, tmpl_pattern: str, self.converters = detect_converters( tmpl_pattern, converters) - def match(self, path_info: str) -> MATCH_RESULT_TYPE: + def match(self, path_info: str) -> MatchResult: """ parse path_info and detect urlvars of url pattern """ matched = self.regex.match(path_info) if matched is None: @@ -118,8 +130,8 @@ def match(self, path_info: str) -> MATCH_RESULT_TYPE: except ValueError: return None - return {"matchdict": matchdict, - "matchlength": matchlength} + return MatchResult(matchdict, + matchlength) def convert_values(self, matchdict: Dict[str, str]) -> Dict[str, Any]: """ convert values of ``matchdict`` diff --git a/webdispatch/urldispatcher.py b/webdispatch/urldispatcher.py index 8eb8fc5..c4c03d5 100644 --- a/webdispatch/urldispatcher.py +++ b/webdispatch/urldispatcher.py @@ -11,7 +11,7 @@ Tuple, Iterable, ) -from .uritemplate import URITemplate +from .uritemplate import URITemplate, MatchResult from .base import DispatchBase @@ -29,14 +29,14 @@ def add(self, name: str, pattern: str) -> None: self.patterns[name] = URITemplate( pattern, converters=self.converters) - def lookup(self, path_info: str) -> Dict[str, Any]: + def lookup(self, path_info: str) -> MatchResult: """ lookup url match for path_info """ for name, pattern in self.patterns.items(): match = pattern.match(path_info) if match is None: continue - match["name"] = name + match.name = name return match return None @@ -106,23 +106,22 @@ def detect_view_name(self, environ: Dict[str, Any]) -> str: if match is None: return None - extra_path_info = path_info[match["matchlength"]:] + splited = match.split_path_info(path_info) + extra_path_info = splited[1] pos_args = [] # type: List[str] - named_args = match["matchdict"] routing_args = environ.get('wsgiorg.routing_args', ((), {})) (cur_pos, cur_named) = routing_args new_pos = list(cur_pos) + list(pos_args) - new_named = cur_named.copy() - new_named.update(named_args) + new_named = match.new_named_args(cur_named) environ['wsgiorg.routing_args'] = (new_pos, new_named) environ['webdispatch.urlmapper'] = self.urlmapper urlgenerator = URLGenerator(environ, self.urlmapper) environ['webdispatch.urlgenerator'] = urlgenerator - environ['SCRIPT_NAME'] = script_name + path_info[:match["matchlength"]] + environ['SCRIPT_NAME'] = script_name + splited[0] environ['PATH_INFO'] = extra_path_info - return match["name"] + return match.name def on_view_not_found( self, From beaebe6deb77bac7b9e00688556b6a2a639d0962 Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sun, 28 Jan 2018 01:22:52 +0900 Subject: [PATCH 08/16] fix 3.4 --- setup.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 511ae0d..6e78dbd 100644 --- a/setup.py +++ b/setup.py @@ -17,12 +17,14 @@ readme = None changes = None + def _read(name): try: return open(os.path.join(here, name)).read() - except: + except Exception: return "" + readme = _read("README.rst") changes = _read("CHANGES.txt") @@ -40,7 +42,9 @@ def _read(name): long_description=readme + "\n" + changes, test_suite="webdispatch", license="MIT", - install_requires=[], + install_requires=[ + "typing; python_version < '3.5'", + ], tests_require=tests_require, extras_require={ "testing": tests_require, From d1aefb2c18f0b14481e3ba3e7db7a6ffb2541422 Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sun, 28 Jan 2018 17:28:21 +0900 Subject: [PATCH 09/16] drop supporting pypy --- .travis.yml | 1 - tox.ini | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7d5a32c..24ecec3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ sudo: false language: python python: - # - pypy - 3.4 - 3.5 - 3.6 diff --git a/tox.ini b/tox.ini index a14c86f..90d5236 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py34,py35,py36,pypy,coverage,flake8,pylint,mypy +envlist = py34,py35,py36,coverage,flake8,pylint,mypy [testenv] commands = From 56cfc88014e6d5effe1c1eaa3d1ba38d79d677a5 Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sun, 28 Jan 2018 17:53:12 +0900 Subject: [PATCH 10/16] added type hints --- webdispatch/base.py | 27 +++++++++++++++++++-------- webdispatch/mixins.py | 8 ++++++-- webdispatch/urldispatcher.py | 8 ++++---- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/webdispatch/base.py b/webdispatch/base.py index d8ea94f..501c8b5 100644 --- a/webdispatch/base.py +++ b/webdispatch/base.py @@ -1,22 +1,26 @@ """ base dispatchers """ +from typing import Dict, Any, Callable, Iterable class DispatchBase(object): """ Base class for dispatcher application""" - def __init__(self, applications=None, extra_environ=None): + def __init__( + self, + applications: Dict[str, Callable]=None, + extra_environ: Dict[str, Any]=None) -> None: if applications is None: - self.applications = {} + self.applications = {} # type: Dict[str, Callable] else: self.applications = applications if extra_environ is None: - self.extra_environ = {} + self.extra_environ = {} # type: Dict[str, Any] else: self.extra_environ = extra_environ - def register_app(self, name, app=None): + def register_app(self, name: str, app: Callable=None) -> Callable: """ register dispatchable wsgi application""" if app is None: def dec(app): @@ -26,20 +30,27 @@ def dec(app): return app return dec self.applications[name] = app + return None - def get_extra_environ(self): + 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): # pragma: nocover + def detect_view_name( + self, environ: Dict[str, Any]) -> str: # pragma: nocover """ must returns view name for request """ raise NotImplementedError() - def on_view_not_found(self, environ, start_response): # pragma: nocover + def on_view_not_found( + self, + environ: Dict[str, Any], + start_response: Callable) -> Iterable[bytes]: # pragma: nocover """ called when view is not found""" raise NotImplementedError() - def __call__(self, environ, start_response): + 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) diff --git a/webdispatch/mixins.py b/webdispatch/mixins.py index 162d1b6..de103be 100644 --- a/webdispatch/mixins.py +++ b/webdispatch/mixins.py @@ -1,15 +1,19 @@ """ useful mixin classes """ +from typing import Dict, Any # noqa pylint: disable=unused-import +from .urldispatcher import URLGenerator class URLMapperMixin(object): """ mixin to add :meth:`generate_url` method. """ - def generate_url(self, name, **kwargs): + environ = {} # type: Dict[str, Any] + + def generate_url(self, name: str, **kwargs) -> str: """ generate url with urlgenerator used by urldispatch""" return self.urlmapper.generate(name, **kwargs) @property - def urlmapper(self): + def urlmapper(self) -> URLGenerator: """ get urlmapper object from wsgi environ """ return self.environ['webdispatch.urlgenerator'] diff --git a/webdispatch/urldispatcher.py b/webdispatch/urldispatcher.py index c4c03d5..7675b98 100644 --- a/webdispatch/urldispatcher.py +++ b/webdispatch/urldispatcher.py @@ -51,18 +51,18 @@ class URLGenerator(object): """ generate url form parameters and url patterns. """ - def __init__(self, environ, urlmapper): + def __init__(self, environ: Dict[str, Any], urlmapper: URLMapper) -> None: self.environ = environ self.urlmapper = urlmapper self.application_uri = application_uri(environ) - def generate(self, name, **kwargs): + def generate(self, name: str, **kwargs): """ generate full qualified url for named url pattern with kwargs """ path = self.urlmapper.generate(name, **kwargs) return self.make_full_qualified_url(path) - def make_full_qualified_url(self, path): + def make_full_qualified_url(self, path: str) -> str: """ append application url to path""" return self.application_uri.rstrip('/') + '/' + path.lstrip('/') @@ -72,7 +72,7 @@ class URLDispatcher(DispatchBase): """ def __init__(self, - applications=None, # type: List[Callable] + applications=None, # type: Dict[str, Callable] extra_environ=None, # type: Dict[str, Any] **kwargs) -> None: super(URLDispatcher, self).__init__( From f065c3d1816ad57b5f938002934b3e483691c6b7 Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sun, 28 Jan 2018 18:40:15 +0900 Subject: [PATCH 11/16] added type hints --- webdispatch/urldispatcher.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/webdispatch/urldispatcher.py b/webdispatch/urldispatcher.py index 7675b98..e76f718 100644 --- a/webdispatch/urldispatcher.py +++ b/webdispatch/urldispatcher.py @@ -1,6 +1,7 @@ """ urldispatcher """ +from abc import ABC, abstractmethod from collections import OrderedDict from wsgiref.util import application_uri from typing import ( # noqa @@ -19,7 +20,7 @@ class URLMapper(object): """ find application matched url pattern. """ - def __init__(self, converters=None): + def __init__(self, converters: Dict[str, Callable]=None): self.patterns = OrderedDict() self.converters = converters @@ -72,25 +73,27 @@ class URLDispatcher(DispatchBase): """ def __init__(self, - applications=None, # type: Dict[str, Callable] - extra_environ=None, # type: Dict[str, Any] - **kwargs) -> None: + *, + applications: Dict[str, Callable]=None, + extra_environ: Dict[str, Any]=None, + converters: Dict[str, Callable]=None, + urlmapper: URLMapper=None, + prefix: str="") -> None: super(URLDispatcher, self).__init__( applications=applications, extra_environ=extra_environ) - converters = kwargs.get('converters') - if 'urlmapper' in kwargs: - self.urlmapper = kwargs['urlmapper'] # type: URLMapper + if urlmapper: + self.urlmapper = urlmapper else: self.urlmapper = URLMapper(converters=converters) - self.prefix = kwargs.get('prefix', '') # type: str + self.prefix = prefix def add_url(self, name: str, pattern: str, application: Callable) -> None: """ add url pattern dispatching to application""" self.urlmapper.add(name, self.prefix + pattern) self.register_app(name, application) - def add_subroute(self, pattern: str) -> DispatchBase: + def add_subroute(self, pattern: str) -> "URLDispatcher": """ create new URLDispatcher routed by pattern """ return URLDispatcher( urlmapper=self.urlmapper, From 5fd75c73e3cc70855a31f85ca16b415808d89f7f Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sun, 28 Jan 2018 18:43:16 +0900 Subject: [PATCH 12/16] fix pylint --- webdispatch/base.py | 6 +++--- webdispatch/urldispatcher.py | 13 ++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/webdispatch/base.py b/webdispatch/base.py index 501c8b5..437fbd2 100644 --- a/webdispatch/base.py +++ b/webdispatch/base.py @@ -8,8 +8,8 @@ class DispatchBase(object): def __init__( self, - applications: Dict[str, Callable]=None, - extra_environ: Dict[str, Any]=None) -> None: + applications: Dict[str, Callable] = None, + extra_environ: Dict[str, Any] = None) -> None: if applications is None: self.applications = {} # type: Dict[str, Callable] @@ -20,7 +20,7 @@ def __init__( else: self.extra_environ = extra_environ - def register_app(self, name: str, app: Callable=None) -> Callable: + def register_app(self, name: str, app: Callable = None) -> Callable: """ register dispatchable wsgi application""" if app is None: def dec(app): diff --git a/webdispatch/urldispatcher.py b/webdispatch/urldispatcher.py index e76f718..9cf3e32 100644 --- a/webdispatch/urldispatcher.py +++ b/webdispatch/urldispatcher.py @@ -1,7 +1,6 @@ """ urldispatcher """ -from abc import ABC, abstractmethod from collections import OrderedDict from wsgiref.util import application_uri from typing import ( # noqa @@ -20,7 +19,7 @@ class URLMapper(object): """ find application matched url pattern. """ - def __init__(self, converters: Dict[str, Callable]=None): + def __init__(self, converters: Dict[str, Callable] = None): self.patterns = OrderedDict() self.converters = converters @@ -74,11 +73,11 @@ class URLDispatcher(DispatchBase): def __init__(self, *, - applications: Dict[str, Callable]=None, - extra_environ: Dict[str, Any]=None, - converters: Dict[str, Callable]=None, - urlmapper: URLMapper=None, - prefix: str="") -> None: + applications: Dict[str, Callable] = None, + extra_environ: Dict[str, Any] = None, + converters: Dict[str, Callable] = None, + urlmapper: URLMapper = None, + prefix: str = "") -> None: super(URLDispatcher, self).__init__( applications=applications, extra_environ=extra_environ) From 869de70216d68fb6cca20e2e68f1e9eaf73487f5 Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sun, 28 Jan 2018 18:48:18 +0900 Subject: [PATCH 13/16] fix mypy --- webdispatch/urldispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webdispatch/urldispatcher.py b/webdispatch/urldispatcher.py index 9cf3e32..5eda492 100644 --- a/webdispatch/urldispatcher.py +++ b/webdispatch/urldispatcher.py @@ -19,8 +19,8 @@ class URLMapper(object): """ find application matched url pattern. """ - def __init__(self, converters: Dict[str, Callable] = None): - self.patterns = OrderedDict() + def __init__(self, converters: Dict[str, Callable] = None) -> None: + self.patterns = OrderedDict() # type: Dict[str, URITemplate] self.converters = converters def add(self, name: str, pattern: str) -> None: From 9fc1d55751d7846c8edb242336d4bcdfa94b0e75 Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sun, 28 Jan 2018 19:20:32 +0900 Subject: [PATCH 14/16] added type hints --- webdispatch/methoddispatcher.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/webdispatch/methoddispatcher.py b/webdispatch/methoddispatcher.py index e43f4fa..2a2eaca 100644 --- a/webdispatch/methoddispatcher.py +++ b/webdispatch/methoddispatcher.py @@ -1,7 +1,7 @@ """ methoddispatcher """ - +from typing import Dict, Any, Iterable, Callable, List, Tuple from wsgiref.util import application_uri from .base import DispatchBase @@ -9,16 +9,19 @@ class MethodDispatcher(DispatchBase): """ dispatch applications with request method. """ - def __init__(self, **kwargs): + 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): + def detect_view_name(self, environ: Dict[str, Any]) -> str: """ convert request method to view name """ return environ['REQUEST_METHOD'].lower() - def on_view_not_found(self, _, start_response): + def on_view_not_found( + self, _, + start_response: Callable[[str, List[Tuple[str, str]]], None], + ) -> Iterable[bytes]: """ called when valid view is not found """ start_response( @@ -27,7 +30,7 @@ def on_view_not_found(self, _, start_response): return [b"Method Not Allowed"] -def action_handler_adapter(handler_cls, action_name): +def action_handler_adapter(handler_cls: type, action_name: str) -> Callable: """ wraps class to wsgi application dispathing action""" if not hasattr(handler_cls(), action_name): @@ -44,11 +47,11 @@ def wsgiapp(environ, start_response): class ActionDispatcher(DispatchBase): """ wsgi application dispatching actions to registered classes""" - def __init__(self, action_var_name='action'): + 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): + def register_actionhandler(self, action_handler: type) -> None: """ register class as action handler """ for k in action_handler.__dict__: if k.startswith('_'): @@ -56,12 +59,15 @@ def register_actionhandler(self, action_handler): app = action_handler_adapter(action_handler, k) self.register_app(k, app) - def detect_view_name(self, environ): + def detect_view_name(self, environ: Dict[str, Any]) -> str: """ get view name from routing args """ urlvars = environ.get('wsgiorg.routing_args', [(), {}])[1] return urlvars.get(self.action_var_name) - def on_view_not_found(self, environ, start_response): + def on_view_not_found( + 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", From cbd7bdaf149484fb94a7442f40def9ad954db023 Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sun, 28 Jan 2018 19:26:35 +0900 Subject: [PATCH 15/16] fix pylint --- webdispatch/methoddispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webdispatch/methoddispatcher.py b/webdispatch/methoddispatcher.py index 2a2eaca..efdfdec 100644 --- a/webdispatch/methoddispatcher.py +++ b/webdispatch/methoddispatcher.py @@ -21,7 +21,7 @@ def detect_view_name(self, environ: Dict[str, Any]) -> str: def on_view_not_found( self, _, start_response: Callable[[str, List[Tuple[str, str]]], None], - ) -> Iterable[bytes]: + ) -> Iterable[bytes]: """ called when valid view is not found """ start_response( @@ -67,7 +67,7 @@ def detect_view_name(self, environ: Dict[str, Any]) -> str: def on_view_not_found( self, environ: Dict[str, Any], start_response: Callable[[str, List[Tuple[str, str]]], None], - ) -> Iterable[bytes]: + ) -> Iterable[bytes]: """ called when action is not found """ start_response( "404 Not Found", From ffb9108ae7c28e71c1e925270dff8d69181a0b1c Mon Sep 17 00:00:00 2001 From: Atsushi Odagiri Date: Sun, 28 Jan 2018 19:38:33 +0900 Subject: [PATCH 16/16] flake8 --- webdispatch/methoddispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webdispatch/methoddispatcher.py b/webdispatch/methoddispatcher.py index efdfdec..6215387 100644 --- a/webdispatch/methoddispatcher.py +++ b/webdispatch/methoddispatcher.py @@ -21,7 +21,7 @@ def detect_view_name(self, environ: Dict[str, Any]) -> str: def on_view_not_found( self, _, start_response: Callable[[str, List[Tuple[str, str]]], None], - ) -> Iterable[bytes]: + ) -> Iterable[bytes]: """ called when valid view is not found """ start_response( @@ -67,7 +67,7 @@ def detect_view_name(self, environ: Dict[str, Any]) -> str: def on_view_not_found( self, environ: Dict[str, Any], start_response: Callable[[str, List[Tuple[str, str]]], None], - ) -> Iterable[bytes]: + ) -> Iterable[bytes]: """ called when action is not found """ start_response( "404 Not Found",