Skip to content

Commit

Permalink
Merge branch 'master' into win87error
Browse files Browse the repository at this point in the history
  • Loading branch information
euri10 committed Jun 21, 2022
2 parents 13f7466 + 39621c2 commit 708c5f3
Show file tree
Hide file tree
Showing 28 changed files with 808 additions and 610 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -47,7 +47,7 @@ In this context, "Cython-based" means the following:
Moreover, "optional extras" means that:

- the websocket protocol will be handled by `websockets` (should you want to use `wsproto` you'd need to install it manually) if possible.
- the `--reload` flag in development mode will use `watchgod`.
- the `--reload` flag in development mode will use `watchfiles`.
- windows users will have `colorama` installed for the colored logs.
- `python-dotenv` will be installed should you want to use the `--env-file` option.
- `PyYAML` will be installed to allow you to provide a `.yaml` file to `--log-config`, if desired.
Expand Down
4 changes: 2 additions & 2 deletions docs/deployment.md
Expand Up @@ -43,12 +43,12 @@ Options:
for files. Includes '*.py' by default; these
defaults can be overridden with `--reload-
exclude`. This option has no effect unless
watchgod is installed.
watchfiles is installed.
--reload-exclude TEXT Set glob patterns to exclude while watching
for files. Includes '.*, .py[cod], .sw.*,
~*' by default; these defaults can be
overridden with `--reload-include`. This
option has no effect unless watchgod is
option has no effect unless watchfiles is
installed.
--reload-delay FLOAT Delay between previous and next check if
application needs to be. Defaults to 0.25s.
Expand Down
49 changes: 43 additions & 6 deletions docs/index.md
Expand Up @@ -54,7 +54,7 @@ In this context, "Cython-based" means the following:
Moreover, "optional extras" means that:

- the websocket protocol will be handled by `websockets` (should you want to use `wsproto` you'd need to install it manually) if possible.
- the `--reload` flag in development mode will use `watchgod`.
- the `--reload` flag in development mode will use `watchfiles`.
- windows users will have `colorama` installed for the colored logs.
- `python-dotenv` will be installed should you want to use the `--env-file` option.
- `PyYAML` will be installed to allow you to provide a `.yaml` file to `--log-config`, if desired.
Expand Down Expand Up @@ -110,12 +110,12 @@ Options:
for files. Includes '*.py' by default; these
defaults can be overridden with `--reload-
exclude`. This option has no effect unless
watchgod is installed.
watchfiles is installed.
--reload-exclude TEXT Set glob patterns to exclude while watching
for files. Includes '.*, .py[cod], .sw.*,
~*' by default; these defaults can be
overridden with `--reload-include`. This
option has no effect unless watchgod is
option has no effect unless watchfiles is
installed.
--reload-delay FLOAT Delay between previous and next check if
application needs to be. Defaults to 0.25s.
Expand Down Expand Up @@ -196,9 +196,26 @@ For more information, see the [settings documentation](settings.md).

### Running programmatically

To run uvicorn directly from your application...
There are several ways to run uvicorn directly from your application.

**example.py**:
#### `uvicorn.run`

If you're looking for a programmatic equivalent of the `uvicorn` command line interface, use `uvicorn.run()`:

```python
# main.py
import uvicorn

async def app(scope, receive, send):
...

if __name__ == "__main__":
uvicorn.run("main:app", port=5000, log_level="info")
```

#### `Config` and `Server` instances

For more control over configuration and server lifecycle, use `uvicorn.Config` and `uvicorn.Server`:

```python
import uvicorn
Expand All @@ -207,7 +224,27 @@ async def app(scope, receive, send):
...

if __name__ == "__main__":
uvicorn.run("example:app", host="127.0.0.1", port=5000, log_level="info")
config = uvicorn.Config("main:app", port=5000, log_level="info")
server = uvicorn.Server(config)
server.run()
```

If you'd like to run Uvicorn from an already running async environment, use `uvicorn.Server.serve()` instead:

```python
import asyncio
import uvicorn

async def app(scope, receive, send):
...

async def main():
config = uvicorn.Config("main:app", port=5000, log_level="info")
server = uvicorn.Server(config)
await server.serve()

if __name__ == "__main__":
asyncio.run(main())
```

### Running with Gunicorn
Expand Down
8 changes: 4 additions & 4 deletions docs/settings.md
Expand Up @@ -32,13 +32,13 @@ For example, in case you want to run the app on port `5000`, just set the enviro
* `--reload` - Enable auto-reload. Uvicorn supports two versions of auto-reloading behavior enabled by this option. There are important differences between them.
* `--reload-dir <path>` - Specify which directories to watch for python file changes. May be used multiple times. If unused, then by default the whole current directory will be watched. If you are running programmatically use `reload_dirs=[]` and pass a list of strings.

### Reloading without watchgod
### Reloading without watchfiles

If Uvicorn _cannot_ load [watchgod](https://pypi.org/project/watchgod/) at runtime, it will periodically look for changes in modification times to all `*.py` files (and only `*.py` files) inside of its monitored directories. See the `--reload-dir` option. Specifying other file extensions is not supported unless watchgod is installed. See the `--reload-include` and `--reload-exclude` options for details.
If Uvicorn _cannot_ load [watchfiles](https://pypi.org/project/watchfiles/) at runtime, it will periodically look for changes in modification times to all `*.py` files (and only `*.py` files) inside of its monitored directories. See the `--reload-dir` option. Specifying other file extensions is not supported unless watchfiles is installed. See the `--reload-include` and `--reload-exclude` options for details.

### Reloading with watchgod
### Reloading with watchfiles

For more nuanced control over which file modifications trigger reloads, install `uvicorn[standard]`, which includes watchgod as a dependency. Alternatively, install [watchgod](https://pypi.org/project/watchgod/) where Unvicorn can see it. This will enable the following options (which are otherwise ignored).
For more nuanced control over which file modifications trigger reloads, install `uvicorn[standard]`, which includes watchfiles as a dependency. Alternatively, install [watchfiles](https://pypi.org/project/watchfiles/) where Uvicorn can see it. This will enable the following options (which are otherwise ignored).

* `--reload-include <glob-pattern>` - Specify a glob pattern to match files or directories which will be watched. May be used multiple times. By default the following patterns are included: `*.py`. These defaults can be overwritten by including them in `--reload-exclude`.
* `--reload-exclude <glob-pattern>` - Specify a glob pattern to match files or directories which will excluded from watching. May be used multiple times. By default the following patterns are excluded: `.*, .py[cod], .sw.*, ~*`. These defaults can be overwritten by including them in `--reload-include`.
Expand Down
3 changes: 1 addition & 2 deletions requirements.txt
Expand Up @@ -22,8 +22,7 @@ cryptography==3.4.8
coverage==6.4
coverage-conditional-plugin==0.5.0
httpx==1.0.0b0
pytest-asyncio==0.15.1

watchgod==0.8.2

# Documentation
mkdocs==1.3.0
Expand Down
5 changes: 4 additions & 1 deletion setup.cfg
Expand Up @@ -22,6 +22,7 @@ files =
uvicorn/supervisors/__init__.py,
uvicorn/middleware/debug.py,
uvicorn/middleware/wsgi.py,
uvicorn/supervisors/watchfilesreload.py,
uvicorn/supervisors/watchgodreload.py,
uvicorn/_logging.py,
uvicorn/middleware/asgi2.py,
Expand Down Expand Up @@ -62,7 +63,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,yaml
known_third_party = click,does_not_exist,gunicorn,h11,httptools,pytest,requests,setuptools,urllib3,uvloop,watchgod,watchfiles,websockets,wsproto,yaml

[tool:pytest]
addopts = -rxXs
Expand All @@ -72,6 +73,7 @@ xfail_strict=True
filterwarnings=
# Turn warnings that aren't filtered into exceptions
error
ignore: \"watchgod\" is depreciated\, you should switch to watchfiles \(`pip install watchfiles`\)\.:DeprecationWarning

[coverage:run]
omit = venv/*
Expand All @@ -88,6 +90,7 @@ exclude_lines =
pragma: no cover
pragma: nocover
if TYPE_CHECKING:
raise NotImplementedError

[coverage:coverage_conditional_plugin]
rules =
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -56,7 +56,7 @@ def get_packages(package):
"httptools>=0.4.0",
"uvloop>=0.14.0,!=0.15.0,!=0.15.1; " + env_marker_cpython,
"colorama>=0.4;" + env_marker_win,
"watchgod>=0.6",
"watchfiles>=0.13",
"python-dotenv>=0.13",
"PyYAML>=5.1",
]
Expand Down
28 changes: 28 additions & 0 deletions tests/conftest.py
Expand Up @@ -4,6 +4,8 @@
from hashlib import md5
from pathlib import Path
from tempfile import TemporaryDirectory
from threading import Thread
from time import sleep
from uuid import uuid4

import pytest
Expand Down Expand Up @@ -138,6 +140,11 @@ def reload_directory_structure(tmp_path_factory: pytest.TempPathFactory):
yield root


@pytest.fixture
def anyio_backend() -> str:
return "asyncio"


@pytest.fixture(scope="function")
def logging_config() -> dict:
return deepcopy(LOGGING_CONFIG)
Expand Down Expand Up @@ -176,3 +183,24 @@ def make_tmp_dir(base_dir):
sock_path = str(tmpd / "".join((identifier, socket_filename)))
yield sock_path
return


def sleep_touch(*paths: Path):
sleep(0.1)
for p in paths:
p.touch()


@pytest.fixture
def touch_soon():
threads = []

def start(*paths: Path):
thread = Thread(target=sleep_touch, args=paths)
thread.start()
threads.append(thread)

yield start

for t in threads:
t.join()
8 changes: 4 additions & 4 deletions tests/middleware/test_debug.py
Expand Up @@ -4,7 +4,7 @@
from uvicorn.middleware.debug import DebugMiddleware


@pytest.mark.asyncio
@pytest.mark.anyio
async def test_debug_text():
async def app(scope, receive, send):
raise RuntimeError("Something went wrong")
Expand All @@ -24,7 +24,7 @@ async def app(scope, receive, send):
assert "RuntimeError" in response.text


@pytest.mark.asyncio
@pytest.mark.anyio
async def test_debug_html():
async def app(scope, receive, send):
raise RuntimeError("Something went wrong")
Expand All @@ -43,7 +43,7 @@ async def app(scope, receive, send):
assert "RuntimeError" in response.text


@pytest.mark.asyncio
@pytest.mark.anyio
async def test_debug_after_response_sent():
async def app(scope, receive, send):
await send({"type": "http.response.start", "status": 204, "headers": []})
Expand All @@ -63,7 +63,7 @@ async def app(scope, receive, send):
assert response.content == b""


@pytest.mark.asyncio
@pytest.mark.anyio
async def test_debug_not_http():
async def app(scope, send, receive):
raise RuntimeError("Something went wrong")
Expand Down
12 changes: 6 additions & 6 deletions tests/middleware/test_logging.py
Expand Up @@ -27,7 +27,7 @@ async def app(scope, receive, send):
await send({"type": "http.response.body", "body": b"", "more_body": False})


@pytest.mark.asyncio
@pytest.mark.anyio
async def test_trace_logging(caplog, logging_config):
config = Config(
app=app, log_level="trace", log_config=logging_config, lifespan="auto"
Expand All @@ -48,7 +48,7 @@ async def test_trace_logging(caplog, logging_config):
assert "ASGI [2] Completed" in messages.pop(0)


@pytest.mark.asyncio
@pytest.mark.anyio
@pytest.mark.parametrize("http_protocol", [("h11"), ("httptools")])
async def test_trace_logging_on_http_protocol(http_protocol, caplog, logging_config):
config = Config(
Expand All @@ -71,7 +71,7 @@ async def test_trace_logging_on_http_protocol(http_protocol, caplog, logging_con
assert any(" - HTTP connection lost" in message for message in messages)


@pytest.mark.asyncio
@pytest.mark.anyio
@pytest.mark.parametrize("ws_protocol", [("websockets"), ("wsproto")])
async def test_trace_logging_on_ws_protocol(ws_protocol, caplog, logging_config):
async def websocket_app(scope, receive, send):
Expand Down Expand Up @@ -107,7 +107,7 @@ async def open_connection(url):
assert any(" - WebSocket connection lost" in message for message in messages)


@pytest.mark.asyncio
@pytest.mark.anyio
@pytest.mark.parametrize("use_colors", [(True), (False), (None)])
async def test_access_logging(use_colors, caplog, logging_config):
config = Config(app=app, use_colors=use_colors, log_config=logging_config)
Expand All @@ -125,7 +125,7 @@ async def test_access_logging(use_colors, caplog, logging_config):
assert '"GET / HTTP/1.1" 204' in messages.pop()


@pytest.mark.asyncio
@pytest.mark.anyio
@pytest.mark.parametrize("use_colors", [(True), (False)])
async def test_default_logging(use_colors, caplog, logging_config):
config = Config(app=app, use_colors=use_colors, log_config=logging_config)
Expand All @@ -146,7 +146,7 @@ async def test_default_logging(use_colors, caplog, logging_config):
assert "Shutting down" in messages.pop(0)


@pytest.mark.asyncio
@pytest.mark.anyio
async def test_unknown_status_code(caplog):
async def app(scope, receive, send):
assert scope["type"] == "http"
Expand Down
4 changes: 2 additions & 2 deletions tests/middleware/test_message_logger.py
Expand Up @@ -6,7 +6,7 @@
from uvicorn.middleware.message_logger import MessageLoggerMiddleware


@pytest.mark.asyncio
@pytest.mark.anyio
async def test_message_logger(caplog):
async def app(scope, receive, send):
await receive()
Expand All @@ -31,7 +31,7 @@ async def app(scope, receive, send):
)


@pytest.mark.asyncio
@pytest.mark.anyio
async def test_message_logger_exc(caplog):
async def app(scope, receive, send):
raise RuntimeError()
Expand Down
6 changes: 3 additions & 3 deletions tests/middleware/test_proxy_headers.py
Expand Up @@ -20,7 +20,7 @@ async def app(
await response(scope, receive, send)


@pytest.mark.asyncio
@pytest.mark.anyio
@pytest.mark.parametrize(
("trusted_hosts", "response_text"),
[
Expand Down Expand Up @@ -50,7 +50,7 @@ async def test_proxy_headers_trusted_hosts(
assert response.text == response_text


@pytest.mark.asyncio
@pytest.mark.anyio
@pytest.mark.parametrize(
("trusted_hosts", "response_text"),
[
Expand Down Expand Up @@ -87,7 +87,7 @@ async def test_proxy_headers_multiple_proxies(
assert response.text == response_text


@pytest.mark.asyncio
@pytest.mark.anyio
async def test_proxy_headers_invalid_x_forwarded_for() -> None:
app_with_middleware = ProxyHeadersMiddleware(app, trusted_hosts="*")
async with httpx.AsyncClient(
Expand Down

0 comments on commit 708c5f3

Please sign in to comment.