mirror of
https://github.com/laurentS/slowapi.git
synced 2026-03-13 09:10:20 +08:00
Merge pull request #15 from laurentS/ci-tests
Setup CI to check formatting and run tests
This commit is contained in:
42
.github/workflows/python-package.yml
vendored
Normal file
42
.github/workflows/python-package.yml
vendored
Normal file
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"""
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user