* Bump the python-packages group with 6 updates Bumps the python-packages group with 6 updates: | Package | From | To | | --- | --- | --- | | [websockets](https://github.com/python-websockets/websockets) | `13.1` | `14.2` | | [a2wsgi](https://github.com/abersheeran/a2wsgi) | `1.10.7` | `1.10.8` | | [twine](https://github.com/pypa/twine) | `6.0.1` | `6.1.0` | | [ruff](https://github.com/astral-sh/ruff) | `0.8.4` | `0.9.4` | | [trustme](https://github.com/python-trio/trustme) | `1.2.0` | `1.2.1` | | [mkdocs-material](https://github.com/squidfunk/mkdocs-material) | `9.5.49` | `9.6.1` | Updates `websockets` from 13.1 to 14.2 - [Release notes](https://github.com/python-websockets/websockets/releases) - [Commits](https://github.com/python-websockets/websockets/compare/13.1...14.2) Updates `a2wsgi` from 1.10.7 to 1.10.8 - [Commits](https://github.com/abersheeran/a2wsgi/compare/v1.10.7...v1.10.8) Updates `twine` from 6.0.1 to 6.1.0 - [Release notes](https://github.com/pypa/twine/releases) - [Changelog](https://github.com/pypa/twine/blob/main/docs/changelog.rst) - [Commits](https://github.com/pypa/twine/compare/6.0.1...6.1.0) Updates `ruff` from 0.8.4 to 0.9.4 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.8.4...0.9.4) Updates `trustme` from 1.2.0 to 1.2.1 - [Release notes](https://github.com/python-trio/trustme/releases) - [Commits](https://github.com/python-trio/trustme/compare/v1.2.0...v1.2.1) Updates `mkdocs-material` from 9.5.49 to 9.6.1 - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.49...9.6.1) --- updated-dependencies: - dependency-name: websockets dependency-type: direct:production update-type: version-update:semver-major dependency-group: python-packages - dependency-name: a2wsgi dependency-type: direct:production update-type: version-update:semver-patch dependency-group: python-packages - dependency-name: twine dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-packages - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-packages - dependency-name: trustme dependency-type: direct:production update-type: version-update:semver-patch dependency-group: python-packages - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python-packages ... Signed-off-by: dependabot[bot] <support@github.com> * lint the whole thing --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
204 lines
6.1 KiB
Python
204 lines
6.1 KiB
Python
import contextlib
|
|
import importlib
|
|
import os
|
|
import platform
|
|
import sys
|
|
from collections.abc import Iterator
|
|
from pathlib import Path
|
|
from textwrap import dedent
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
import uvicorn
|
|
from uvicorn.config import Config
|
|
from uvicorn.main import main as cli
|
|
from uvicorn.server import Server
|
|
from uvicorn.supervisors import ChangeReload, Multiprocess
|
|
|
|
HEADERS = "Content-Security-Policy:default-src 'self'; script-src https://example.com"
|
|
main = importlib.import_module("uvicorn.main")
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def load_env_var(key: str, value: str) -> Iterator[None]:
|
|
old_environ = dict(os.environ)
|
|
os.environ[key] = value
|
|
yield
|
|
os.environ.clear()
|
|
os.environ.update(old_environ)
|
|
|
|
|
|
class App:
|
|
pass
|
|
|
|
|
|
def test_cli_print_version() -> None:
|
|
runner = CliRunner()
|
|
|
|
result = runner.invoke(cli, ["--version"])
|
|
|
|
assert result.exit_code == 0
|
|
assert (
|
|
"Running uvicorn {version} with {py_implementation} {py_version} on {system}".format( # noqa: UP032
|
|
version=uvicorn.__version__,
|
|
py_implementation=platform.python_implementation(),
|
|
py_version=platform.python_version(),
|
|
system=platform.system(),
|
|
)
|
|
) in result.output
|
|
|
|
|
|
def test_cli_headers() -> None:
|
|
runner = CliRunner()
|
|
|
|
with mock.patch.object(main, "run") as mock_run:
|
|
result = runner.invoke(cli, ["tests.test_cli:App", "--header", HEADERS])
|
|
|
|
assert result.output == ""
|
|
assert result.exit_code == 0
|
|
mock_run.assert_called_once()
|
|
assert mock_run.call_args[1]["headers"] == [
|
|
[
|
|
"Content-Security-Policy",
|
|
"default-src 'self'; script-src https://example.com",
|
|
]
|
|
]
|
|
|
|
|
|
def test_cli_call_server_run() -> None:
|
|
runner = CliRunner()
|
|
|
|
with mock.patch.object(Server, "run") as mock_run:
|
|
result = runner.invoke(cli, ["tests.test_cli:App"])
|
|
|
|
assert result.exit_code == 3
|
|
mock_run.assert_called_once()
|
|
|
|
|
|
def test_cli_call_change_reload_run() -> None:
|
|
runner = CliRunner()
|
|
|
|
with mock.patch.object(Config, "bind_socket") as mock_bind_socket:
|
|
with mock.patch.object(ChangeReload, "run") as mock_run:
|
|
result = runner.invoke(cli, ["tests.test_cli:App", "--reload"])
|
|
|
|
assert result.exit_code == 0
|
|
mock_bind_socket.assert_called_once()
|
|
mock_run.assert_called_once()
|
|
|
|
|
|
def test_cli_call_multiprocess_run() -> None:
|
|
runner = CliRunner()
|
|
|
|
with mock.patch.object(Config, "bind_socket") as mock_bind_socket:
|
|
with mock.patch.object(Multiprocess, "run") as mock_run:
|
|
result = runner.invoke(cli, ["tests.test_cli:App", "--workers=2"])
|
|
|
|
assert result.exit_code == 0
|
|
mock_bind_socket.assert_called_once()
|
|
mock_run.assert_called_once()
|
|
|
|
|
|
@pytest.fixture(params=(True, False))
|
|
def uds_file(tmp_path: Path, request: pytest.FixtureRequest) -> Path: # pragma: py-win32
|
|
file = tmp_path / "uvicorn.sock"
|
|
should_create_file = request.param
|
|
if should_create_file:
|
|
file.touch(exist_ok=True)
|
|
return file
|
|
|
|
|
|
@pytest.mark.skipif(sys.platform == "win32", reason="require unix-like system")
|
|
def test_cli_uds(uds_file: Path) -> None: # pragma: py-win32
|
|
runner = CliRunner()
|
|
|
|
with mock.patch.object(Config, "bind_socket") as mock_bind_socket:
|
|
with mock.patch.object(Multiprocess, "run") as mock_run:
|
|
result = runner.invoke(cli, ["tests.test_cli:App", "--workers=2", "--uds", str(uds_file)])
|
|
|
|
assert result.exit_code == 0
|
|
assert result.output == ""
|
|
mock_bind_socket.assert_called_once()
|
|
mock_run.assert_called_once()
|
|
assert not uds_file.exists()
|
|
|
|
|
|
def test_cli_incomplete_app_parameter() -> None:
|
|
runner = CliRunner()
|
|
|
|
result = runner.invoke(cli, ["tests.test_cli"])
|
|
|
|
assert (
|
|
'Error loading ASGI app. Import string "tests.test_cli" must be in format "<module>:<attribute>".'
|
|
) in result.output
|
|
assert result.exit_code == 1
|
|
|
|
|
|
def test_cli_event_size() -> None:
|
|
runner = CliRunner()
|
|
|
|
with mock.patch.object(main, "run") as mock_run:
|
|
result = runner.invoke(
|
|
cli,
|
|
["tests.test_cli:App", "--h11-max-incomplete-event-size", str(32 * 1024)],
|
|
)
|
|
|
|
assert result.output == ""
|
|
assert result.exit_code == 0
|
|
mock_run.assert_called_once()
|
|
assert mock_run.call_args[1]["h11_max_incomplete_event_size"] == 32768
|
|
|
|
|
|
@pytest.mark.parametrize("http_protocol", ["h11", "httptools"])
|
|
def test_env_variables(http_protocol: str):
|
|
with load_env_var("UVICORN_HTTP", http_protocol):
|
|
runner = CliRunner(env=os.environ)
|
|
with mock.patch.object(main, "run") as mock_run:
|
|
runner.invoke(cli, ["tests.test_cli:App"])
|
|
_, kwargs = mock_run.call_args
|
|
assert kwargs["http"] == http_protocol
|
|
|
|
|
|
def test_ignore_environment_variable_when_set_on_cli():
|
|
with load_env_var("UVICORN_HTTP", "h11"):
|
|
runner = CliRunner(env=os.environ)
|
|
with mock.patch.object(main, "run") as mock_run:
|
|
runner.invoke(cli, ["tests.test_cli:App", "--http=httptools"])
|
|
_, kwargs = mock_run.call_args
|
|
assert kwargs["http"] == "httptools"
|
|
|
|
|
|
def test_app_dir(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:
|
|
app_dir = tmp_path / "dir" / "app_dir"
|
|
app_file = app_dir / "main.py"
|
|
app_dir.mkdir(parents=True)
|
|
app_file.touch()
|
|
app_file.write_text(
|
|
dedent(
|
|
"""
|
|
async def app(scope, receive, send):
|
|
...
|
|
"""
|
|
)
|
|
)
|
|
runner = CliRunner()
|
|
with mock.patch.object(Server, "run") as mock_run:
|
|
result = runner.invoke(cli, ["main:app", "--app-dir", f"{str(app_dir)}"])
|
|
|
|
assert result.exit_code == 3
|
|
mock_run.assert_called_once()
|
|
assert sys.path[0] == str(app_dir)
|
|
|
|
|
|
def test_set_app_via_environment_variable():
|
|
app_path = "tests.test_cli:App"
|
|
with load_env_var("UVICORN_APP", app_path):
|
|
runner = CliRunner(env=os.environ)
|
|
with mock.patch.object(main, "run") as mock_run:
|
|
result = runner.invoke(cli)
|
|
args, _ = mock_run.call_args
|
|
assert result.exit_code == 0
|
|
assert args == (app_path,)
|