Skip to content

Mypy uvicorn #740

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 77 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
a21fb74
Added scripts, they were ignre by my global gitignore
euri10 Aug 1, 2020
aa380dd
Added mypy
euri10 Aug 2, 2020
a7ea501
Typed importer.py
euri10 Aug 2, 2020
c7b910c
Typed Loops
euri10 Aug 2, 2020
75f71dd
Typed Loops 1
euri10 Aug 2, 2020
d401e8c
Typed Loops 2
euri10 Aug 2, 2020
c65fd61
Typed config
euri10 Aug 2, 2020
fe776b9
Typed asgi2 and proxy middlewares
euri10 Aug 2, 2020
ed1f709
Typed debug
euri10 Aug 2, 2020
ffcffcb
Mass type WIP
euri10 Aug 2, 2020
4cd0afe
Type trace and logger middlewares
euri10 Aug 2, 2020
20cd60a
Almost ok on wsgi types
euri10 Aug 2, 2020
de7e5e9
wLint
euri10 Aug 2, 2020
c1c8c05
Return -> None where possible
euri10 Aug 2, 2020
e0d2a9a
Typed logging and utils.py
euri10 Aug 2, 2020
23e104f
subprocess.py
euri10 Aug 2, 2020
d452d2e
Need to be more specific in config.py
euri10 Aug 2, 2020
025eefa
Make use of TYPE_CHECKING an quote all type I wouldnt import
euri10 Aug 2, 2020
8333941
Lint
euri10 Aug 2, 2020
3ef8bf9
Better _types.py
euri10 Aug 2, 2020
7a87fb4
Better _types.py
euri10 Aug 2, 2020
94828bc
WIP protocols
euri10 Aug 2, 2020
981bf28
Almost ok httptools protocol
euri10 Aug 2, 2020
22d0991
More type error corrected
euri10 Aug 2, 2020
b431b16
httptools_impl.py ok
euri10 Aug 2, 2020
a4a81d3
Lint
euri10 Aug 2, 2020
918ad89
H11 protocol almost ok too
euri10 Aug 2, 2020
5aba168
Lost in types
euri10 Aug 2, 2020
17cef0a
WSproto almost ok
euri10 Aug 2, 2020
c637249
Progress on websockets_impl.py
euri10 Aug 2, 2020
d96c001
Lint
euri10 Aug 2, 2020
2fc59b8
Ignore errors on detection, cant manage it :
euri10 Aug 3, 2020
e614d10
Ignore errors on detection, cant manage it :
euri10 Aug 3, 2020
29e6090
Making sure it's a dict to use copy
euri10 Aug 3, 2020
ce2feb6
Reloaders wip
euri10 Aug 3, 2020
48ed461
Supervisor section seem ok
euri10 Aug 3, 2020
ae4f924
Supervisor section seem ok now
euri10 Aug 3, 2020
3683f2d
WIP mypy-ing
euri10 Aug 4, 2020
39212e5
WSGI types look ok
euri10 Aug 6, 2020
dab14fd
Most are asgi 3 apps I think
euri10 Aug 6, 2020
a4cb3df
Non-local
euri10 Aug 6, 2020
f60ef4d
Fixed some wsproto events
euri10 Aug 6, 2020
98bf2ce
Transport types
euri10 Aug 6, 2020
00cb13d
Some progress
euri10 Aug 6, 2020
247e960
35 to go
euri10 Aug 6, 2020
3ee5cf4
Stricter scope as a TypedDict ?
euri10 Aug 6, 2020
40fb176
Better scope
euri10 Aug 6, 2020
29fc95e
Make use of new http scope type for wsgi
euri10 Aug 7, 2020
bb5e37f
Using HTTPConnectionScope
euri10 Aug 8, 2020
11f9f1e
Merge master
euri10 Aug 8, 2020
b6b698e
Try with typeddicts on receive / send messages
euri10 Aug 8, 2020
5ccb3f9
WS types messages
euri10 Aug 8, 2020
317d747
Tests pass
euri10 Aug 8, 2020
b9adddd
using typeddict is panful ?
euri10 Aug 8, 2020
343e800
Some stuf is better some is not easy
euri10 Aug 9, 2020
2d37978
Merge branch 'master' into mypy_uvicorn
euri10 Aug 13, 2020
3e68ca8
Using https://mypy.readthedocs.io/en/stable/literal_types.html#tagged…
euri10 Aug 13, 2020
355220d
More use of tagged union to be sure we have a http.request message
euri10 Aug 13, 2020
61f26e3
Comments
euri10 Aug 13, 2020
1b566ad
All wsgi fixed and clean
euri10 Aug 13, 2020
6fc8eb4
Subprotocol is a str
euri10 Aug 13, 2020
b74bc1a
Fixed websockets_impl.py
euri10 Aug 13, 2020
4e933aa
This was deleted in master...
euri10 Aug 13, 2020
73a0d66
All httptools_impl.py fixed
euri10 Aug 13, 2020
295824f
All h11_impl.py fixed
euri10 Aug 13, 2020
358d1e4
Switched to class-based typeddict because we dont use python < 3.5
euri10 Aug 14, 2020
bc1ffb0
Trying to disciminated typeddict using tagged union but maybe this is…
euri10 Aug 14, 2020
31d0c76
Fixed client.py
euri10 Aug 14, 2020
dee3ec1
Fixed workers.py
euri10 Aug 14, 2020
f93ff42
Fixed config.py
euri10 Aug 14, 2020
38ba9dc
Fixed main.py
euri10 Aug 14, 2020
6238179
Fixed proxy_headers.py, not sure mypy doesnt make me do something sil…
euri10 Aug 14, 2020
6a862d1
Weird asserts
euri10 Aug 14, 2020
50733ac
Using class in WSend too
euri10 Aug 14, 2020
53e39b3
Literal appears in 3.8
euri10 Aug 14, 2020
e7ff74c
Typeddict in 3.8
euri10 Aug 14, 2020
e887905
3.8
euri10 Aug 14, 2020
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 requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pytest
pytest-cov
requests
seed-isort-config
mypy

# Documentation
mkdocs
Expand Down
1 change: 1 addition & 0 deletions scripts/check
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ set -x

${PREFIX}black --check --diff --target-version=py36 $SOURCE_FILES
${PREFIX}flake8 $SOURCE_FILES
${PREFIX}mypy $SOURCE_FILES
${PREFIX}isort --check --diff --project=uvicorn $SOURCE_FILES
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ check_untyped_defs = True
profile = black
combine_as_imports = True
known_first_party = uvicorn,tests
known_third_party = click,does_not_exist,gunicorn,h11,httptools,pytest,requests,setuptools,urllib3,uvloop,watchgod,websockets,wsproto
known_third_party = _pytest,click,does_not_exist,gunicorn,h11,httptools,pytest,requests,setuptools,typing_extensions,urllib3,uvloop,watchgod,websockets,wsproto

[tool:pytest]
addopts = --cov=uvicorn --cov=tests -rxXs
13 changes: 8 additions & 5 deletions tests/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import requests

from uvicorn._types import HTTPSendMessage


class _HeaderDict(requests.packages.urllib3._collections.HTTPHeaderDict):
def get_all(self, key, default):
Expand Down Expand Up @@ -75,7 +77,10 @@ async def receive():
body_bytes = body
return {"type": "http.request", "body": body_bytes}

async def send(message):
raw_kwargs: typing.Dict[str, typing.Any] = {"body": io.BytesIO()}
response_started = False

async def send(message: HTTPSendMessage):
nonlocal raw_kwargs, response_started

if message["type"] == "http.response.start":
Expand All @@ -96,9 +101,6 @@ async def send(message):
if not more_body:
raw_kwargs["body"].seek(0)

response_started = False
raw_kwargs = {"body": io.BytesIO()}

loop = asyncio.get_event_loop()

try:
Expand All @@ -122,7 +124,8 @@ def __init__(
self.headers.update({"user-agent": "testclient"})
self.base_url = base_url

def request(self, method: str, url: str, **kwargs) -> requests.Response:
def request(self, *args, **kwargs) -> requests.Response:
method, url = args
url = urljoin(self.base_url, url)
return super().request(method, url, **kwargs)

Expand Down
17 changes: 9 additions & 8 deletions tests/middleware/test_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import pytest

from tests.client import TestClient
from uvicorn._types import ASGI3App, Receive, Scope, Send
from uvicorn.middleware.debug import DebugMiddleware


def test_debug_text():
async def app(scope, receive, send):
def test_debug_text() -> None:
async def app(scope: Scope, receive: Receive, send: Send) -> ASGI3App:
raise RuntimeError("Something went wrong")

app = DebugMiddleware(app)
Expand All @@ -18,8 +19,8 @@ async def app(scope, receive, send):
assert "RuntimeError" in response.text


def test_debug_html():
async def app(scope, receive, send):
def test_debug_html() -> None:
async def app(scope: Scope, receive: Receive, send: Send) -> ASGI3App:
raise RuntimeError("Something went wrong")

app = DebugMiddleware(app)
Expand All @@ -30,8 +31,8 @@ async def app(scope, receive, send):
assert "RuntimeError" in response.text


def test_debug_after_response_sent():
async def app(scope, receive, send):
def test_debug_after_response_sent() -> None:
async def app(scope: Scope, receive: Receive, send: Send) -> ASGI3App:
await send({"type": "http.response.start", "status": 204, "headers": []})
await send({"type": "http.response.body", "body": b"", "more_body": False})
raise RuntimeError("Something went wrong")
Expand All @@ -43,8 +44,8 @@ async def app(scope, receive, send):
assert response.content == b""


def test_debug_not_http():
async def app(scope, send, receive):
def test_debug_not_http() -> None:
async def app(scope: Scope, receive: Receive, send: Send) -> ASGI3App:
raise RuntimeError("Something went wrong")

app = DebugMiddleware(app)
Expand Down
10 changes: 6 additions & 4 deletions tests/middleware/test_message_logger.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import pytest
from _pytest.logging import LogCaptureFixture

from tests.client import TestClient
from uvicorn._types import ASGI3App, Receive, Scope, Send
from uvicorn.middleware.message_logger import MessageLoggerMiddleware

TRACE_LOG_LEVEL = 5


def test_message_logger(caplog):
async def app(scope, receive, send):
def test_message_logger(caplog: LogCaptureFixture) -> None:
async def app(scope: Scope, receive: Receive, send: Send) -> ASGI3App:
await receive()
await send({"type": "http.response.start", "status": 200, "headers": []})
await send({"type": "http.response.body", "body": b"", "more_body": False})
Expand All @@ -27,8 +29,8 @@ async def app(scope, receive, send):
assert sum(["ASGI [1] Raised exception" in message for message in messages]) == 0


def test_message_logger_exc(caplog):
async def app(scope, receive, send):
def test_message_logger_exc(caplog: LogCaptureFixture) -> None:
async def app(scope: Scope, receive: Receive, send: Send) -> ASGI3App:
raise RuntimeError()

caplog.set_level(TRACE_LOG_LEVEL, logger="uvicorn.asgi")
Expand Down
7 changes: 4 additions & 3 deletions tests/middleware/test_proxy_headers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from tests.client import TestClient
from tests.response import Response
from uvicorn._types import Receive, Scope, Send
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware


async def app(scope, receive, send):
async def app(scope: Scope, receive: Receive, send: Send) -> None:
scheme = scope["scheme"]
host, port = scope["client"]
addr = "%s://%s:%d" % (scheme, host, port)
Expand All @@ -14,15 +15,15 @@ async def app(scope, receive, send):
app = ProxyHeadersMiddleware(app, trusted_hosts="*")


def test_proxy_headers():
def test_proxy_headers() -> None:
client = TestClient(app)
headers = {"X-Forwarded-Proto": "https", "X-Forwarded-For": "1.2.3.4"}
response = client.get("/", headers=headers)
assert response.status_code == 200
assert response.text == "Remote: https://1.2.3.4:0"


def test_proxy_headers_no_port():
def test_proxy_headers_no_port() -> None:
client = TestClient(app)
headers = {"X-Forwarded-Proto": "https", "X-Forwarded-For": "1.2.3.4"}
response = client.get("/", headers=headers)
Expand Down
18 changes: 10 additions & 8 deletions tests/middleware/test_trace_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

import pytest
import requests
from _pytest.capture import CaptureFixture

from uvicorn import Config, Server
from uvicorn._types import Receive, Scope, Send

test_logging_config = {
"version": 1,
Expand Down Expand Up @@ -52,18 +54,18 @@
sys.platform.startswith("win") or platform.python_implementation() == "PyPy",
reason="Skipping test on Windows and PyPy",
)
def test_trace_logging(capsys):
def test_trace_logging(capsys: CaptureFixture) -> None:
class App:
def __init__(self, scope):
def __init__(self, scope: Scope):
if scope["type"] != "http":
raise Exception()

async def __call__(self, receive, send):
async def __call__(self, receive: Receive, send: Send) -> None:
await send({"type": "http.response.start", "status": 204, "headers": []})
await send({"type": "http.response.body", "body": b"", "more_body": False})

class CustomServer(Server):
def install_signal_handlers(self):
def install_signal_handlers(self) -> None:
pass

config = Config(
Expand Down Expand Up @@ -91,18 +93,18 @@ def install_signal_handlers(self):
reason="Skipping test on Windows and PyPy",
)
@pytest.mark.parametrize("http_protocol", [("h11"), ("httptools")])
def test_access_logging(capsys, http_protocol):
def test_access_logging(capsys: CaptureFixture, http_protocol: str) -> None:
class App:
def __init__(self, scope):
def __init__(self, scope: Scope) -> None:
if scope["type"] != "http":
raise Exception()

async def __call__(self, receive, send):
async def __call__(self, receive: Receive, send: Send) -> None:
await send({"type": "http.response.start", "status": 204, "headers": []})
await send({"type": "http.response.body", "body": b"", "more_body": False})

class CustomServer(Server):
def install_signal_handlers(self):
def install_signal_handlers(self) -> None:
pass

config = Config(
Expand Down
17 changes: 9 additions & 8 deletions tests/middleware/test_wsgi.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import sys
from typing import Callable, List, Type

import pytest

from tests.client import TestClient
from uvicorn.middleware.wsgi import WSGIMiddleware


def hello_world(environ, start_response):
def hello_world(environ: dict, start_response: Callable) -> List[bytes]:
status = "200 OK"
output = b"Hello World!\n"
headers = [
Expand All @@ -17,7 +18,7 @@ def hello_world(environ, start_response):
return [output]


def echo_body(environ, start_response):
def echo_body(environ: dict, start_response: Callable) -> List[bytes]:
status = "200 OK"
output = environ["wsgi.input"].read()
headers = [
Expand All @@ -28,11 +29,11 @@ def echo_body(environ, start_response):
return [output]


def raise_exception(environ, start_response):
def raise_exception(environ: dict, start_response: Callable) -> Type[Exception]:
raise RuntimeError("Something went wrong")


def return_exc_info(environ, start_response):
def return_exc_info(environ: dict, start_response: Callable) -> List[bytes]:
try:
raise RuntimeError("Something went wrong")
except RuntimeError:
Expand All @@ -46,23 +47,23 @@ def return_exc_info(environ, start_response):
return [output]


def test_wsgi_get():
def test_wsgi_get() -> None:
app = WSGIMiddleware(hello_world)
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200
assert response.text == "Hello World!\n"


def test_wsgi_post():
def test_wsgi_post() -> None:
app = WSGIMiddleware(echo_body)
client = TestClient(app)
response = client.post("/", json={"example": 123})
assert response.status_code == 200
assert response.text == '{"example": 123}'


def test_wsgi_exception():
def test_wsgi_exception() -> None:
# Note that we're testing the WSGI app directly here.
# The HTTP protocol implementations would catch this error and return 500.
app = WSGIMiddleware(raise_exception)
Expand All @@ -71,7 +72,7 @@ def test_wsgi_exception():
client.get("/")


def test_wsgi_exc_info():
def test_wsgi_exc_info() -> None:
# Note that we're testing the WSGI app directly here.
# The HTTP protocol implementations would catch this error and return 500.
app = WSGIMiddleware(return_exc_info)
Expand Down
2 changes: 1 addition & 1 deletion tests/protocols/test_websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ async def websocket_connect(self, message):
path = self.scope.get("path")
raw_path = self.scope.get("raw_path")
assert path == "/one/two"
assert raw_path == "/one%2Ftwo"
assert raw_path == b"/one%2Ftwo"
await self.send({"type": "websocket.accept"})

async def open_connection(url):
Expand Down
6 changes: 4 additions & 2 deletions tests/test_auto_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import websockets
except ImportError: # pragma: no cover
# Note that we skip the websocket tests completely in this case.
websockets = None
websockets = None # type: ignore


# TODO: Add pypy to our testing matrix, and assert we get the correct classes
Expand All @@ -46,6 +46,8 @@ def test_http_auto():
def test_websocket_auto():
config = Config(app=None)
server_state = ServerState()
protocol = AutoWebSocketsProtocol(config=config, server_state=server_state)
protocol = AutoWebSocketsProtocol( # type: ignore
config=config, server_state=server_state
)
expected_websockets = "WSProtocol" if websockets is None else "WebSocketProtocol"
assert type(protocol).__name__ == expected_websockets
3 changes: 2 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import threading
import time
from typing import Optional, Union

import requests

Expand Down Expand Up @@ -100,7 +101,7 @@ def install_signal_handlers(self):
config = Config(app=App, loop="asyncio", workers=2, limit_max_requests=1)
server = CustomServer(config=config)
sock = config.bind_socket()
exc = True
exc: Optional[Union[Exception, bool]] = True

def safe_run():
nonlocal exc, server
Expand Down
Loading