diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 00000000..ba2778dc --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,6 @@ +[settings] +multi_line_output=3 +include_trailing_comma=True +force_grid_wrap=0 +use_parentheses=True +line_length=88 diff --git a/.travis.yml b/.travis.yml index e50d3a4a..9fbf9433 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,23 @@ matrix: - os: linux sudo: required python: 3.7 - env: TOXENV=py37-marshmallow2 + env: TOXENV=py37-marshmallow3 + - os: linux + sudo: required + python: 3.7 + env: TOXENV=py37-black + - os: linux + sudo: required + python: 3.7 + env: TOXENV=py37-flake8 + - os: linux + sudo: required + python: 3.7 + env: TOXENV=py37-bandit + - os: linux + sudo: required + python: 3.7 + env: TOXENV=py37-vulture - os: linux sudo: required python: pypy3.5-6.0 diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000..b9ba84f6 --- /dev/null +++ b/Pipfile @@ -0,0 +1,11 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] + +[dev-packages] + +[requires] +python_version = "3.7" diff --git a/hug/__init__.py b/hug/__init__.py index 297ba78c..c5d78270 100644 --- a/hug/__init__.py +++ b/hug/__init__.py @@ -34,7 +34,7 @@ from falcon import * from hug import ( - authentication, + defaults, directives, exceptions, format, @@ -89,11 +89,11 @@ ) from hug.types import create as type -# The following imports must be imported last for defaults to have access to all modules +# The following imports must be imported last; in particular, defaults to have access to all modules +from hug import authentication #isort:skip from hug import development_runner # isort:skip from hug import defaults # isort:skip - try: # pragma: no cover - defaulting to uvloop if it is installed import uvloop import asyncio diff --git a/hug/api.py b/hug/api.py index 0a6c35b1..1bb9f669 100644 --- a/hug/api.py +++ b/hug/api.py @@ -78,6 +78,7 @@ def __init__(self, api): class HTTPInterfaceAPI(InterfaceAPI): """Defines the HTTP interface specific API""" + __slots__ = ( "routes", "versions", @@ -123,10 +124,10 @@ def urls(self): def handlers(self): """Returns all registered handlers attached to this API""" used = [] - for base_url, mapping in self.routes.items(): - for url, methods in mapping.items(): - for method, versions in methods.items(): - for version, handler in versions.items(): + for _base_url, mapping in self.routes.items(): + for _url, methods in mapping.items(): + for _method, versions in methods.items(): + for _version, handler in versions.items(): if not handler in used: used.append(handler) yield handler @@ -179,15 +180,15 @@ def extend(self, http_api, route="", base_url="", **kwargs): self.versions.update(http_api.versions) base_url = base_url or self.base_url - for router_base_url, routes in http_api.routes.items(): + for _router_base_url, routes in http_api.routes.items(): self.routes.setdefault(base_url, OrderedDict()) for item_route, handler in routes.items(): - for method, versions in handler.items(): - for version, function in versions.items(): + for _method, versions in handler.items(): + for _version, function in versions.items(): function.interface.api = self.api self.routes[base_url].setdefault(route + item_route, {}).update(handler) - for sink_base_url, sinks in http_api.sinks.items(): + for _sink_base_url, sinks in http_api.sinks.items(): for url, sink in sinks.items(): self.add_sink(sink, route + url, base_url=base_url) @@ -344,9 +345,11 @@ def handle_404(request, response, *args, **kwargs): return handle_404 def version_router( - self, request, response, api_version=None, versions={}, not_found=None, **kwargs + self, request, response, api_version=None, versions=None, not_found=None, **kwargs ): """Intelligently routes a request to the correct handler based on the version being requested""" + if versions is None: + versions = {} request_version = self.determine_version(request, api_version) if request_version: request_version = int(request_version) @@ -551,9 +554,9 @@ def add_directive(self, directive): def handlers(self): """Returns all registered handlers attached to this API""" - if getattr(self, "_http"): + if getattr(self, "_http", None): yield from self.http.handlers() - if getattr(self, "_cli"): + if getattr(self, "_cli", None): yield from self.cli.handlers() @property diff --git a/hug/interface.py b/hug/interface.py index 0539fbfe..42065774 100644 --- a/hug/interface.py +++ b/hug/interface.py @@ -117,7 +117,7 @@ def __init__(self, function, args=None): self.input_transformations[name] = transformer - def __call__(__hug_internal_self, *args, **kwargs): + def __call__(__hug_internal_self, *args, **kwargs): # noqa: N805 """"Calls the wrapped function, uses __hug_internal_self incase self is passed in as a kwarg from the wrapper""" if not __hug_internal_self.is_coroutine: return __hug_internal_self._function(*args, **kwargs) @@ -270,7 +270,7 @@ def validate(self, input_parameters, context): type_handler, input_parameters[key], context=context ) except InvalidTypeData as error: - errors[key] = error.reasons or str(error.message) + errors[key] = error.reasons or str(error) except Exception as error: if hasattr(error, "args") and error.args: errors[key] = error.args[0] @@ -342,7 +342,7 @@ def _rewrite_params(self, params): @staticmethod def cleanup_parameters(parameters, exception=None): - for parameter, directive in parameters.items(): + for _parameter, directive in parameters.items(): if hasattr(directive, "cleanup"): directive.cleanup(exception=exception) @@ -385,7 +385,7 @@ def __call__(self, *args, **kwargs): context = self.api.context_factory(api=self.api, api_version=self.version, interface=self) """Defines how calling the function locally should be handled""" - for requirement in self.requires: + for _requirement in self.requires: lacks_requirement = self.check_requirements(context=context) if lacks_requirement: self.api.delete_context(context, lacks_requirement=lacks_requirement) @@ -604,7 +604,7 @@ def exit_callback(message): args.extend(pass_to_function.pop(self.additional_options, ())) if self.interface.takes_kwargs: add_options_to = None - for index, option in enumerate(unknown): + for option in unknown: if option.startswith("--"): if add_options_to: value = pass_to_function[add_options_to] @@ -959,9 +959,9 @@ def documentation(self, add_to=None, version=None, prefix="", base_url="", url=" def urls(self, version=None): """Returns all URLS that are mapped to this interface""" urls = [] - for base_url, routes in self.api.http.routes.items(): + for _base_url, routes in self.api.http.routes.items(): for url, methods in routes.items(): - for method, versions in methods.items(): + for _method, versions in methods.items(): for interface_version, interface in versions.items(): if interface_version == version and interface == self: if not url in urls: diff --git a/hug/json_module.py b/hug/json_module.py index 5df4f4ae..f881f7f8 100644 --- a/hug/json_module.py +++ b/hug/json_module.py @@ -5,7 +5,7 @@ if HUG_USE_UJSON: import ujson as json - class dumps_proxy: + class dumps_proxy: # noqa: N801 """Proxies the call so non supported kwargs are skipped and it enables escape_forward_slashes to simulate built-in json """ diff --git a/hug/middleware.py b/hug/middleware.py index 61bd0ffb..68e8d396 100644 --- a/hug/middleware.py +++ b/hug/middleware.py @@ -153,8 +153,10 @@ class CORSMiddleware(object): __slots__ = ("api", "allow_origins", "allow_credentials", "max_age") def __init__( - self, api, allow_origins: list = ["*"], allow_credentials: bool = True, max_age: int = None + self, api, allow_origins: list = None, allow_credentials: bool = True, max_age: int = None ): + if allow_origins is None: + allow_origins = ["*"] self.api = api self.allow_origins = allow_origins self.allow_credentials = allow_credentials diff --git a/hug/output_format.py b/hug/output_format.py index 1d50d38c..6e60dfb3 100644 --- a/hug/output_format.py +++ b/hug/output_format.py @@ -384,7 +384,7 @@ def output_type(data, request, response): handler = default accepted = [accept_quality(accept_type) for accept_type in accept.split(",")] accepted.sort(key=itemgetter(0)) - for quality, accepted_content_type in reversed(accepted): + for _quality, accepted_content_type in reversed(accepted): if accepted_content_type in handlers: handler = handlers[accepted_content_type] break diff --git a/hug/route.py b/hug/route.py index 91eac8a2..56c3b96c 100644 --- a/hug/route.py +++ b/hug/route.py @@ -27,13 +27,13 @@ from falcon import HTTP_METHODS import hug.api -from hug.routing import CLIRouter as cli -from hug.routing import ExceptionRouter as exception -from hug.routing import LocalRouter as local -from hug.routing import NotFoundRouter as not_found -from hug.routing import SinkRouter as sink -from hug.routing import StaticRouter as static -from hug.routing import URLRouter as http +from hug.routing import CLIRouter as cli # noqa: N813 +from hug.routing import ExceptionRouter as exception # noqa: N813 +from hug.routing import LocalRouter as local # noqa: N813 +from hug.routing import NotFoundRouter as not_found # noqa: N813 +from hug.routing import SinkRouter as sink # noqa: N813 +from hug.routing import StaticRouter as static # noqa: N813 +from hug.routing import URLRouter as http # noqa: N813 class Object(http): diff --git a/hug/routing.py b/hug/routing.py index e215f257..4a8af026 100644 --- a/hug/routing.py +++ b/hug/routing.py @@ -221,13 +221,15 @@ def __init__( versions=any, parse_body=False, parameters=None, - defaults={}, + defaults=None, status=None, response_headers=None, private=False, inputs=None, **kwargs ): + if defaults is None: + defaults = {} super().__init__(**kwargs) if versions is not any: self.route["versions"] = ( diff --git a/requirements/build_common.txt b/requirements/build_common.txt index 848e90ac..bd32ae27 100644 --- a/requirements/build_common.txt +++ b/requirements/build_common.txt @@ -8,3 +8,4 @@ wheel==0.29.0 PyJWT==1.4.2 pytest-xdist==1.14.0 numpy==1.15.4 + diff --git a/requirements/build_style_tools.txt b/requirements/build_style_tools.txt new file mode 100644 index 00000000..a24c2023 --- /dev/null +++ b/requirements/build_style_tools.txt @@ -0,0 +1,6 @@ +-r build_common.txt +black==19.3b0 +pep8-naming==0.8.2 +flake8-bugbear==19.3.0 +vulture==1.0 +bandit==1.6.0 \ No newline at end of file diff --git a/tests/test_full_request.py b/tests/test_full_request.py index f7f339a8..56866138 100644 --- a/tests/test_full_request.py +++ b/tests/test_full_request.py @@ -39,11 +39,13 @@ def post(body, response): """ -@pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="Can't run hug CLI from travis PyPy") +@pytest.mark.skipif( + platform.python_implementation() == "PyPy", reason="Can't run hug CLI from travis PyPy" +) def test_hug_post(tmp_path): - hug_test_file = (tmp_path / "hug_postable.py") + hug_test_file = tmp_path / "hug_postable.py" hug_test_file.write_text(TEST_HUG_API) - hug_server = Popen(['hug', '-f', str(hug_test_file), '-p', '3000']) + hug_server = Popen(["hug", "-f", str(hug_test_file), "-p", "3000"]) time.sleep(5) - requests.post('http://127.0.0.1:3000/test', {'data': 'here'}) + requests.post("http://127.0.0.1:3000/test", {"data": "here"}) hug_server.kill() diff --git a/tox.ini b/tox.ini index 8b850d5c..c1857807 100644 --- a/tox.ini +++ b/tox.ini @@ -7,9 +7,49 @@ deps= marshmallow2: marshmallow <3.0 marshmallow3: marshmallow >=3.0.0rc5 +whitelist_externals=flake8 +commands=py.test --cov-report html --cov hug -n auto tests + +[testenv:py37-black] +deps= + -rrequirements/build_style_tools.txt + marshmallow >=3.0.0rc5 + +whitelist_externals=flake8 +commands=black --check --verbose -l 100 hug + +[testenv:py37-vulture] +deps= + -rrequirements/build_style_tools.txt + marshmallow >=3.0.0rc5 + +whitelist_externals=flake8 +commands=vulture hug --min-confidence 100 + + +[testenv:py37-flake8] +deps= + -rrequirements/build_style_tools.txt + marshmallow >=3.0.0rc5 + whitelist_externals=flake8 commands=flake8 hug - py.test --cov-report html --cov hug -n auto tests + +[testenv:py37-bandit] +deps= + -rrequirements/build_style_tools.txt + marshmallow >=3.0.0rc5 + +whitelist_externals=flake8 +commands=bandit -r hug/ -ll + +[testenv:py37-isort] +deps= + -rrequirements/build_style_tools.txt + marshmallow >=3.0.0rc5 + +whitelist_externals=flake8 +commands=isort -c hug/*py [testenv:pywin] deps =-rrequirements/build_windows.txt