diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..6510775 --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,42 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python package + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install Poetry + uses: dschep/install-poetry-action@v1.3 + with: + # Version of Poetry to use + version: 1.0.10 + - name: Install dependencies + run: | + poetry install + - name: Check formatting with black + run: | + black --check . + - name: Check typing annotations with mypy + run: | + mypy . + - name: Test with pytest + run: | + pytest diff --git a/pyproject.toml b/pyproject.toml index 36746b5..69d2345 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,9 @@ hiro = "^0.5.1" requests = "^2.22.0" pytest = "^5.3.5" +[tool.black] +line-length = 88 + [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" diff --git a/slowapi/extension.py b/slowapi/extension.py index 29e5eed..b99976d 100644 --- a/slowapi/extension.py +++ b/slowapi/extension.py @@ -13,8 +13,19 @@ import time import warnings from email.utils import formatdate, parsedate_to_datetime from functools import wraps -from typing import (Any, Awaitable, Callable, Dict, List, Optional, Set, Tuple, - Type, TypeVar, Union) +from typing import ( + Any, + Awaitable, + Callable, + Dict, + List, + Optional, + Set, + Tuple, + Type, + TypeVar, + Union, +) from limits import RateLimitItem # type: ignore from limits.errors import ConfigurationError # type: ignore @@ -269,8 +280,8 @@ class Limiter: """ Starlette startup event handler that links the app with the Limiter instance. """ - app.state.limiter = self - app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) + app.state.limiter = self # type: ignore + app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) # type: ignore def get_app_config(self, key: str, default_value: T = None) -> T: """ @@ -563,14 +574,16 @@ class Limiter: # get the request object from the decorated endpoint function request = kwargs.get("request", args[idx] if args else None) if not isinstance(request, Request): - raise Exception("parameter `request` must be an instance of starlette.requests.Request") + raise Exception( + "parameter `request` must be an instance of starlette.requests.Request" + ) if self._auto_check and not getattr( request.state, "_rate_limiting_complete", False ): self.__check_request_limit(request, func, False) request.state._rate_limiting_complete = True - response = await func(*args, **kwargs) + response = await func(*args, **kwargs) # type: ignore self._inject_headers(response, request.state.view_rate_limit) return response @@ -583,7 +596,9 @@ class Limiter: # get the request object from the decorated endpoint function request = kwargs.get("request", args[idx] if args else None) if not isinstance(request, Request): - raise Exception("parameter `request` must be an instance of starlette.requests.Request") + raise Exception( + "parameter `request` must be an instance of starlette.requests.Request" + ) if self._auto_check and not getattr( request.state, "_rate_limiting_complete", False diff --git a/slowapi/wrappers.py b/slowapi/wrappers.py index 5dda55d..001e589 100644 --- a/slowapi/wrappers.py +++ b/slowapi/wrappers.py @@ -42,7 +42,7 @@ class Limit(object): return "" else: return ( - self.__scope(request.endpoint) + self.__scope(request.endpoint) # type: ignore if callable(self.__scope) else self.__scope ) diff --git a/tests/__init__.py b/tests/__init__.py index 54d71ff..3ba0b5b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -5,7 +5,7 @@ from functools import wraps import redis from fastapi import FastAPI -from mock import mock +from mock import mock # type: ignore from starlette.applications import Starlette from slowapi.errors import RateLimitExceeded diff --git a/tests/test_fastapi_extension.py b/tests/test_fastapi_extension.py index 436e871..2994f02 100644 --- a/tests/test_fastapi_extension.py +++ b/tests/test_fastapi_extension.py @@ -1,5 +1,5 @@ -import hiro -import pytest +import hiro # type: ignore +import pytest # type: ignore from starlette.requests import Request from starlette.responses import PlainTextResponse from starlette.testclient import TestClient @@ -43,29 +43,37 @@ class TestDecorators(TestSlowapi): assert cli.get("/t1").status_code == 429 assert ( - cli.get("/t1", headers={"X_FORWARDED_FOR": "127.0.0.3"}).status_code - == 429 + cli.get("/t1", headers={"X_FORWARDED_FOR": "127.0.0.3"}).status_code + == 429 ) def test_endpoint_missing_request_param(self): app, limiter = self.build_fastapi_app(key_func=get_ipaddr) with pytest.raises(Exception) as exc_info: + @app.get("/t3") @limiter.limit("5/minute") async def t3(): return PlainTextResponse("test") - assert exc_info.match(r"""^No "request" or "websocket" argument on function .*""") + + assert exc_info.match( + r"""^No "request" or "websocket" argument on function .*""" + ) def test_endpoint_missing_request_param_sync(self): app, limiter = self.build_fastapi_app(key_func=get_ipaddr) with pytest.raises(Exception) as exc_info: + @app.get("/t3_sync") @limiter.limit("5/minute") def t3(): return PlainTextResponse("test") - assert exc_info.match(r"""^No "request" or "websocket" argument on function .*""") + + assert exc_info.match( + r"""^No "request" or "websocket" argument on function .*""" + ) def test_endpoint_request_param_invalid(self): app, limiter = self.build_fastapi_app(key_func=get_ipaddr) @@ -78,7 +86,9 @@ class TestDecorators(TestSlowapi): with pytest.raises(Exception) as exc_info: client = TestClient(app) client.get("/t4") - assert exc_info.match(r"""parameter `request` must be an instance of starlette.requests.Request""") + assert exc_info.match( + r"""parameter `request` must be an instance of starlette.requests.Request""" + ) def test_endpoint_request_param_invalid_sync(self): app, limiter = self.build_fastapi_app(key_func=get_ipaddr) @@ -91,4 +101,6 @@ class TestDecorators(TestSlowapi): with pytest.raises(Exception) as exc_info: client = TestClient(app) client.get("/t5") - assert exc_info.match(r"""parameter `request` must be an instance of starlette.requests.Request""") + assert exc_info.match( + r"""parameter `request` must be an instance of starlette.requests.Request""" + )