Skip to content

Commit

Permalink
Add type annotations to test_config.py
Browse files Browse the repository at this point in the history
PR #978 added logic to convert `reload_dirs` from string to list, as
shown in `test_reload_dir_is_set()`, so the annotation on reload_dirs
will be updated to accept a string.
  • Loading branch information
br3ndonland committed Jun 4, 2021
1 parent 459544d commit c436bba
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 41 deletions.
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ files =
uvicorn/lifespan,
tests/test_lifespan.py,
uvicorn/config.py,
tests/test_config.py,
uvicorn/middleware/message_logger.py,
uvicorn/supervisors/basereload.py,
uvicorn/importer.py,
Expand Down
114 changes: 74 additions & 40 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@
import logging
import os
import socket
import sys
from copy import deepcopy
from pathlib import Path
from typing import Any, Callable, Iterator, Optional
from unittest.mock import MagicMock

if sys.version_info < (3, 8):
from typing_extensions import Literal
else:
from typing import Literal

import pytest
import yaml
from asgiref.typing import ASGIApplication, ASGIReceiveCallable, ASGISendCallable, Scope
from pytest_mock import MockerFixture

from uvicorn.config import LOGGING_CONFIG, Config
from uvicorn.middleware.debug import DebugMiddleware
Expand All @@ -15,34 +26,36 @@


@pytest.fixture
def mocked_logging_config_module(mocker):
def mocked_logging_config_module(mocker: MockerFixture) -> MagicMock:
return mocker.patch("logging.config")


@pytest.fixture(scope="function")
def logging_config():
def logging_config() -> dict:
return deepcopy(LOGGING_CONFIG)


@pytest.fixture
def json_logging_config(logging_config):
def json_logging_config(logging_config: dict) -> str:
return json.dumps(logging_config)


@pytest.fixture
def yaml_logging_config(logging_config):
def yaml_logging_config(logging_config: dict) -> str:
return yaml.dump(logging_config)


async def asgi_app(scope, receive, send):
async def asgi_app(
scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable
) -> None:
pass # pragma: nocover


def wsgi_app(environ, start_response):
def wsgi_app(environ: Any, start_response: Any) -> None:
pass # pragma: nocover


def test_debug_app():
def test_debug_app() -> None:
config = Config(app=asgi_app, debug=True, proxy_headers=False)
config.load()

Expand All @@ -54,7 +67,9 @@ def test_debug_app():
"app, expected_should_reload",
[(asgi_app, False), ("tests.test_config:asgi_app", True)],
)
def test_config_should_reload_is_set(app, expected_should_reload):
def test_config_should_reload_is_set(
app: ASGIApplication, expected_should_reload: bool
) -> None:
config_debug = Config(app=app, debug=True)
assert config_debug.debug is True
assert config_debug.should_reload is expected_should_reload
Expand All @@ -64,12 +79,12 @@ def test_config_should_reload_is_set(app, expected_should_reload):
assert config_reload.should_reload is expected_should_reload


def test_reload_dir_is_set():
def test_reload_dir_is_set() -> None:
config = Config(app=asgi_app, reload=True, reload_dirs="reload_me")
assert config.reload_dirs == ["reload_me"]


def test_wsgi_app():
def test_wsgi_app() -> None:
config = Config(app=wsgi_app, interface="wsgi", proxy_headers=False)
config.load()

Expand All @@ -78,21 +93,21 @@ def test_wsgi_app():
assert config.asgi_version == "3.0"


def test_proxy_headers():
def test_proxy_headers() -> None:
config = Config(app=asgi_app)
config.load()

assert config.proxy_headers is True
assert isinstance(config.loaded_app, ProxyHeadersMiddleware)


def test_app_unimportable_module():
def test_app_unimportable_module() -> None:
config = Config(app="no.such:app")
with pytest.raises(ImportError):
config.load()


def test_app_unimportable_other(caplog):
def test_app_unimportable_other(caplog: pytest.LogCaptureFixture) -> None:
config = Config(app="tests.test_config:app")
with pytest.raises(SystemExit):
config.load()
Expand All @@ -107,8 +122,8 @@ def test_app_unimportable_other(caplog):
)


def test_app_factory(caplog):
def create_app():
def test_app_factory(caplog: pytest.LogCaptureFixture) -> None:
def create_app() -> ASGIApplication:
return asgi_app

config = Config(app=create_app, factory=True, proxy_headers=False)
Expand All @@ -125,27 +140,30 @@ def create_app():
assert len(caplog.records) == 1
assert "--factory" in caplog.records[0].message

# App not a no-arguments callable.
# App not a no-arguments ASGIApplication.
config = Config(app=asgi_app, factory=True)
with pytest.raises(SystemExit):
config.load()


def test_concrete_http_class():
def test_concrete_http_class() -> None:
config = Config(app=asgi_app, http=H11Protocol)
config.load()
assert config.http_protocol_class is H11Protocol


def test_socket_bind():
def test_socket_bind() -> None:
config = Config(app=asgi_app)
config.load()
sock = config.bind_socket()
assert isinstance(sock, socket.socket)
sock.close()


def test_ssl_config(tls_ca_certificate_pem_path, tls_ca_certificate_private_key_path):
def test_ssl_config(
tls_ca_certificate_pem_path: str,
tls_ca_certificate_private_key_path: str,
) -> None:
config = Config(
app=asgi_app,
ssl_certfile=tls_ca_certificate_pem_path,
Expand All @@ -156,7 +174,7 @@ def test_ssl_config(tls_ca_certificate_pem_path, tls_ca_certificate_private_key_
assert config.is_ssl is True


def test_ssl_config_combined(tls_certificate_pem_path):
def test_ssl_config_combined(tls_certificate_pem_path: str) -> None:
config = Config(
app=asgi_app,
ssl_certfile=tls_certificate_pem_path,
Expand All @@ -166,8 +184,10 @@ def test_ssl_config_combined(tls_certificate_pem_path):
assert config.is_ssl is True


def asgi2_app(scope):
async def asgi(receive, send): # pragma: nocover
def asgi2_app(scope: Scope) -> Callable:
async def asgi(
receive: ASGIReceiveCallable, send: ASGISendCallable
) -> None: # pragma: nocover
pass

return asgi # pragma: nocover
Expand All @@ -176,7 +196,9 @@ async def asgi(receive, send): # pragma: nocover
@pytest.mark.parametrize(
"app, expected_interface", [(asgi_app, "3.0"), (asgi2_app, "2.0")]
)
def test_asgi_version(app, expected_interface):
def test_asgi_version(
app: ASGIApplication, expected_interface: Literal["2.0", "3.0"]
) -> None:
config = Config(app=app)
config.load()
assert config.asgi_version == expected_interface
Expand All @@ -191,7 +213,11 @@ def test_asgi_version(app, expected_interface):
pytest.param(False, False, id="use_colors_disabled"),
],
)
def test_log_config_default(mocked_logging_config_module, use_colors, expected):
def test_log_config_default(
mocked_logging_config_module: MagicMock,
use_colors: Optional[bool],
expected: Optional[bool],
) -> None:
"""
Test that one can specify the use_colors option when using the default logging
config.
Expand All @@ -206,8 +232,11 @@ def test_log_config_default(mocked_logging_config_module, use_colors, expected):


def test_log_config_json(
mocked_logging_config_module, logging_config, json_logging_config, mocker
):
mocked_logging_config_module: MagicMock,
logging_config: dict,
json_logging_config: str,
mocker: MockerFixture,
) -> None:
"""
Test that one can load a json config from disk.
"""
Expand All @@ -224,12 +253,12 @@ def test_log_config_json(

@pytest.mark.parametrize("config_filename", ["log_config.yml", "log_config.yaml"])
def test_log_config_yaml(
mocked_logging_config_module,
logging_config,
yaml_logging_config,
mocker,
config_filename,
):
mocked_logging_config_module: MagicMock,
logging_config: dict,
yaml_logging_config: str,
mocker: MockerFixture,
config_filename: str,
) -> None:
"""
Test that one can load a yaml config from disk.
"""
Expand All @@ -244,7 +273,7 @@ def test_log_config_yaml(
mocked_logging_config_module.dictConfig.assert_called_once_with(logging_config)


def test_log_config_file(mocked_logging_config_module):
def test_log_config_file(mocked_logging_config_module: MagicMock) -> None:
"""
Test that one can load a configparser config from disk.
"""
Expand All @@ -257,20 +286,25 @@ def test_log_config_file(mocked_logging_config_module):


@pytest.fixture(params=[0, 1])
def web_concurrency(request):
def web_concurrency(request: Any) -> Iterator[int]:
yield request.param
if os.getenv("WEB_CONCURRENCY"):
del os.environ["WEB_CONCURRENCY"]


@pytest.fixture(params=["127.0.0.1", "127.0.0.2"])
def forwarded_allow_ips(request):
def forwarded_allow_ips(request: Any) -> Iterator[str]:
yield request.param
if os.getenv("FORWARDED_ALLOW_IPS"):
del os.environ["FORWARDED_ALLOW_IPS"]


def test_env_file(web_concurrency: int, forwarded_allow_ips: str, caplog, tmp_path):
def test_env_file(
web_concurrency: int,
forwarded_allow_ips: str,
caplog: pytest.LogCaptureFixture,
tmp_path: Path,
) -> None:
"""
Test that one can load environment variables using an env file.
"""
Expand All @@ -284,7 +318,7 @@ def test_env_file(web_concurrency: int, forwarded_allow_ips: str, caplog, tmp_pa
config = Config(app=asgi_app, env_file=fp)
config.load()

assert config.workers == int(os.getenv("WEB_CONCURRENCY"))
assert config.workers == int(str(os.getenv("WEB_CONCURRENCY")))
assert config.forwarded_allow_ips == os.getenv("FORWARDED_ALLOW_IPS")
assert len(caplog.records) == 1
assert f"Loading environment from '{fp}'" in caplog.records[0].message
Expand All @@ -297,7 +331,7 @@ def test_env_file(web_concurrency: int, forwarded_allow_ips: str, caplog, tmp_pa
pytest.param(False, 0, id="access log disabled shouldn't have handlers"),
],
)
def test_config_access_log(access_log: bool, handlers: int):
def test_config_access_log(access_log: bool, handlers: int) -> None:
config = Config(app=asgi_app, access_log=access_log)
config.load()

Expand All @@ -306,7 +340,7 @@ def test_config_access_log(access_log: bool, handlers: int):


@pytest.mark.parametrize("log_level", [5, 10, 20, 30, 40, 50])
def test_config_log_level(log_level):
def test_config_log_level(log_level: int) -> None:
config = Config(app=asgi_app, log_level=log_level)
config.load()

Expand All @@ -316,7 +350,7 @@ def test_config_log_level(log_level):
assert config.log_level == log_level


def test_ws_max_size():
def test_ws_max_size() -> None:
config = Config(app=asgi_app, ws_max_size=1000)
config.load()
assert config.ws_max_size == 1000
2 changes: 1 addition & 1 deletion uvicorn/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def __init__(
interface: Literal["auto", "asgi3", "asgi2", "wsgi"] = "auto",
debug: bool = False,
reload: bool = False,
reload_dirs: Optional[List[str]] = None,
reload_dirs: Optional[Union[List[str], str]] = None,
reload_delay: Optional[float] = None,
workers: Optional[int] = None,
proxy_headers: bool = True,
Expand Down

0 comments on commit c436bba

Please sign in to comment.