mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-27 08:10:57 -05:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a6e47bd49 | ||
|
|
58872dca74 | ||
|
|
9b04593260 | ||
|
|
6d77e2ac5f | ||
|
|
b16ca54c30 | ||
|
|
834723cf2c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ site
|
||||
coverage.xml
|
||||
.netlify
|
||||
test.db
|
||||
log.txt
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
## Next release
|
||||
|
||||
## 0.10.0
|
||||
|
||||
* Add support for Background Tasks in *path operation functions* and dependencies. New documentation about <a href="https://fastapi.tiangolo.com/tutorial/background-tasks/" target="_blank">Background Tasks is here</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/103" target="_blank">#103</a>.
|
||||
|
||||
* Add support for `.websocket_route()` in `APIRouter`. PR <a href="https://github.com/tiangolo/fastapi/pull/100" target="_blank">#100</a> by <a href="https://github.com/euri10" target="_blank">@euri10</a>.
|
||||
|
||||
* New docs section about <a href="https://fastapi.tiangolo.com/tutorial/events/" target="_blank">Events: startup - shutdown</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/99" target="_blank">#99</a>.
|
||||
|
||||
## 0.9.1
|
||||
|
||||
* Document receiving <a href="https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#query-parameter-list-multiple-values" target="_blank">Multiple values with the same query parameter</a> and <a href="https://fastapi.tiangolo.com/tutorial/header-params/#duplicate-headers" target="_blank">Duplicate headers</a>. PR <a href="https://github.com/tiangolo/fastapi/pull/95" target="_blank">#95</a>.
|
||||
|
||||
15
docs/src/background_tasks/tutorial001.py
Normal file
15
docs/src/background_tasks/tutorial001.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from fastapi import BackgroundTasks, FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
def write_notification(email: str, message=""):
|
||||
with open("log.txt", mode="w") as email_file:
|
||||
content = f"notification for {email}: {message}"
|
||||
email_file.write(content)
|
||||
|
||||
|
||||
@app.post("/send-notification/{email}")
|
||||
async def send_notification(email: str, background_tasks: BackgroundTasks):
|
||||
background_tasks.add_task(write_notification, email, message="some notification")
|
||||
return {"message": "Notification sent in the background"}
|
||||
24
docs/src/background_tasks/tutorial002.py
Normal file
24
docs/src/background_tasks/tutorial002.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from fastapi import BackgroundTasks, Depends, FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
def write_log(message: str):
|
||||
with open("log.txt", mode="a") as log:
|
||||
log.write(message)
|
||||
|
||||
|
||||
def get_query(background_tasks: BackgroundTasks, q: str = None):
|
||||
if q:
|
||||
message = f"found query: {q}\n"
|
||||
background_tasks.add_task(write_log, message)
|
||||
return q
|
||||
|
||||
|
||||
@app.post("/send-notification/{email}")
|
||||
async def send_notification(
|
||||
email: str, background_tasks: BackgroundTasks, q: str = Depends(get_query)
|
||||
):
|
||||
message = f"message to {email}\n"
|
||||
background_tasks.add_task(write_log, message)
|
||||
return {"message": "Message sent"}
|
||||
16
docs/src/events/tutorial001.py
Normal file
16
docs/src/events/tutorial001.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
items = {}
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
items["foo"] = {"name": "Fighters"}
|
||||
items["bar"] = {"name": "Tenders"}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_item(item_id: str):
|
||||
return items[item_id]
|
||||
14
docs/src/events/tutorial002.py
Normal file
14
docs/src/events/tutorial002.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.on_event("shutdown")
|
||||
def startup_event():
|
||||
with open("log.txt", mode="a") as log:
|
||||
log.write("Application shutdown")
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items():
|
||||
return [{"name": "Foo"}]
|
||||
86
docs/tutorial/background-tasks.md
Normal file
86
docs/tutorial/background-tasks.md
Normal file
@@ -0,0 +1,86 @@
|
||||
You can define background tasks to be run *after* returning a response.
|
||||
|
||||
This is useful for operations that need to happen after a request, but that the client doesn't really have to be waiting for the operation to complete before receiving his response.
|
||||
|
||||
This includes, for example:
|
||||
|
||||
* Email notifications sent after performing an action:
|
||||
* As connecting to an email server and sending an email tends to be "slow" (several seconds), you can return the response right away and send the email notification in the background.
|
||||
* Processing data:
|
||||
* For example, let's say you receive a file that must go through a slow process, you can return a response of "Accepted" (HTTP 202) and process it in the background.
|
||||
|
||||
## Using `BackgroundTasks`
|
||||
|
||||
First, import `BackgroundTasks` and define a parameter in your *path operation function* with a type declaration of `BackgroundTasks`:
|
||||
|
||||
```Python hl_lines="1 13"
|
||||
{!./src/background_tasks/tutorial001.py!}
|
||||
```
|
||||
|
||||
**FastAPI** will create the object of type `BackgroundTasks` for you and pass it as that parameter.
|
||||
|
||||
!!! tip
|
||||
You declare a parameter of `BackgroundTasks` and use it in a very similar way as to when <a href="/tutorial/using-request-directly/" target="_blank">using the `Request` directly</a>.
|
||||
|
||||
|
||||
## Create a task function
|
||||
|
||||
Create a function to be run as the background task.
|
||||
|
||||
It is just a standard function that can receive parameters.
|
||||
|
||||
It can be an `async def` or normal `def` function, **FastAPI** will know how to handle it correctly.
|
||||
|
||||
In this case, the task function will write to a file (simulating sending an email).
|
||||
|
||||
And as the write operation doesn't use `async` and `await`, we define the function with normal `def`:
|
||||
|
||||
```Python hl_lines="6 7 8 9"
|
||||
{!./src/background_tasks/tutorial001.py!}
|
||||
```
|
||||
|
||||
## Add the background task
|
||||
|
||||
Inside of your *path operation function*, pass your task function to the *background tasks* object with the method `.add_task()`:
|
||||
|
||||
```Python hl_lines="14"
|
||||
{!./src/background_tasks/tutorial001.py!}
|
||||
```
|
||||
|
||||
`.add_task()` receives as arguments:
|
||||
|
||||
* A task function to be run in the background (`write_notification`).
|
||||
* Any sequence of arguments that should be passed to the task function in order (`email`).
|
||||
* Any keyword arguments that should be passed to the task function (`message="some notification"`).
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
Using `BackgroundTasks` also works with the dependency injection system, you can declare a parameter of type `BackgroundTasks` at multiple levels: in a *path operation function*, in a dependency (dependable), in a sub-dependency, etc.
|
||||
|
||||
**FastAPI** knows what to do in each case and how to re-use the same object, so that all the background tasks are merged together and are run in the background afterwards:
|
||||
|
||||
```Python hl_lines="11 14 20 23"
|
||||
{!./src/background_tasks/tutorial002.py!}
|
||||
```
|
||||
|
||||
In this example, the messages will be written to the `log.txt` file *after* the response is sent.
|
||||
|
||||
If there was a query in the request, it will be written to the log in a background task.
|
||||
|
||||
And then another background task generated at the *path operation function* will write a message using the `email` path parameter.
|
||||
|
||||
## Technical Details
|
||||
|
||||
The class `BackgroundTasks` comes directly from <a href="https://www.starlette.io/background/" target="_blank">`starlette.background`</a>.
|
||||
|
||||
It is imported/included directly into FastAPI so that you can import it from `fastapi` and avoid accidentally importing the alternative `BackgroundTask` (without the `s` at the end) from `starlette.background`.
|
||||
|
||||
By only using `BackgroundTasks` (and not `BackgroundTask`), it's then possible to use it as a *path operation function* parameter and have **FastAPI** handle the rest for you, just like when using the `Request` object directly.
|
||||
|
||||
It's still possible to use `BackgroundTask` alone in FastAPI, but you have to create the object in your code and return a Starlette `Response` including it.
|
||||
|
||||
You can see more details in <a href="https://www.starlette.io/background/" target="_blank">Starlette's official docs for Background Tasks</a>.
|
||||
|
||||
## Recap
|
||||
|
||||
Import and use `BackgroundTasks` with parameters in *path operation functions* and dependencies to add background tasks.
|
||||
43
docs/tutorial/events.md
Normal file
43
docs/tutorial/events.md
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
You can define event handlers (functions) that need to be executed before the application starts up, or when the application is shutting down.
|
||||
|
||||
These functions can be declared with `async def` or normal `def`.
|
||||
|
||||
## `startup` event
|
||||
|
||||
To add a function that should be run before the application starts, declare it with the event `"startup"`:
|
||||
|
||||
```Python hl_lines="8"
|
||||
{!./src/events/tutorial001.py!}
|
||||
```
|
||||
|
||||
In this case, the `startup` event handler function will initialize the items "database" (just a `dict`) with some values.
|
||||
|
||||
You can add more than one event handler function.
|
||||
|
||||
And your application won't start receiving requests until all the `startup` event handlers have completed.
|
||||
|
||||
## `shutdown` event
|
||||
|
||||
To add a function that should be run when the application is shutting down, declare it with the event `"shutdown"`:
|
||||
|
||||
```Python hl_lines="6"
|
||||
{!./src/events/tutorial002.py!}
|
||||
```
|
||||
|
||||
Here, the `shutdown` event handler function will write a text line `"Application shutdown"` to a file `log.txt`.
|
||||
|
||||
!!! info
|
||||
In the `open()` function, the `mode="a"` means "append", so, the line will be added after whatever is on that file, without overwriting the previous contents.
|
||||
|
||||
!!! tip
|
||||
Notice that in this case we are using a standard Python `open()` function that interacts with a file.
|
||||
|
||||
So, it involves I/O (input/output), that requires "waiting" for things to be written to disk.
|
||||
|
||||
But `open()` doesn't use `async` and `await`.
|
||||
|
||||
So, we declare the event handler function with standard `def` instead of `async def`.
|
||||
|
||||
!!! info
|
||||
You can read more about these event handlers in <a href="https://www.starlette.io/events/" target="_blank">Starlette's Events' docs</a>.
|
||||
@@ -1,6 +1,8 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.9.1"
|
||||
__version__ = "0.10.0"
|
||||
|
||||
from starlette.background import BackgroundTasks
|
||||
|
||||
from .applications import FastAPI
|
||||
from .datastructures import UploadFile
|
||||
|
||||
@@ -26,6 +26,7 @@ class Dependant:
|
||||
name: str = None,
|
||||
call: Callable = None,
|
||||
request_param_name: str = None,
|
||||
background_tasks_param_name: str = None,
|
||||
) -> None:
|
||||
self.path_params = path_params or []
|
||||
self.query_params = query_params or []
|
||||
@@ -35,5 +36,6 @@ class Dependant:
|
||||
self.dependencies = dependencies or []
|
||||
self.security_requirements = security_schemes or []
|
||||
self.request_param_name = request_param_name
|
||||
self.background_tasks_param_name = background_tasks_param_name
|
||||
self.name = name
|
||||
self.call = call
|
||||
|
||||
@@ -3,7 +3,18 @@ import inspect
|
||||
from copy import deepcopy
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Any, Callable, Dict, List, Mapping, Sequence, Tuple, Type, Union
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
List,
|
||||
Mapping,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import params
|
||||
@@ -16,6 +27,7 @@ from pydantic.errors import MissingError
|
||||
from pydantic.fields import Field, Required, Shape
|
||||
from pydantic.schema import get_annotation_from_schema
|
||||
from pydantic.utils import lenient_issubclass
|
||||
from starlette.background import BackgroundTasks
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
from starlette.datastructures import UploadFile
|
||||
from starlette.requests import Headers, QueryParams, Request
|
||||
@@ -125,6 +137,8 @@ def get_dependant(*, path: str, call: Callable, name: str = None) -> Dependant:
|
||||
)
|
||||
elif lenient_issubclass(param.annotation, Request):
|
||||
dependant.request_param_name = param_name
|
||||
elif lenient_issubclass(param.annotation, BackgroundTasks):
|
||||
dependant.background_tasks_param_name = param_name
|
||||
elif not isinstance(param.default, params.Depends):
|
||||
add_param_to_body_fields(param=param, dependant=dependant)
|
||||
return dependant
|
||||
@@ -215,13 +229,20 @@ def is_coroutine_callable(call: Callable) -> bool:
|
||||
|
||||
|
||||
async def solve_dependencies(
|
||||
*, request: Request, dependant: Dependant, body: Dict[str, Any] = None
|
||||
) -> Tuple[Dict[str, Any], List[ErrorWrapper]]:
|
||||
*,
|
||||
request: Request,
|
||||
dependant: Dependant,
|
||||
body: Dict[str, Any] = None,
|
||||
background_tasks: BackgroundTasks = None,
|
||||
) -> Tuple[Dict[str, Any], List[ErrorWrapper], Optional[BackgroundTasks]]:
|
||||
values: Dict[str, Any] = {}
|
||||
errors: List[ErrorWrapper] = []
|
||||
for sub_dependant in dependant.dependencies:
|
||||
sub_values, sub_errors = await solve_dependencies(
|
||||
request=request, dependant=sub_dependant, body=body
|
||||
sub_values, sub_errors, background_tasks = await solve_dependencies(
|
||||
request=request,
|
||||
dependant=sub_dependant,
|
||||
body=body,
|
||||
background_tasks=background_tasks,
|
||||
)
|
||||
if sub_errors:
|
||||
errors.extend(sub_errors)
|
||||
@@ -258,7 +279,11 @@ async def solve_dependencies(
|
||||
errors.extend(body_errors)
|
||||
if dependant.request_param_name:
|
||||
values[dependant.request_param_name] = request
|
||||
return values, errors
|
||||
if dependant.background_tasks_param_name:
|
||||
if background_tasks is None:
|
||||
background_tasks = BackgroundTasks()
|
||||
values[dependant.background_tasks_param_name] = background_tasks
|
||||
return values, errors, background_tasks
|
||||
|
||||
|
||||
def request_params_to_args(
|
||||
|
||||
@@ -68,7 +68,7 @@ def get_app(
|
||||
raise HTTPException(
|
||||
status_code=400, detail="There was an error parsing the body"
|
||||
)
|
||||
values, errors = await solve_dependencies(
|
||||
values, errors, background_tasks = await solve_dependencies(
|
||||
request=request, dependant=dependant, body=body
|
||||
)
|
||||
if errors:
|
||||
@@ -83,11 +83,17 @@ def get_app(
|
||||
else:
|
||||
raw_response = await run_in_threadpool(dependant.call, **values)
|
||||
if isinstance(raw_response, Response):
|
||||
if raw_response.background is None:
|
||||
raw_response.background = background_tasks
|
||||
return raw_response
|
||||
response_data = serialize_response(
|
||||
field=response_field, response=raw_response
|
||||
)
|
||||
return content_type(content=response_data, status_code=status_code)
|
||||
return content_type(
|
||||
content=response_data,
|
||||
status_code=status_code,
|
||||
background=background_tasks,
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
@@ -271,6 +277,10 @@ class APIRouter(routing.Router):
|
||||
include_in_schema=route.include_in_schema,
|
||||
name=route.name,
|
||||
)
|
||||
elif isinstance(route, routing.WebSocketRoute):
|
||||
self.add_websocket_route(
|
||||
prefix + route.path, route.endpoint, name=route.name
|
||||
)
|
||||
|
||||
def get(
|
||||
self,
|
||||
|
||||
@@ -59,10 +59,12 @@ nav:
|
||||
- SQL (Relational) Databases: 'tutorial/sql-databases.md'
|
||||
- NoSQL (Distributed / Big Data) Databases: 'tutorial/nosql-databases.md'
|
||||
- Bigger Applications - Multiple Files: 'tutorial/bigger-applications.md'
|
||||
- Background Tasks: 'tutorial/background-tasks.md'
|
||||
- Sub Applications - Behind a Proxy: 'tutorial/sub-applications-proxy.md'
|
||||
- Application Configuration: 'tutorial/application-configuration.md'
|
||||
- GraphQL: 'tutorial/graphql.md'
|
||||
- WebSockets: 'tutorial/websockets.md'
|
||||
- 'Events: startup - shutdown': 'tutorial/events.md'
|
||||
- Debugging: 'tutorial/debugging.md'
|
||||
- Concurrency and async / await: 'async.md'
|
||||
- Deployment: 'deployment.md'
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from background_tasks.tutorial001 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test():
|
||||
log = Path("log.txt")
|
||||
if log.is_file():
|
||||
os.remove(log) # pragma: no cover
|
||||
response = client.post("/send-notification/foo@example.com")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"message": "Notification sent in the background"}
|
||||
with open("./log.txt") as f:
|
||||
assert "notification for foo@example.com: some notification" in f.read()
|
||||
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from background_tasks.tutorial002 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
def test():
|
||||
log = Path("log.txt")
|
||||
if log.is_file():
|
||||
os.remove(log) # pragma: no cover
|
||||
response = client.post("/send-notification/foo@example.com?q=some-query")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"message": "Message sent"}
|
||||
with open("./log.txt") as f:
|
||||
assert "found query: some-query\nmessage to foo@example.com" in f.read()
|
||||
0
tests/test_tutorial/test_events/__init__.py
Normal file
0
tests/test_tutorial/test_events/__init__.py
Normal file
79
tests/test_tutorial/test_events/test_tutorial001.py
Normal file
79
tests/test_tutorial/test_events/test_tutorial001.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from events.tutorial001 import app
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "Fast API", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/{item_id}": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Read Item Get",
|
||||
"operationId": "read_item_items__item_id__get",
|
||||
"parameters": [
|
||||
{
|
||||
"required": True,
|
||||
"schema": {"title": "Item_Id", "type": "string"},
|
||||
"name": "item_id",
|
||||
"in": "path",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_events():
|
||||
with TestClient(app) as client:
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == openapi_schema
|
||||
response = client.get("/items/foo")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"name": "Fighters"}
|
||||
34
tests/test_tutorial/test_events/test_tutorial002.py
Normal file
34
tests/test_tutorial/test_events/test_tutorial002.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from events.tutorial002 import app
|
||||
|
||||
openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "Fast API", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
}
|
||||
},
|
||||
"summary": "Read Items Get",
|
||||
"operationId": "read_items_items__get",
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_events():
|
||||
with TestClient(app) as client:
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == openapi_schema
|
||||
response = client.get("/items/")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == [{"name": "Foo"}]
|
||||
with open("log.txt") as log:
|
||||
assert "Application shutdown" in log.read()
|
||||
53
tests/test_ws_router.py
Normal file
53
tests/test_ws_router.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from starlette.testclient import TestClient
|
||||
from starlette.websockets import WebSocket
|
||||
|
||||
router = APIRouter()
|
||||
prefix_router = APIRouter()
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.websocket_route("/")
|
||||
async def index(websocket: WebSocket):
|
||||
await websocket.accept()
|
||||
await websocket.send_text("Hello, world!")
|
||||
await websocket.close()
|
||||
|
||||
|
||||
@router.websocket_route("/router")
|
||||
async def routerindex(websocket: WebSocket):
|
||||
await websocket.accept()
|
||||
await websocket.send_text("Hello, router!")
|
||||
await websocket.close()
|
||||
|
||||
|
||||
@prefix_router.websocket_route("/")
|
||||
async def routerprefixindex(websocket: WebSocket):
|
||||
await websocket.accept()
|
||||
await websocket.send_text("Hello, router with prefix!")
|
||||
await websocket.close()
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
app.include_router(prefix_router, prefix="/prefix")
|
||||
|
||||
|
||||
def test_app():
|
||||
client = TestClient(app)
|
||||
with client.websocket_connect("/") as websocket:
|
||||
data = websocket.receive_text()
|
||||
assert data == "Hello, world!"
|
||||
|
||||
|
||||
def test_router():
|
||||
client = TestClient(app)
|
||||
with client.websocket_connect("/router") as websocket:
|
||||
data = websocket.receive_text()
|
||||
assert data == "Hello, router!"
|
||||
|
||||
|
||||
def test_prefix_router():
|
||||
client = TestClient(app)
|
||||
with client.websocket_connect("/prefix/") as websocket:
|
||||
data = websocket.receive_text()
|
||||
assert data == "Hello, router with prefix!"
|
||||
Reference in New Issue
Block a user