diff --git a/.gitignore b/.gitignore index b09dc3076..a3df1d054 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ site coverage.xml .netlify test.db +log.txt diff --git a/docs/src/events/tutorial001.py b/docs/src/events/tutorial001.py new file mode 100644 index 000000000..6a0bd5a20 --- /dev/null +++ b/docs/src/events/tutorial001.py @@ -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] diff --git a/docs/src/events/tutorial002.py b/docs/src/events/tutorial002.py new file mode 100644 index 000000000..e28d5f5ba --- /dev/null +++ b/docs/src/events/tutorial002.py @@ -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"}] diff --git a/docs/tutorial/events.md b/docs/tutorial/events.md new file mode 100644 index 000000000..4998e50f9 --- /dev/null +++ b/docs/tutorial/events.md @@ -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 Starlette's Events' docs. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 2760194af..0e4e91c4a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -63,6 +63,7 @@ nav: - 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' diff --git a/tests/test_tutorial/test_events/__init__.py b/tests/test_tutorial/test_events/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_tutorial/test_events/test_tutorial001.py b/tests/test_tutorial/test_events/test_tutorial001.py new file mode 100644 index 000000000..2b05f1375 --- /dev/null +++ b/tests/test_tutorial/test_events/test_tutorial001.py @@ -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"} diff --git a/tests/test_tutorial/test_events/test_tutorial002.py b/tests/test_tutorial/test_events/test_tutorial002.py new file mode 100644 index 000000000..edcf9944c --- /dev/null +++ b/tests/test_tutorial/test_events/test_tutorial002.py @@ -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()