Coming soon...
```Python
-{!./tutorial/src/bigger-applications/tutorial001.py!}
+{!./tutorial/src/bigger_applications/app/routers/tutorial001.py!}
```
```Python
-{!./tutorial/src/bigger-applications/tutorial002.py!}
+{!./tutorial/src/bigger_applications/app/routers/tutorial002.py!}
```
```Python
-{!./tutorial/src/bigger-applications/tutorial003.py!}
+{!./tutorial/src/bigger_applications/app/tutorial003.py!}
```
First, of course, you can mix `Path`, `Query` and request body parameter declarations freely and **FastAPI** will know what to do.
-```Python hl_lines="17 18 19"
-{!./tutorial/src/body-multiple-params/tutorial001.py!}
+And you can also declare body parameters as optional, by setting the default to `None`:
+
+```Python hl_lines="18 19 20"
+{!./tutorial/src/body_multiple_params/tutorial001.py!}
```
!!! note
But you can also declare multiple body parameters, e.g. `item` and `user`:
-```Python hl_lines="20"
-{!./tutorial/src/body-multiple-params/tutorial002.py!}
+```Python hl_lines="21"
+{!./tutorial/src/body_multiple_params/tutorial002.py!}
```
In this case, **FastAPI** will notice that there are more than one body parameter in the function (two parameters that are Pydantic models).
But you can instruct **FastAPI** to treat it as another body key using `Body`:
-```Python hl_lines="21"
-{!./tutorial/src/body-multiple-params/tutorial003.py!}
+```Python hl_lines="22"
+{!./tutorial/src/body_multiple_params/tutorial003.py!}
```
In this case, **FastAPI** will expect a body like:
as in:
-```Python hl_lines="25"
-{!./tutorial/src/body-multiple-params/tutorial004.py!}
+```Python hl_lines="27"
+{!./tutorial/src/body_multiple_params/tutorial004.py!}
```
!!! info
as in:
-```Python hl_lines="15"
-{!./tutorial/src/body-multiple-params/tutorial005.py!}
+```Python hl_lines="16"
+{!./tutorial/src/body_multiple_params/tutorial005.py!}
```
In this case **FastAPI** will expect a body like:
You can define an attribute to be a subtype. For example, a Python `list`:
-```Python hl_lines="12"
-{!./tutorial/src/body-nested-models/tutorial001.py!}
+```Python hl_lines="13"
+{!./tutorial/src/body_nested_models/tutorial001.py!}
```
This will make `tags` be a list of items. Although it doesn't declare the type of each of the items.
First, import `List` from standard Python's `typing` module:
```Python hl_lines="1"
-{!./tutorial/src/body-nested-models/tutorial002.py!}
+{!./tutorial/src/body_nested_models/tutorial002.py!}
```
### Declare a `List` with a subtype
So, in our example, we can make `tags` be specifically a "list of strings":
-```Python hl_lines="14"
-{!./tutorial/src/body-nested-models/tutorial002.py!}
+```Python hl_lines="15"
+{!./tutorial/src/body_nested_models/tutorial002.py!}
```
## Set types
Then we can import `Set` and declare `tags` as a `set` of `str`:
-```Python hl_lines="1 14"
-{!./tutorial/src/body-nested-models/tutorial003.py!}
+```Python hl_lines="1 15"
+{!./tutorial/src/body_nested_models/tutorial003.py!}
```
With this, even if you receive a request with duplicate data, it will be converted to a set of unique items.
For example, we can define an `Image` model:
-```Python hl_lines="9 10 11"
-{!./tutorial/src/body-nested-models/tutorial004.py!}
+```Python hl_lines="10 11 12"
+{!./tutorial/src/body_nested_models/tutorial004.py!}
```
### Use the submodel as a type
And then we can use it as the type of an attribute:
-```Python hl_lines="20"
-{!./tutorial/src/body-nested-models/tutorial004.py!}
+```Python hl_lines="21"
+{!./tutorial/src/body_nested_models/tutorial004.py!}
```
This would mean that **FastAPI** would expect a body similar to:
For example, as in the `Image` model we have a `url` field, we can declare it to be instead of a `str`, a Pydantic's `UrlStr`:
-```Python hl_lines="5 11"
-{!./tutorial/src/body-nested-models/tutorial005.py!}
+```Python hl_lines="4 12"
+{!./tutorial/src/body_nested_models/tutorial005.py!}
```
The string will be checked to be a valid URL, and documented in JSON Schema / OpenAPI as such.
You can also use Pydantic models as subtypes of `list`, `set`, etc:
-```Python hl_lines="21"
-{!./tutorial/src/body-nested-models/tutorial006.py!}
+```Python hl_lines="22"
+{!./tutorial/src/body_nested_models/tutorial006.py!}
```
This will expect (convert, validate, document, etc) a JSON body like:
You can define arbitrarily deeply nested models:
-```Python hl_lines="10 15 21 24 28"
-{!./tutorial/src/body-nested-models/tutorial007.py!}
+```Python hl_lines="11 16 22 25 29"
+{!./tutorial/src/body_nested_models/tutorial007.py!}
```
!!! info
as in:
-```Python hl_lines="16"
-{!./tutorial/src/body-nested-models/tutorial008.py!}
+```Python hl_lines="17"
+{!./tutorial/src/body_nested_models/tutorial008.py!}
```
## Editor support everywhere
First, you have to import it:
-```Python hl_lines="2"
-{!./tutorial/src/body-schema/tutorial001.py!}
+```Python hl_lines="3"
+{!./tutorial/src/body_schema/tutorial001.py!}
```
!!! warning
You can then use `Schema` with model attributes:
-```Python hl_lines="9 10"
-{!./tutorial/src/body-schema/tutorial001.py!}
+```Python hl_lines="10 11"
+{!./tutorial/src/body_schema/tutorial001.py!}
```
`Schema` works the same way as `Query`, `Path` and `Body`, it has all the same parameters, etc.
For example, you can use that functionality to pass a <a href="http://json-schema.org/latest/json-schema-validation.html#rfc.section.8.5" target="_blank">JSON Schema example</a> field to a body request JSON Schema:
-```Python hl_lines="20 21 22 23 24 25"
-{!./tutorial/src/body-schema/tutorial002.py!}
+```Python hl_lines="21 22 23 24 25 26"
+{!./tutorial/src/body_schema/tutorial002.py!}
```
## Recap
First, you need to import `BaseModel` from `pydantic`:
-```Python hl_lines="2"
+```Python hl_lines="1"
{!./tutorial/src/body/tutorial001.py!}
```
Use standard Python types for all the attributes:
-```Python hl_lines="5 6 7 8 9"
+```Python hl_lines="6 7 8 9 10"
{!./tutorial/src/body/tutorial001.py!}
```
To add it to your path operation, declare it the same way you declared path and query parameters:
-```Python hl_lines="16"
+```Python hl_lines="17"
{!./tutorial/src/body/tutorial001.py!}
```
Inside of the function, you can access all the attributes of the model object directly:
-```Python hl_lines="19"
+```Python hl_lines="20"
{!./tutorial/src/body/tutorial002.py!}
```
**FastAPI** will recognize that the function parameters that match path parameters should be **taken from the path**, and that function parameters that are declared to be Pydantic models should be **taken from the request body**.
-```Python hl_lines="15 16"
+```Python hl_lines="16 17"
{!./tutorial/src/body/tutorial003.py!}
```
**FastAPI** will recognize each of them and take the data from the correct place.
-```Python hl_lines="16"
+```Python hl_lines="17"
{!./tutorial/src/body/tutorial004.py!}
```
First import `Cookie`:
```Python hl_lines="1"
-{!./tutorial/src/cookie-params/tutorial001.py!}
+{!./tutorial/src/cookie_params/tutorial001.py!}
```
## Declare `Cookie` parameteres
The first value is the default value, you can pass all the extra validation or annotation parameteres:
```Python hl_lines="7"
-{!./tutorial/src/cookie-params/tutorial001.py!}
+{!./tutorial/src/cookie_params/tutorial001.py!}
```
!!! info
### Import `UJSONResponse`
-```Python hl_lines="2"
-{!./tutorial/src/custom-response/tutorial001.py!}
+```Python hl_lines="1"
+{!./tutorial/src/custom_response/tutorial001.py!}
```
!!! note
Make your path operation use `UJSONResponse` as the response class using the parameter `content_type`:
-```Python hl_lines="7"
-{!./tutorial/src/custom-response/tutorial001.py!}
+```Python hl_lines="8"
+{!./tutorial/src/custom_response/tutorial001.py!}
```
!!! info
### Import `HTMLResponse`
-```Python hl_lines="2"
-{!./tutorial/src/custom-response/tutorial002.py!}
+```Python hl_lines="1"
+{!./tutorial/src/custom_response/tutorial002.py!}
```
!!! note
Pass `HTMLResponse` as the parameter `content_type` of your path operation:
-```Python hl_lines="7"
-{!./tutorial/src/custom-response/tutorial002.py!}
+```Python hl_lines="8"
+{!./tutorial/src/custom_response/tutorial002.py!}
```
!!! info
The same example from above, returning an `HTMLResponse`, could look like:
-```Python hl_lines="7"
-{!./tutorial/src/custom-response/tutorial003.py!}
+```Python hl_lines="8 20"
+{!./tutorial/src/custom_response/tutorial003.py!}
```
!!! info
For example, it could be something like:
-```Python hl_lines="7 23"
-{!./tutorial/src/custom-response/tutorial004.py!}
+```Python hl_lines="8 19 22"
+{!./tutorial/src/custom_response/tutorial004.py!}
```
In this example, the function `generate_html_response()` already generates a Starlette `Response` instead of the HTML in a `str`.
But by declaring it also in the path operation decorator:
-```Python hl_lines="21"
-{!./tutorial/src/custom-response/tutorial004.py!}
+```Python hl_lines="22"
+{!./tutorial/src/custom_response/tutorial004.py!}
```
#### OpenAPI knows how to document it
Create a model for the common parameters (and don't pay attention to the rest, for now):
-```Python hl_lines="10 11 12 13"
+```Python hl_lines="11 12 13 14"
{!./tutorial/src/dependencies/tutorial002.py!}
```
Now we can return a Pydantic model from the dependency ("dependable") with the same data as the dict before:
-```Python hl_lines="17"
+```Python hl_lines="18"
{!./tutorial/src/dependencies/tutorial002.py!}
```
It won't be interpreted as a JSON request `Body` because we are using `Depends`:
-```Python hl_lines="21"
+```Python hl_lines="22"
{!./tutorial/src/dependencies/tutorial002.py!}
```
And now we can use that model in our code, with all the lovable editor support:
-```Python hl_lines="23 24 25"
+```Python hl_lines="24 25 26"
{!./tutorial/src/dependencies/tutorial002.py!}
```
This is especially the case for user models, because:
-* The **input model** needs to be able to have a password
-* The **output model** should do not have a password
-* The **database model** would probably need to have a hashed password
+* The **input model** needs to be able to have a password.
+* The **output model** should do not have a password.
+* The **database model** would probably need to have a hashed password.
!!! danger
Never store user's plaintext passwords. Always store a secure hash that you can then verify.
Here's a general idea of how the models could look like with their password fields and the places where they are used:
-```Python hl_lines="8 10 15 21 23 32 34 39 40"
-{!./tutorial/src/extra-models/tutorial001.py!}
+```Python hl_lines="9 11 16 22 24 33 35 40 41"
+{!./tutorial/src/extra_models/tutorial001.py!}
```
!!! warning
That way, we can declare just the differences between the models (with plaintext `password`, with `hashed_password` and without password):
-```Python hl_lines="8 14 15 18 19 22 23"
-{!./tutorial/src/extra-models/tutorial002.py!}
+```Python hl_lines="9 15 16 19 20 23 24"
+{!./tutorial/src/extra_models/tutorial002.py!}
```
## Recap
The simplest FastAPI file could look like this:
```Python
-{!tutorial/src/first-steps/tutorial001.py!}
+{!tutorial/src/first_steps/tutorial001.py!}
```
Copy that to a file `main.py`.
### Step 1: import `FastAPI`
```Python hl_lines="1"
-{!tutorial/src/first-steps/tutorial001.py!}
+{!tutorial/src/first_steps/tutorial001.py!}
```
`FastAPI` is a Python class that provides all the functionality for your API.
### Step 2: create a `FastAPI` "instance"
```Python hl_lines="3"
-{!tutorial/src/first-steps/tutorial001.py!}
+{!tutorial/src/first_steps/tutorial001.py!}
```
Here the `app` variable will be an "instance" of the class `FastAPI`.
If you create your app like:
```Python hl_lines="3"
-{!tutorial/src/first-steps/tutorial002.py!}
+{!tutorial/src/first_steps/tutorial002.py!}
```
And put it in a file `main.py`, then you would call `uvicorn` like:
#### Define a path operation function
```Python hl_lines="6"
-{!tutorial/src/first-steps/tutorial001.py!}
+{!tutorial/src/first_steps/tutorial001.py!}
```
The `@app.get("/")` tells **FastAPI** that the function right below is in charge of handling requests that go to:
### Step 4: define the path operation function
```Python hl_lines="7"
-{!tutorial/src/first-steps/tutorial001.py!}
+{!tutorial/src/first_steps/tutorial001.py!}
```
This is a Python function.
You could also define it as a normal function instead of `async def`:
```Python hl_lines="7"
-{!tutorial/src/first-steps/tutorial003.py!}
+{!tutorial/src/first_steps/tutorial003.py!}
```
To know the difference, read the section about [Concurrency and `async` / `await`](/async/).
### Step 5: return the content
```Python hl_lines="8"
-{!tutorial/src/first-steps/tutorial001.py!}
+{!tutorial/src/first_steps/tutorial001.py!}
```
You can return a `dict`, `list`, singular values as `str`, `int`, etc.
First import `Header`:
```Python hl_lines="1"
-{!./tutorial/src/header-params/tutorial001.py!}
+{!./tutorial/src/header_params/tutorial001.py!}
```
## Declare `Header` parameteres
The first value is the default value, you can pass all the extra validation or annotation parameteres:
```Python hl_lines="7"
-{!./tutorial/src/header-params/tutorial001.py!}
+{!./tutorial/src/header_params/tutorial001.py!}
```
!!! info
If for some reason you need to disable automatic conversion of underscores to hyphens, set the parameter `convert_underscores` of `Header` to `False`:
```Python hl_lines="7"
-{!./tutorial/src/header-params/tutorial002.py!}
+{!./tutorial/src/header_params/tutorial002.py!}
```
!!! warning
For now, don't pay attention to the rest, only the imports:
-```Python hl_lines="6 7 8"
-{!./tutorial/src/nosql-databases/tutorial001.py!}
+```Python hl_lines="5 6 7"
+{!./tutorial/src/nosql_databases/tutorial001.py!}
```
## Define a constant to use as a "document type"
This is not required by Couchbase, but is a good practice that will help you afterwards.
```Python hl_lines="10"
-{!./tutorial/src/nosql-databases/tutorial001.py!}
+{!./tutorial/src/nosql_databases/tutorial001.py!}
```
## Add a function to get a `Bucket`
* Set defaults for timeouts.
* Return it.
-```Python hl_lines="13 14 15 16 17 18 19 20"
-{!./tutorial/src/nosql-databases/tutorial001.py!}
+```Python hl_lines="13 14 15 16 17 18 19 20 21 22"
+{!./tutorial/src/nosql_databases/tutorial001.py!}
```
## Create Pydantic models
First, let's create a `User` model:
-```Python hl_lines="23 24 25 26 27"
-{!./tutorial/src/nosql-databases/tutorial001.py!}
+```Python hl_lines="25 26 27 28 29"
+{!./tutorial/src/nosql_databases/tutorial001.py!}
```
We will use this model in our path operation function, so, we don't include in it the `hashed_password`.
We don't create it as a subclass of Pydantic's `BaseModel` but as a subclass of our own `User`, because it will have all the attributes in `User` plus a couple more:
-```Python hl_lines="30 31 32"
-{!./tutorial/src/nosql-databases/tutorial001.py!}
+```Python hl_lines="32 33 34"
+{!./tutorial/src/nosql_databases/tutorial001.py!}
```
!!! note
By creating a function that is only dedicated to getting your user from a `username` (or any other parameter) independent of your path operation function, you can more easily re-use it in multiple parts and also add <abbr title="Automated test, written in code, that checks if another piece of code is working correctly.">unit tests</abbr> for it:
-```Python hl_lines="35 36 37 38 39 40 41"
-{!./tutorial/src/nosql-databases/tutorial001.py!}
+```Python hl_lines="37 38 39 40 41 42 43"
+{!./tutorial/src/nosql_databases/tutorial001.py!}
```
### f-strings
### Create the `FastAPI` app
-```Python hl_lines="45"
-{!./tutorial/src/nosql-databases/tutorial001.py!}
+```Python hl_lines="47"
+{!./tutorial/src/nosql_databases/tutorial001.py!}
```
### Create the path operation function
Also, Couchbase recommends not using a single `Bucket` object in multiple "<abbr title="A sequence of code being executed by the program, while at the same time, or at intervals, there can be others being executed too.">thread</abbr>s", so, we can get just get the bucket directly and pass it to our utility functions:
-```Python hl_lines="48 49 50 51 52"
-{!./tutorial/src/nosql-databases/tutorial001.py!}
+```Python hl_lines="50 51 52 53 54"
+{!./tutorial/src/nosql_databases/tutorial001.py!}
```
## Recap
You would have to make sure that it is unique for each operation.
```Python hl_lines="6"
-{!./tutorial/src/path-operation-advanced-configuration/tutorial001.py!}
+{!./tutorial/src/path_operation_advanced_configuration/tutorial001.py!}
```
## Exclude from OpenAPI
To exclude a path operation from the generated OpenAPI schema (and thus, from the automatic documentation systems), use the parameter `include_in_schema` and set it to `False`;
```Python hl_lines="6"
-{!./tutorial/src/path-operation-advanced-configuration/tutorial002.py!}
+{!./tutorial/src/path_operation_advanced_configuration/tutorial002.py!}
```
But if you don't remember what each number code is for, you can use the shortcut constants from `starlette`:
-```Python hl_lines="5 18"
-{!./tutorial/src/path-operation-configuration/tutorial001.py!}
+```Python hl_lines="4 19"
+{!./tutorial/src/path_operation_configuration/tutorial001.py!}
```
That status code will be used in the response and will be added to the OpenAPI schema.
You can add tags to your path operation, pass the parameter `tags` with a `list` of `str` (commonly just one `str`):
-```Python hl_lines="17 22 27"
-{!./tutorial/src/path-operation-configuration/tutorial002.py!}
+```Python hl_lines="18 23 28"
+{!./tutorial/src/path_operation_configuration/tutorial002.py!}
```
They will be added to the OpenAPI schema and used by the automatic documentation interfaces:
You can add a `summary` and `description`:
-```Python hl_lines="20 21"
-{!./tutorial/src/path-operation-configuration/tutorial003.py!}
+```Python hl_lines="21 22"
+{!./tutorial/src/path_operation_configuration/tutorial003.py!}
```
## Description from docstring
As descriptions tend to be long and cover multiple lines, you can declare the path operation description in the function <abbr title="a multi-line string as the first expression inside a function (not assigned to any variable) used for documentation">docstring</abbr> and **FastAPI** will read it from there.
-```Python hl_lines="19 20 21 22 23 24 25 26 27"
-{!./tutorial/src/path-operation-configuration/tutorial004.py!}
+```Python hl_lines="20 21 22 23 24 25 26 27 28"
+{!./tutorial/src/path_operation_configuration/tutorial004.py!}
```
It will be used in the interactive docs:
You can specify the response description with the parameter `response_description`:
-```Python hl_lines="21"
-{!./tutorial/src/path-operation-configuration/tutorial005.py!}
+```Python hl_lines="22"
+{!./tutorial/src/path_operation_configuration/tutorial005.py!}
```
!!! info
```Python hl_lines="16"
-{!./tutorial/src/path-operation-configuration/tutorial006.py!}
+{!./tutorial/src/path_operation_configuration/tutorial006.py!}
```
It will be clearly marked as deprecated in the interactive docs:
First, import `Path` from `fastapi`:
```Python hl_lines="1"
-{!./tutorial/src/path-params-numeric-validations/tutorial001.py!}
+{!./tutorial/src/path_params_numeric_validations/tutorial001.py!}
```
## Declare metadata
For example, to declare a `title` metadata value for the path parameter `item_id` you can type:
```Python hl_lines="8"
-{!./tutorial/src/path-params-numeric-validations/tutorial001.py!}
+{!./tutorial/src/path_params_numeric_validations/tutorial001.py!}
```
!!! note
So, you can declare your function as:
```Python hl_lines="8"
-{!./tutorial/src/path-params-numeric-validations/tutorial002.py!}
+{!./tutorial/src/path_params_numeric_validations/tutorial002.py!}
```
## Order the parameters as you need, tricks
Python won't do anything with that `*`, but it will know that all the following parameters should be called as keyword arguments (key-value pairs), also known as <abbr title="From: K-ey W-ord Arg-uments"><code>kwargs</code></abbr>. Even if they don't have a default value.
```Python hl_lines="8"
-{!./tutorial/src/path-params-numeric-validations/tutorial003.py!}
+{!./tutorial/src/path_params_numeric_validations/tutorial003.py!}
```
## Number validations: greater than or equal
Here, with `ge=1`, `item_id` will need to be an integer number "`g`reater than or `e`qual" to `1`.
```Python hl_lines="8"
-{!./tutorial/src/path-params-numeric-validations/tutorial004.py!}
+{!./tutorial/src/path_params_numeric_validations/tutorial004.py!}
```
## Number validations: greater than and less than or equal
* `le`: `l`ess than or `e`qual
```Python hl_lines="9"
-{!./tutorial/src/path-params-numeric-validations/tutorial005.py!}
+{!./tutorial/src/path_params_numeric_validations/tutorial005.py!}
```
## Number validations: floats, greater than and less than
And the same for <abbr title="less than"><code>lt</code></abbr>.
```Python hl_lines="11"
-{!./tutorial/src/path-params-numeric-validations/tutorial006.py!}
+{!./tutorial/src/path_params_numeric_validations/tutorial006.py!}
```
## Recap
You can declare path "parameters" or "variables" with the same syntax used by Python format strings:
```Python hl_lines="6 7"
-{!./tutorial/src/path-params/tutorial001.py!}
+{!./tutorial/src/path_params/tutorial001.py!}
```
The value of the path parameter `item_id` will be passed to your function as the argument `item_id`.
You can declare the type of a path parameter in the function, using standard Python type annotations:
```Python hl_lines="7"
-{!./tutorial/src/path-params/tutorial002.py!}
+{!./tutorial/src/path_params/tutorial002.py!}
```
In this case, `item_id` is declared to be an `int`.
Let's start with a simple example:
```Python
-{!./tutorial/src/python-types/tutorial001.py!}
+{!./tutorial/src/python_types/tutorial001.py!}
```
Calling this program outputs:
* <abbr title="Puts them together, as one. With the contents of one after the other.">Concatenates</abbr> them with a space in the middle.
```Python hl_lines="2"
-{!./tutorial/src/python-types/tutorial001.py!}
+{!./tutorial/src/python_types/tutorial001.py!}
```
### Edit it
Those are the "type hints":
```Python hl_lines="1"
-{!./tutorial/src/python-types/tutorial002.py!}
+{!./tutorial/src/python_types/tutorial002.py!}
```
That is not the same as declaring default values like would be with:
Check this function, it already has type hints:
```Python hl_lines="1"
-{!./tutorial/src/python-types/tutorial003.py!}
+{!./tutorial/src/python_types/tutorial003.py!}
```
Because the editor knows the types of the variables, you don't only get completion, you also get error checks:
Now you know that you have to fix it, convert `age` to a string with `str(age)`:
```Python hl_lines="2"
-{!./tutorial/src/python-types/tutorial004.py!}
+{!./tutorial/src/python_types/tutorial004.py!}
```
* `bytes`
```Python hl_lines="1"
-{!./tutorial/src/python-types/tutorial005.py!}
+{!./tutorial/src/python_types/tutorial005.py!}
```
### Types with subtypes
From `typing`, import `List` (with a capital `L`):
```Python hl_lines="1"
-{!./tutorial/src/python-types/tutorial006.py!}
+{!./tutorial/src/python_types/tutorial006.py!}
```
Declare the variable, with the same colon (`:`) syntax.
As the list is a type that takes a "subtype", you put the subtype in square brackets:
```Python hl_lines="4"
-{!./tutorial/src/python-types/tutorial006.py!}
+{!./tutorial/src/python_types/tutorial006.py!}
```
That means: "the variable `items` is a `list`, and each of the items in this list is a `str`".
You would do the same to declare `tuple`s and `set`s:
```Python hl_lines="1 4"
-{!./tutorial/src/python-types/tutorial007.py!}
+{!./tutorial/src/python_types/tutorial007.py!}
```
This means:
The second subtype is for the values of the `dict`:
```Python hl_lines="1 4"
-{!./tutorial/src/python-types/tutorial008.py!}
+{!./tutorial/src/python_types/tutorial008.py!}
```
This means:
Let's say you have a class `Person`, with a name:
```Python hl_lines="1 2 3"
-{!./tutorial/src/python-types/tutorial009.py!}
+{!./tutorial/src/python_types/tutorial009.py!}
```
Then you can declare a variable to be of type `Person`:
```Python hl_lines="6"
-{!./tutorial/src/python-types/tutorial009.py!}
+{!./tutorial/src/python_types/tutorial009.py!}
```
And then, again, you get all the editor support:
Taken from the official Pydantic docs:
```Python
-{!./tutorial/src/python-types/tutorial010.py!}
+{!./tutorial/src/python_types/tutorial010.py!}
```
!!! info
Let's take this application as example:
```Python hl_lines="7"
-{!./tutorial/src/query-params-str-validations/tutorial001.py!}
+{!./tutorial/src/query_params_str_validations/tutorial001.py!}
```
The query parameter `q` is of type `str`, and by default is `None`, so it is optional.
To achieve that, first import `Query` from `fastapi`:
```Python hl_lines="1"
-{!./tutorial/src/query-params-str-validations/tutorial002.py!}
+{!./tutorial/src/query_params_str_validations/tutorial002.py!}
```
## Use `Query` as the default value
And now use it as the default value of your parameter, setting the parameter `max_length` to 50:
```Python hl_lines="7"
-{!./tutorial/src/query-params-str-validations/tutorial002.py!}
+{!./tutorial/src/query_params_str_validations/tutorial002.py!}
```
As we have to replace the default value `None` with `Query(None)`, the first parameter to `Query` serves the same purpose of defining that default value.
You can also add a parameter `min_length`:
```Python hl_lines="7"
-{!./tutorial/src/query-params-str-validations/tutorial003.py!}
+{!./tutorial/src/query_params_str_validations/tutorial003.py!}
```
## Add regular expressions
You can define a <abbr title="A regular expression, regex or regexp is a sequence of characters that define a search pattern for strings.">regular expression</abbr> that the parameter should match:
```Python hl_lines="8"
-{!./tutorial/src/query-params-str-validations/tutorial004.py!}
+{!./tutorial/src/query_params_str_validations/tutorial004.py!}
```
This specific regular expression checks that the received parameter value:
Let's say that you want to declare the `q` query parameter to have a `min_length` of `3`, and to have a default value of `"fixedquery"`:
```Python hl_lines="7"
-{!./tutorial/src/query-params-str-validations/tutorial005.py!}
+{!./tutorial/src/query_params_str_validations/tutorial005.py!}
```
!!! note
So, when you need to declare a value as required while using `Query`, you can use `...` as the first argument:
```Python hl_lines="7"
-{!./tutorial/src/query-params-str-validations/tutorial006.py!}
+{!./tutorial/src/query_params_str_validations/tutorial006.py!}
```
!!! info
You can add a `title`:
```Python hl_lines="7"
-{!./tutorial/src/query-params-str-validations/tutorial007.py!}
+{!./tutorial/src/query_params_str_validations/tutorial007.py!}
```
And a `description`:
```Python hl_lines="11"
-{!./tutorial/src/query-params-str-validations/tutorial008.py!}
+{!./tutorial/src/query_params_str_validations/tutorial008.py!}
```
## Alias parameters
Then you can declare an `alias`, and that alias is what will be used to find the parameter value:
```Python hl_lines="7"
-{!./tutorial/src/query-params-str-validations/tutorial009.py!}
+{!./tutorial/src/query_params_str_validations/tutorial009.py!}
```
## Deprecating parameters
Then pass the parameter `deprecated=True` to `Query`:
```Python hl_lines="16"
-{!./tutorial/src/query-params-str-validations/tutorial010.py!}
+{!./tutorial/src/query_params_str_validations/tutorial010.py!}
```
The docs will show it like this:
When you declare other function parameters that are not part of the path parameters, they are automatically interpreted as "query" parameters.
```Python hl_lines="9"
-{!./tutorial/src/query-params/tutorial001.py!}
+{!./tutorial/src/query_params/tutorial001.py!}
```
The query is the set of key-value pairs that go after the `?` in a URL, separated by `&` characters.
The same way, you can declare optional query parameters, by setting their default to `None`:
```Python hl_lines="7"
-{!./tutorial/src/query-params/tutorial002.py!}
+{!./tutorial/src/query_params/tutorial002.py!}
```
In this case, the function parameter `q` will be optional, and will be `None` by default.
You can also declare `bool` types, and they will be converted:
```Python hl_lines="7"
-{!./tutorial/src/query-params/tutorial003.py!}
+{!./tutorial/src/query_params/tutorial003.py!}
```
In this case, if you go to:
They will be detected by name:
```Python hl_lines="6 8"
-{!./tutorial/src/query-params/tutorial004.py!}
+{!./tutorial/src/query_params/tutorial004.py!}
```
## Required query parameters
But when you want to make a query parameter required, you can just do not declare any default value:
```Python hl_lines="6 7"
-{!./tutorial/src/query-params/tutorial005.py!}
+{!./tutorial/src/query_params/tutorial005.py!}
```
Here the query parameter `needy` is a required query parameter of type `str`.
Import `File` from `fastapi`:
```Python hl_lines="1"
-{!./tutorial/src/request-files/tutorial001.py!}
+{!./tutorial/src/request_files/tutorial001.py!}
```
## Define `File` parameters
Create file parameters the same way you would for `Body` or `Form`:
```Python hl_lines="7"
-{!./tutorial/src/request-files/tutorial001.py!}
+{!./tutorial/src/request_files/tutorial001.py!}
```
The files will be uploaded as form data and you will receive the contents as `bytes`.
## Import `File` and `Form`
```Python hl_lines="1"
-{!./tutorial/src/request-forms-and-files/tutorial001.py!}
+{!./tutorial/src/request_forms_and_files/tutorial001.py!}
```
## Define `File` and `Form` parameters
Create file and form parameters the same way you would for `Body` or `Query`:
```Python hl_lines="7"
-{!./tutorial/src/request-forms-and-files/tutorial001.py!}
+{!./tutorial/src/request_forms_and_files/tutorial001.py!}
```
The files and form fields will be uploaded as form data and you will receive the files and form fields.
Import `Form` from `fastapi`:
```Python hl_lines="1"
-{!./tutorial/src/request-forms/tutorial001.py!}
+{!./tutorial/src/request_forms/tutorial001.py!}
```
## Define `Form` parameters
Create form parameters the same way you would for `Body` or `Query`:
```Python hl_lines="7"
-{!./tutorial/src/request-forms/tutorial001.py!}
+{!./tutorial/src/request_forms/tutorial001.py!}
```
For example, in one of the ways the OAuth2 specification can be used (called "password flow") it is required to send a `username` and `password` as form fields.
* `@app.delete()`
* etc.
-```Python hl_lines="17"
-{!./tutorial/src/response-model/tutorial001.py!}
+```Python hl_lines="18"
+{!./tutorial/src/response_model/tutorial001.py!}
```
!!! note
Here we are declaring a `UserIn` model, it will contain a plaintext password:
-```Python hl_lines="8 10"
-{!./tutorial/src/response-model/tutorial002.py!}
+```Python hl_lines="9 11"
+{!./tutorial/src/response_model/tutorial002.py!}
```
And we are using this model to declare our input and the same model to declare our output:
-```Python hl_lines="16 17"
-{!./tutorial/src/response-model/tutorial002.py!}
+```Python hl_lines="17 18"
+{!./tutorial/src/response_model/tutorial002.py!}
```
Now, whenever a browser is creating a user with a password, the API will return the same password in the response.
We can instead create an input model with the plaintext password and an output model without it:
-```Python hl_lines="8 10 15"
-{!./tutorial/src/response-model/tutorial003.py!}
+```Python hl_lines="9 11 16"
+{!./tutorial/src/response_model/tutorial003.py!}
```
Here, even though our path operation function is returning the same input user that contains the password:
-```Python hl_lines="23"
-{!./tutorial/src/response-model/tutorial003.py!}
+```Python hl_lines="24"
+{!./tutorial/src/response_model/tutorial003.py!}
```
...we declared the `response_model` to be our model `UserOut`, that doesn't include the password:
-```Python hl_lines="21"
-{!./tutorial/src/response-model/tutorial003.py!}
+```Python hl_lines="22"
+{!./tutorial/src/response_model/tutorial003.py!}
```
So, **FastAPI** will take care of filtering out all the data that is not declared in the output model (using Pydantic).
For now, don't pay attention to the rest, only the imports:
-```Python hl_lines="3 4 5"
-{!./tutorial/src/sql-databases/tutorial001.py!}
+```Python hl_lines="2 3 4"
+{!./tutorial/src/sql_databases/tutorial001.py!}
```
## Define the database
Define the database that SQLAlchemy should connect to:
-```Python hl_lines="8"
-{!./tutorial/src/sql-databases/tutorial001.py!}
+```Python hl_lines="7"
+{!./tutorial/src/sql_databases/tutorial001.py!}
```
!!! tip
## Create the SQLAlchemy `engine`
-```Python hl_lines="10"
-{!./tutorial/src/sql-databases/tutorial001.py!}
+```Python hl_lines="9"
+{!./tutorial/src/sql_databases/tutorial001.py!}
```
## Create a `scoped_session`
-```Python hl_lines="11 12 13"
-{!./tutorial/src/sql-databases/tutorial001.py!}
+```Python hl_lines="10 11 12"
+{!./tutorial/src/sql_databases/tutorial001.py!}
```
!!! note "Very Technical Details"
So, your models will behave very similarly to, for example, Flask-SQLAlchemy.
```Python hl_lines="15 16 17 18 19"
-{!./tutorial/src/sql-databases/tutorial001.py!}
+{!./tutorial/src/sql_databases/tutorial001.py!}
```
## Create the SQLAlchemy `Base` model
```Python hl_lines="22"
-{!./tutorial/src/sql-databases/tutorial001.py!}
+{!./tutorial/src/sql_databases/tutorial001.py!}
```
## Create your application data model
Here's a user model that will be a table in the database:
```Python hl_lines="25 26 27 28 29"
-{!./tutorial/src/sql-databases/tutorial001.py!}
+{!./tutorial/src/sql_databases/tutorial001.py!}
```
## Get a user
By creating a function that is only dedicated to getting your user from a `username` (or any other parameter) independent of your path operation function, you can more easily re-use it in multiple parts and also add <abbr title="Automated test, written in code, that checks if another piece of code is working correctly.">unit tests</abbr> for it:
```Python hl_lines="32 33"
-{!./tutorial/src/sql-databases/tutorial001.py!}
+{!./tutorial/src/sql_databases/tutorial001.py!}
```
## Create your **FastAPI** code
Create your app and path operation function:
```Python hl_lines="37 40 41 42 43"
-{!./tutorial/src/sql-databases/tutorial001.py!}
+{!./tutorial/src/sql_databases/tutorial001.py!}
```
As we are using SQLAlchemy's `scoped_session`, we don't even have to create a dependency with `Depends`.
Then we should declare the path operation without `async def`, just with a normal `def`:
```Python hl_lines="41"
-{!./tutorial/src/sql-databases/tutorial001.py!}
+{!./tutorial/src/sql_databases/tutorial001.py!}
```
## Migrations
return [{"username": "Foo"}, {"username": "Bar"}]
-@router.get("/users/{username}")
-async def read_user(username: str):
- return {"username": username}
-
-
@router.get("/users/me")
async def read_user_me():
return {"username": "fakecurrentuser"}
+
+
+@router.get("/users/{username}")
+async def read_user(username: str):
+ return {"username": username}
from fastapi import FastAPI
-from .tutorial01 import router as users_router
-from .tutorial02 import router as items_router
+from .routers.tutorial001 import router as users_router
+from .routers.tutorial002 import router as items_router
app = FastAPI()
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
class Item(BaseModel):
name: str
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
class Item(BaseModel):
name: str
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
class Item(BaseModel):
name: str
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
class Item(BaseModel):
name: str
-from fastapi import FastAPI, Path
from pydantic import BaseModel
+from fastapi import FastAPI, Path
+
app = FastAPI()
async def update_item(
*,
item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000),
- q: str,
+ q: str = None,
item: Item = None,
):
results = {"item_id": item_id}
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
app = FastAPI()
-from fastapi import Body, FastAPI
from pydantic import BaseModel
+from fastapi import Body, FastAPI
+
app = FastAPI()
-from fastapi import Body, FastAPI
from pydantic import BaseModel
+from fastapi import Body, FastAPI
+
app = FastAPI()
-from fastapi import Body, FastAPI
from pydantic import BaseModel
+from fastapi import Body, FastAPI
+
app = FastAPI()
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
app = FastAPI()
from typing import List
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
app = FastAPI()
from typing import Set
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
app = FastAPI()
from typing import Set
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
app = FastAPI()
from typing import Set
-from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import UrlStr
+from fastapi import FastAPI
+
app = FastAPI()
from typing import List, Set
-from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import UrlStr
+from fastapi import FastAPI
+
app = FastAPI()
from typing import List, Set
-from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import UrlStr
+from fastapi import FastAPI
+
app = FastAPI()
from typing import List
-from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import UrlStr
+from fastapi import FastAPI
+
app = FastAPI()
-from fastapi import Body, FastAPI
from pydantic import BaseModel, Schema
+from fastapi import Body, FastAPI
+
app = FastAPI()
-from fastapi import Body, FastAPI
from pydantic import BaseModel
+from fastapi import Body, FastAPI
+
app = FastAPI()
-from fastapi import FastAPI
from starlette.responses import UJSONResponse
+from fastapi import FastAPI
+
app = FastAPI()
-from fastapi import FastAPI
from starlette.responses import HTMLResponse
+from fastapi import FastAPI
+
app = FastAPI()
-from fastapi import FastAPI
from starlette.responses import HTMLResponse
+from fastapi import FastAPI
+
app = FastAPI()
-from fastapi import FastAPI
from starlette.responses import HTMLResponse
+from fastapi import FastAPI
+
app = FastAPI()
-from fastapi import Depends, FastAPI
from pydantic import BaseModel
+from fastapi import Depends, FastAPI
+
app = FastAPI()
from typing import List
-from fastapi import Cookie, Depends, FastAPI
from pydantic import BaseModel
+from fastapi import Cookie, Depends, FastAPI
+
app = FastAPI()
from random import choice
from typing import List
-from fastapi import Cookie, Depends, FastAPI
from pydantic import BaseModel
+from fastapi import Cookie, Depends, FastAPI
+
app = FastAPI()
-from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import EmailStr
+from fastapi import FastAPI
+
app = FastAPI()
-from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import EmailStr
+from fastapi import FastAPI
+
app = FastAPI()
from typing import Optional
-from fastapi import FastAPI
from pydantic import BaseModel
from couchbase import LOCKMODE_WAIT
from couchbase.bucket import Bucket
from couchbase.cluster import Cluster, PasswordAuthenticator
+from fastapi import FastAPI
USERPROFILE_DOC_TYPE = "userprofile"
def get_bucket():
- cluster = Cluster("couchbase://couchbasehost:8091?fetch_mutation_tokens=1&operation_timeout=30&n1ql_timeout=300")
+ cluster = Cluster(
+ "couchbase://couchbasehost:8091?fetch_mutation_tokens=1&operation_timeout=30&n1ql_timeout=300"
+ )
authenticator = PasswordAuthenticator("username", "password")
cluster.authenticate(authenticator)
bucket: Bucket = cluster.open_bucket("bucket_name", lockmode=LOCKMODE_WAIT)
from typing import Set
-from fastapi import FastAPI
from pydantic import BaseModel
from starlette.status import HTTP_201_CREATED
+from fastapi import FastAPI
+
app = FastAPI()
from typing import Set
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
app = FastAPI()
from typing import Set
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
app = FastAPI()
from typing import Set
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
app = FastAPI()
from typing import Set
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
app = FastAPI()
from typing import Set
-from fastapi import FastAPI
from pydantic import BaseModel
+from fastapi import FastAPI
+
app = FastAPI()
-from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import EmailStr
+from fastapi import FastAPI
+
app = FastAPI()
-from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import EmailStr
+from fastapi import FastAPI
+
app = FastAPI()
from typing import Optional
+from pydantic import BaseModel
+
from fastapi import Depends, FastAPI, Security
from fastapi.security import OAuth2PasswordBearer
-from pydantic import BaseModel
app = FastAPI()
from typing import Optional
-from fastapi import Depends, FastAPI, Security
-from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from starlette.exceptions import HTTPException
+from fastapi import Depends, FastAPI, Security
+from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+
fake_users_db = {
"johndoe": {
"username": "johndoe",
from typing import Optional
import jwt
-from fastapi import Depends, FastAPI, Security
-from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jwt import PyJWTError
from passlib.context import CryptContext
from pydantic import BaseModel
from starlette.exceptions import HTTPException
from starlette.status import HTTP_403_FORBIDDEN
+from fastapi import Depends, FastAPI, Security
+from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
+
# to get a string like this run:
# openssl rand -hex 32
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
from typing import Any, Callable, Dict, List, Optional, Type
-from fastapi import routing
-from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
-from fastapi.openapi.utils import get_openapi
from pydantic import BaseModel
from starlette.applications import Starlette
from starlette.exceptions import ExceptionMiddleware, HTTPException
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
+from fastapi import routing
+from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
+from fastapi.openapi.utils import get_openapi
+
async def http_exception(request: Request, exc: HTTPException) -> JSONResponse:
return JSONResponse({"detail": exc.detail}, status_code=exc.status_code)
from typing import Callable, List, Sequence
-from fastapi.security.base import SecurityBase
from pydantic.fields import Field
+from fastapi.security.base import SecurityBase
+
param_supported_types = (str, int, float, bool)
try:
from pydantic.types import EmailStr # type: ignore
-except ImportError:
+except ImportError: # pragma: no cover
logging.warning(
"email-validator not installed, email fields will be treated as str.\n"
+ "To install, run: pip install email-validator"
from typing import Any, Dict, List, Optional, Sequence, Tuple, Type
+from pydantic.fields import Field
+from pydantic.schema import Schema, field_schema, get_model_name_map
+from pydantic.utils import lenient_issubclass
+from starlette.responses import JSONResponse
+from starlette.routing import BaseRoute
+from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
+
from fastapi import routing
from fastapi.dependencies.models import Dependant
from fastapi.dependencies.utils import get_flat_dependant
from fastapi.openapi.models import OpenAPI
from fastapi.params import Body, Param
from fastapi.utils import get_flat_models_from_routes, get_model_definitions
-from pydantic.fields import Field
-from pydantic.schema import Schema, field_schema, get_model_name_map
-from pydantic.utils import lenient_issubclass
-from starlette.responses import JSONResponse
-from starlette.routing import BaseRoute
-from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
validation_error_definition = {
"title": "ValidationError",
+from starlette.requests import Request
+
from fastapi.openapi.models import APIKey, APIKeyIn
from fastapi.security.base import SecurityBase
-from starlette.requests import Request
class APIKeyBase(SecurityBase):
+from starlette.requests import Request
+
from fastapi.openapi.models import (
HTTPBase as HTTPBaseModel,
HTTPBearer as HTTPBearerModel,
)
from fastapi.security.base import SecurityBase
-from starlette.requests import Request
class HTTPBase(SecurityBase):
from typing import List, Optional
-from fastapi.openapi.models import OAuth2 as OAuth2Model, OAuthFlows as OAuthFlowsModel
-from fastapi.security.base import SecurityBase
from pydantic import BaseModel, Schema
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.status import HTTP_403_FORBIDDEN
+from fastapi.openapi.models import OAuth2 as OAuth2Model, OAuthFlows as OAuthFlowsModel
+from fastapi.security.base import SecurityBase
+
class OAuth2PasswordRequestData(BaseModel):
grant_type: str = "password"
+from starlette.requests import Request
+
from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel
from fastapi.security.base import SecurityBase
-from starlette.requests import Request
class OpenIdConnect(SecurityBase):
import re
from typing import Any, Dict, List, Sequence, Set, Type
-from fastapi import routing
-from fastapi.openapi.constants import REF_PREFIX
from pydantic import BaseModel
from pydantic.fields import Field
from pydantic.schema import get_flat_models_from_fields, model_process_schema
from starlette.routing import BaseRoute
+from fastapi import routing
+from fastapi.openapi.constants import REF_PREFIX
+
def get_flat_models_from_routes(
routes: Sequence[Type[BaseRoute]]
# PYTHONPATH=. pytest --cov=fastapi --cov=tests --cov-fail-under=100 --cov-report=term-missing ${@} --cov-report=html
-PYTHONPATH=. pytest --cov=fastapi --cov=tests --cov-report=term-missing ${@} --cov-report=html
+PYTHONPATH=.:./docs/tutorial/src pytest --cov=fastapi --cov=tests --cov=docs/tutorial/src --cov-report=term-missing ${@} --cov-report=html
mypy fastapi --disallow-untyped-defs
if [ "${PYTHON_VERSION}" = '3.7' ]; then
echo "Skipping 'black' on 3.7. See issue https://github.com/ambv/black/issues/494"
-from fastapi import (
- Body,
- Cookie,
- Depends,
- FastAPI,
- File,
- Form,
- Header,
- Path,
- Query,
- Security,
-)
-from fastapi.security import (
- HTTPBasic,
- OAuth2,
- OAuth2PasswordBearer,
- OAuth2PasswordRequestForm,
-)
-from pydantic import BaseModel
-from starlette.responses import HTMLResponse, JSONResponse, PlainTextResponse
-from starlette.status import HTTP_202_ACCEPTED
-
-from .endpoints.a import router as router_a
-from .endpoints.b import router as router_b
+from fastapi import FastAPI
app = FastAPI()
-app.include_router(router_a)
-app.include_router(router_b, prefix="/b")
-
-
-@app.get("/text")
-def get_text():
- return "Hello World"
-
-
-@app.get("/path/{item_id}")
-def get_id(item_id):
- return item_id
-
-
-@app.get("/path/str/{item_id}")
-def get_str_id(item_id: str):
- return item_id
-
-
-@app.get("/path/int/{item_id}")
-def get_int_id(item_id: int):
- return item_id
-
-
-@app.get("/path/float/{item_id}")
-def get_float_id(item_id: float):
- return item_id
-
-
-@app.get("/path/bool/{item_id}")
-def get_bool_id(item_id: bool):
- return item_id
-
-
-@app.get("/path/param/{item_id}")
-def get_path_param_id(item_id: str = Path(None)):
- return item_id
-
-
-@app.get("/path/param-required/{item_id}")
-def get_path_param_required_id(item_id: str = Path(...)):
- return item_id
-
-
-@app.get("/path/param-minlength/{item_id}")
-def get_path_param_min_length(item_id: str = Path(..., min_length=3)):
- return item_id
-
-
-@app.get("/path/param-maxlength/{item_id}")
-def get_path_param_max_length(item_id: str = Path(..., max_length=3)):
- return item_id
-
-
-@app.get("/path/param-min_maxlength/{item_id}")
-def get_path_param_min_max_length(item_id: str = Path(..., max_length=3, min_length=2)):
- return item_id
-
-
-@app.get("/path/param-gt/{item_id}")
-def get_path_param_gt(item_id: float = Path(..., gt=3)):
- return item_id
-
-
-@app.get("/path/param-gt0/{item_id}")
-def get_path_param_gt0(item_id: float = Path(..., gt=0)):
- return item_id
-
-
-@app.get("/path/param-ge/{item_id}")
-def get_path_param_ge(item_id: float = Path(..., ge=3)):
- return item_id
-
-
-@app.get("/path/param-lt/{item_id}")
-def get_path_param_lt(item_id: float = Path(..., lt=3)):
- return item_id
-
-
-@app.get("/path/param-lt0/{item_id}")
-def get_path_param_lt0(item_id: float = Path(..., lt=0)):
- return item_id
-
-
-@app.get("/path/param-le/{item_id}")
-def get_path_param_le(item_id: float = Path(..., le=3)):
- return item_id
-
-
-@app.get("/path/param-lt-gt/{item_id}")
-def get_path_param_lt_gt(item_id: float = Path(..., lt=3, gt=1)):
- return item_id
-
-
-@app.get("/path/param-le-ge/{item_id}")
-def get_path_param_le_ge(item_id: float = Path(..., le=3, ge=1)):
- return item_id
-
-
-@app.get("/path/param-lt-int/{item_id}")
-def get_path_param_lt_int(item_id: int = Path(..., lt=3)):
- return item_id
-
-
-@app.get("/path/param-gt-int/{item_id}")
-def get_path_param_gt_int(item_id: int = Path(..., gt=3)):
- return item_id
-
-
-@app.get("/path/param-le-int/{item_id}")
-def get_path_param_le_int(item_id: int = Path(..., le=3)):
- return item_id
-
-
-@app.get("/path/param-ge-int/{item_id}")
-def get_path_param_ge_int(item_id: int = Path(..., ge=3)):
- return item_id
-
-
-@app.get("/path/param-lt-gt-int/{item_id}")
-def get_path_param_lt_gt_int(item_id: int = Path(..., lt=3, gt=1)):
- return item_id
-
-
-@app.get("/path/param-le-ge-int/{item_id}")
-def get_path_param_le_ge_int(item_id: int = Path(..., le=3, ge=1)):
- return item_id
-
-
-@app.get("/query")
-def get_query(query):
- if query is None:
- return "foo bar"
- return f"foo bar {query}"
-
-
-@app.get("/query/optional")
-def get_query_optional(query=None):
- if query is None:
- return "foo bar"
- return f"foo bar {query}"
-
-
-@app.get("/query/int")
-def get_query_type(query: int):
- if query is None:
- return "foo bar"
- return f"foo bar {query}"
-
-
-@app.get("/query/int/optional")
-def get_query_type_optional(query: int = None):
- if query is None:
- return "foo bar"
- return f"foo bar {query}"
-
-
-@app.get("/query/int/default")
-def get_query_type_optional(query: int = 10):
- return f"foo bar {query}"
-
-
-@app.get("/query/param")
-def get_query_param(query=Query(None)):
- if query is None:
- return "foo bar"
- return f"foo bar {query}"
-
-
-@app.get("/query/param-required")
-def get_query_param_required(query=Query(...)):
- if query is None:
- return "foo bar"
- return f"foo bar {query}"
-
-
-@app.get("/query/param-required/int")
-def get_query_param_required_type(query: int = Query(...)):
- if query is None:
- return "foo bar"
- return f"foo bar {query}"
-
-
-@app.get("/cookie")
-def get_cookie(coo=Cookie(None)):
- return coo
-
-
-@app.get("/header")
-def get_header(head_name=Header(None)):
- return head_name
-
-
-@app.get("/header_under")
-def get_header(head_name=Header(None, convert_underscores=False)):
- return head_name
-
-
-@app.get("/security")
-def get_security(sec=Security(HTTPBasic())):
- return sec
-
-
-reusable_oauth2 = OAuth2(
- flows={
- "password": {
- "tokenUrl": "/token",
- "scopes": {"read:user": "Read a User", "write:user": "Create a user"},
- }
- }
-)
-
-
-@app.get("/security/oauth2")
-def get_security_oauth2(sec=Security(reusable_oauth2, scopes=["read:user"])):
- return sec
-
-
-reusable_oauth2b = OAuth2PasswordBearer(tokenUrl="/token")
-
-
-class User(BaseModel):
- username: str
-
-
-def get_current_user(oauth_header: str = Security(reusable_oauth2b)):
- user = User(username=oauth_header)
- return user
-
-
-@app.get("/security/oauth2b")
-def read_current_user(current_user: User = Depends(get_current_user)):
- return current_user
-
-
-@app.post("/token")
-def post_token(request_data: OAuth2PasswordRequestForm = Form(...)):
- data = request_data.parse()
- access_token = data.username + ":" + data.password
- return {"access_token": access_token}
-
-
-class Item(BaseModel):
- name: str
- price: float
- is_offer: bool
-
-
-@app.put("/items/{item_id}")
-def put_item(item_id: str, item: Item):
- return item
-
-
-@app.post("/items/")
-def post_item(item: Item):
- return item
-
-
-@app.post("/items-all-params/{item_id}")
-def post_items_all_params(
- item_id: str = Path(...),
- body: Item = Body(...),
- query_a: int = Query(None),
- query_b=Query(None),
- coo: str = Cookie(None),
- x_head: int = Header(None),
- x_under: str = Header(None, convert_underscores=False),
-):
- return {
- "item_id": item_id,
- "body": body,
- "query_a": query_a,
- "query_b": query_b,
- "coo": coo,
- "x_head": x_head,
- "x_under": x_under,
- }
-
-
-@app.post("/items-all-params-defaults/{item_id}")
-def post_items_all_params_default(
- item_id: str,
- body_item_a: Item,
- body_item_b: Item,
- query_a: int,
- query_b: int,
- coo: str = Cookie(None),
- x_head: int = Header(None),
- x_under: str = Header(None, convert_underscores=False),
-):
- return {
- "item_id": item_id,
- "body_item_a": body_item_a,
- "body_item_b": body_item_b,
- "query_a": query_a,
- "query_b": query_b,
- "coo": coo,
- "x_head": x_head,
- "x_under": x_under,
- }
-
-
-@app.delete("/items/{item_id}")
-def delete_item(item_id: str):
- return item_id
-
-
-@app.options("/options/")
-def options():
- return JSONResponse(headers={"x-fastapi": "fast"})
-
-
-@app.head("/head/")
-def head():
- return {"not sent": "nope"}
-
-
-@app.patch("/patch/{user_id}")
-def patch(user_id: str, increment: float):
- return {"user_id": user_id, "total": 5 + increment}
-
-
-@app.trace("/trace/")
-def trace():
- return PlainTextResponse(media_type="message/http")
-
-
-@app.get("/model", response_model=Item, status_code=HTTP_202_ACCEPTED)
-def model():
- return {"name": "Foo", "price": "5.0", "password": "not sent"}
-
-
-@app.get(
- "/metadata",
- tags=["tag1", "tag2"],
- summary="The summary",
- description="The description",
- response_description="Response description",
- deprecated=True,
- operation_id="a_very_long_and_strange_operation_id",
-)
-def get_meta():
- return "Foo"
-
-
-@app.get("/html", content_type=HTMLResponse)
-def get_html():
- return """
- <html>
- <body>
- <h1>
- Some text inside
- </h1>
- </body>
- </html>
- """
-
-
-class FakeDB:
- def __init__(self):
- self.data = {
- "johndoe": {
- "username": "johndoe",
- "password": "shouldbehashed",
- "fist_name": "John",
- "last_name": "Doe",
- }
- }
-
-
-class DBConnectionManager:
- def __init__(self):
- self.db = FakeDB()
-
- def __call__(self):
- return self.db
-
-
-connection_manager = DBConnectionManager()
-
-
-class TokenUserData(BaseModel):
- username: str
- password: str
-
-
-class UserInDB(BaseModel):
- username: str
- password: str
- fist_name: str
- last_name: str
-
-
-def require_token(
- token: str = Security(reusable_oauth2, scopes=["read:user", "write:user"])
-):
- raw_token = token.replace("Bearer ", "")
- # Never do this plaintext password usage in production
- username, password = raw_token.split(":")
- return TokenUserData(username=username, password=password)
-
-
-def require_user(
- db: FakeDB = Depends(connection_manager),
- user_data: TokenUserData = Depends(require_token),
-):
- return db.data[user_data.username]
+@app.api_route("/api_route")
+def non_operation():
+ return {"message": "Hello World"}
-class UserOut(BaseModel):
- username: str
- fist_name: str
- last_name: str
+def non_decorated_route():
+ return {"message": "Hello World"}
-@app.get("/dependency", response_model=UserOut)
-def get_dependency(user: UserInDB = Depends(require_user)):
- return user
+app.add_api_route("/non_decorated_route", non_decorated_route)
--- /dev/null
+from pydantic import BaseModel
+from starlette.responses import HTMLResponse, JSONResponse, PlainTextResponse
+from starlette.status import HTTP_202_ACCEPTED
+
+from fastapi import (
+ Body,
+ Cookie,
+ Depends,
+ FastAPI,
+ File,
+ Form,
+ Header,
+ Path,
+ Query,
+ Security,
+)
+from fastapi.security import (
+ HTTPBasic,
+ OAuth2,
+ OAuth2PasswordBearer,
+ OAuth2PasswordRequestForm,
+)
+
+from .endpoints.a import router as router_a
+from .endpoints.b import router as router_b
+
+app = FastAPI()
+
+
+app.include_router(router_a)
+app.include_router(router_b, prefix="/b")
+
+
+@app.get("/text")
+def get_text():
+ return "Hello World"
+
+
+@app.get("/path/{item_id}")
+def get_id(item_id):
+ return item_id
+
+
+@app.get("/path/str/{item_id}")
+def get_str_id(item_id: str):
+ return item_id
+
+
+@app.get("/path/int/{item_id}")
+def get_int_id(item_id: int):
+ return item_id
+
+
+@app.get("/path/float/{item_id}")
+def get_float_id(item_id: float):
+ return item_id
+
+
+@app.get("/path/bool/{item_id}")
+def get_bool_id(item_id: bool):
+ return item_id
+
+
+@app.get("/path/param/{item_id}")
+def get_path_param_id(item_id: str = Path(None)):
+ return item_id
+
+
+@app.get("/path/param-required/{item_id}")
+def get_path_param_required_id(item_id: str = Path(...)):
+ return item_id
+
+
+@app.get("/path/param-minlength/{item_id}")
+def get_path_param_min_length(item_id: str = Path(..., min_length=3)):
+ return item_id
+
+
+@app.get("/path/param-maxlength/{item_id}")
+def get_path_param_max_length(item_id: str = Path(..., max_length=3)):
+ return item_id
+
+
+@app.get("/path/param-min_maxlength/{item_id}")
+def get_path_param_min_max_length(item_id: str = Path(..., max_length=3, min_length=2)):
+ return item_id
+
+
+@app.get("/path/param-gt/{item_id}")
+def get_path_param_gt(item_id: float = Path(..., gt=3)):
+ return item_id
+
+
+@app.get("/path/param-gt0/{item_id}")
+def get_path_param_gt0(item_id: float = Path(..., gt=0)):
+ return item_id
+
+
+@app.get("/path/param-ge/{item_id}")
+def get_path_param_ge(item_id: float = Path(..., ge=3)):
+ return item_id
+
+
+@app.get("/path/param-lt/{item_id}")
+def get_path_param_lt(item_id: float = Path(..., lt=3)):
+ return item_id
+
+
+@app.get("/path/param-lt0/{item_id}")
+def get_path_param_lt0(item_id: float = Path(..., lt=0)):
+ return item_id
+
+
+@app.get("/path/param-le/{item_id}")
+def get_path_param_le(item_id: float = Path(..., le=3)):
+ return item_id
+
+
+@app.get("/path/param-lt-gt/{item_id}")
+def get_path_param_lt_gt(item_id: float = Path(..., lt=3, gt=1)):
+ return item_id
+
+
+@app.get("/path/param-le-ge/{item_id}")
+def get_path_param_le_ge(item_id: float = Path(..., le=3, ge=1)):
+ return item_id
+
+
+@app.get("/path/param-lt-int/{item_id}")
+def get_path_param_lt_int(item_id: int = Path(..., lt=3)):
+ return item_id
+
+
+@app.get("/path/param-gt-int/{item_id}")
+def get_path_param_gt_int(item_id: int = Path(..., gt=3)):
+ return item_id
+
+
+@app.get("/path/param-le-int/{item_id}")
+def get_path_param_le_int(item_id: int = Path(..., le=3)):
+ return item_id
+
+
+@app.get("/path/param-ge-int/{item_id}")
+def get_path_param_ge_int(item_id: int = Path(..., ge=3)):
+ return item_id
+
+
+@app.get("/path/param-lt-gt-int/{item_id}")
+def get_path_param_lt_gt_int(item_id: int = Path(..., lt=3, gt=1)):
+ return item_id
+
+
+@app.get("/path/param-le-ge-int/{item_id}")
+def get_path_param_le_ge_int(item_id: int = Path(..., le=3, ge=1)):
+ return item_id
+
+
+@app.get("/query")
+def get_query(query):
+ if query is None:
+ return "foo bar"
+ return f"foo bar {query}"
+
+
+@app.get("/query/optional")
+def get_query_optional(query=None):
+ if query is None:
+ return "foo bar"
+ return f"foo bar {query}"
+
+
+@app.get("/query/int")
+def get_query_type(query: int):
+ if query is None:
+ return "foo bar"
+ return f"foo bar {query}"
+
+
+@app.get("/query/int/optional")
+def get_query_type_optional(query: int = None):
+ if query is None:
+ return "foo bar"
+ return f"foo bar {query}"
+
+
+@app.get("/query/int/default")
+def get_query_type_optional(query: int = 10):
+ return f"foo bar {query}"
+
+
+@app.get("/query/param")
+def get_query_param(query=Query(None)):
+ if query is None:
+ return "foo bar"
+ return f"foo bar {query}"
+
+
+@app.get("/query/param-required")
+def get_query_param_required(query=Query(...)):
+ if query is None:
+ return "foo bar"
+ return f"foo bar {query}"
+
+
+@app.get("/query/param-required/int")
+def get_query_param_required_type(query: int = Query(...)):
+ if query is None:
+ return "foo bar"
+ return f"foo bar {query}"
+
+
+@app.get("/cookie")
+def get_cookie(coo=Cookie(None)):
+ return coo
+
+
+@app.get("/header")
+def get_header(head_name=Header(None)):
+ return head_name
+
+
+@app.get("/header_under")
+def get_header(head_name=Header(None, convert_underscores=False)):
+ return head_name
+
+
+@app.get("/security")
+def get_security(sec=Security(HTTPBasic())):
+ return sec
+
+
+reusable_oauth2 = OAuth2(
+ flows={
+ "password": {
+ "tokenUrl": "/token",
+ "scopes": {"read:user": "Read a User", "write:user": "Create a user"},
+ }
+ }
+)
+
+
+@app.get("/security/oauth2")
+def get_security_oauth2(sec=Security(reusable_oauth2, scopes=["read:user"])):
+ return sec
+
+
+reusable_oauth2b = OAuth2PasswordBearer(tokenUrl="/token")
+
+
+class User(BaseModel):
+ username: str
+
+
+def get_current_user(oauth_header: str = Security(reusable_oauth2b)):
+ user = User(username=oauth_header)
+ return user
+
+
+@app.get("/security/oauth2b")
+def read_current_user(current_user: User = Depends(get_current_user)):
+ return current_user
+
+
+@app.post("/token")
+def post_token(request_data: OAuth2PasswordRequestForm = Form(...)):
+ data = request_data.parse()
+ access_token = data.username + ":" + data.password
+ return {"access_token": access_token}
+
+
+class Item(BaseModel):
+ name: str
+ price: float
+ is_offer: bool
+
+
+@app.put("/items/{item_id}")
+def put_item(item_id: str, item: Item):
+ return item
+
+
+@app.post("/items/")
+def post_item(item: Item):
+ return item
+
+
+@app.post("/items-all-params/{item_id}")
+def post_items_all_params(
+ item_id: str = Path(...),
+ body: Item = Body(...),
+ query_a: int = Query(None),
+ query_b=Query(None),
+ coo: str = Cookie(None),
+ x_head: int = Header(None),
+ x_under: str = Header(None, convert_underscores=False),
+):
+ return {
+ "item_id": item_id,
+ "body": body,
+ "query_a": query_a,
+ "query_b": query_b,
+ "coo": coo,
+ "x_head": x_head,
+ "x_under": x_under,
+ }
+
+
+@app.post("/items-all-params-defaults/{item_id}")
+def post_items_all_params_default(
+ item_id: str,
+ body_item_a: Item,
+ body_item_b: Item,
+ query_a: int,
+ query_b: int,
+ coo: str = Cookie(None),
+ x_head: int = Header(None),
+ x_under: str = Header(None, convert_underscores=False),
+):
+ return {
+ "item_id": item_id,
+ "body_item_a": body_item_a,
+ "body_item_b": body_item_b,
+ "query_a": query_a,
+ "query_b": query_b,
+ "coo": coo,
+ "x_head": x_head,
+ "x_under": x_under,
+ }
+
+
+@app.delete("/items/{item_id}")
+def delete_item(item_id: str):
+ return item_id
+
+
+@app.options("/options/")
+def options():
+ return JSONResponse(headers={"x-fastapi": "fast"})
+
+
+@app.head("/head/")
+def head():
+ return {"not sent": "nope"}
+
+
+@app.patch("/patch/{user_id}")
+def patch(user_id: str, increment: float):
+ return {"user_id": user_id, "total": 5 + increment}
+
+
+@app.trace("/trace/")
+def trace():
+ return PlainTextResponse(media_type="message/http")
+
+
+@app.get("/model", response_model=Item, status_code=HTTP_202_ACCEPTED)
+def model():
+ return {"name": "Foo", "price": "5.0", "password": "not sent"}
+
+
+@app.get(
+ "/metadata",
+ tags=["tag1", "tag2"],
+ summary="The summary",
+ description="The description",
+ response_description="Response description",
+ deprecated=True,
+ operation_id="a_very_long_and_strange_operation_id",
+)
+def get_meta():
+ return "Foo"
+
+
+@app.get("/html", content_type=HTMLResponse)
+def get_html():
+ return """
+ <html>
+ <body>
+ <h1>
+ Some text inside
+ </h1>
+ </body>
+ </html>
+ """
+
+
+class FakeDB:
+ def __init__(self):
+ self.data = {
+ "johndoe": {
+ "username": "johndoe",
+ "password": "shouldbehashed",
+ "fist_name": "John",
+ "last_name": "Doe",
+ }
+ }
+
+
+class DBConnectionManager:
+ def __init__(self):
+ self.db = FakeDB()
+
+ def __call__(self):
+ return self.db
+
+
+connection_manager = DBConnectionManager()
+
+
+class TokenUserData(BaseModel):
+ username: str
+ password: str
+
+
+class UserInDB(BaseModel):
+ username: str
+ password: str
+ fist_name: str
+ last_name: str
+
+
+def require_token(
+ token: str = Security(reusable_oauth2, scopes=["read:user", "write:user"])
+):
+ raw_token = token.replace("Bearer ", "")
+ # Never do this plaintext password usage in production
+ username, password = raw_token.split(":")
+ return TokenUserData(username=username, password=password)
+
+
+def require_user(
+ db: FakeDB = Depends(connection_manager),
+ user_data: TokenUserData = Depends(require_token),
+):
+ return db.data[user_data.username]
+
+
+class UserOut(BaseModel):
+ username: str
+ fist_name: str
+ last_name: str
+
+
+@app.get("/dependency", response_model=UserOut)
+def get_dependency(user: UserInDB = Depends(require_user)):
+ return user
--- /dev/null
+import pytest
+from starlette.testclient import TestClient
+
+from .main import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "Fast API", "version": "0.1.0"},
+ "paths": {
+ "/api_route": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Non Operation Get",
+ "operationId": "non_operation_api_route_get",
+ }
+ },
+ "/non_decorated_route": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Non Decorated Route Get",
+ "operationId": "non_decorated_route_non_decorated_route_get",
+ }
+ },
+ },
+}
+
+
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ ("/api_route", 200, {"message": "Hello World"}),
+ ("/nonexistent", 404, {"detail": "Not Found"}),
+ ("/openapi.json", 200, openapi_schema),
+ ],
+)
+def test_get_path(path, expected_status, expected_response):
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
+
+
+def test_swagger_ui():
+ response = client.get("/docs")
+ assert response.status_code == 200
+ assert "swagger-ui-dist" in response.text
+
+
+def test_redoc():
+ response = client.get("/redoc")
+ assert response.status_code == 200
+ assert "redoc@next" in response.text
--- /dev/null
+import pytest
+from starlette.testclient import TestClient
+
+from bigger_applications.app.tutorial003 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "Fast API", "version": "0.1.0"},
+ "paths": {
+ "/users/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Read Users Get",
+ "operationId": "read_users_users__get",
+ }
+ },
+ "/users/{username}": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read User Get",
+ "operationId": "read_user_users__username__get",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {"title": "Username", "type": "string"},
+ "name": "username",
+ "in": "path",
+ }
+ ],
+ }
+ },
+ "/users/me": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Read User Me Get",
+ "operationId": "read_user_me_users_me_get",
+ }
+ },
+ "/items/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Read Items Get",
+ "operationId": "read_items_items__get",
+ }
+ },
+ "/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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ ("/users", 200, [{"username": "Foo"}, {"username": "Bar"}]),
+ ("/users/foo", 200, {"username": "foo"}),
+ ("/users/me", 200, {"username": "fakecurrentuser"}),
+ ("/items", 200, [{"name": "Item Foo"}, {"name": "item Bar"}]),
+ ("/items/bar", 200, {"name": "Fake Specific Item", "item_id": "bar"}),
+ ("/openapi.json", 200, openapi_schema),
+ ],
+)
+def test_get_path(path, expected_status, expected_response):
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
--- /dev/null
+import pytest
+from starlette.testclient import TestClient
+
+from body.tutorial001 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "Fast API", "version": "0.1.0"},
+ "paths": {
+ "/items/": {
+ "post": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Create Item Post",
+ "operationId": "create_item_items__post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ },
+ "required": True,
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"title": "Tax", "type": "number"},
+ },
+ },
+ "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_openapi_scheme():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == openapi_schema
+
+
+price_missing = {
+ "detail": [
+ {
+ "loc": ["body", "item", "price"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ }
+ ]
+}
+
+price_not_float = {
+ "detail": [
+ {
+ "loc": ["body", "item", "price"],
+ "msg": "value is not a valid float",
+ "type": "type_error.float",
+ }
+ ]
+}
+
+name_price_missing = {
+ "detail": [
+ {
+ "loc": ["body", "item", "name"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ {
+ "loc": ["body", "item", "price"],
+ "msg": "field required",
+ "type": "value_error.missing",
+ },
+ ]
+}
+
+
+@pytest.mark.parametrize(
+ "path,body,expected_status,expected_response",
+ [
+ (
+ "/items/",
+ {"name": "Foo", "price": 50.5},
+ 200,
+ {"name": "Foo", "price": 50.5, "description": None, "tax": None},
+ ),
+ (
+ "/items/",
+ {"name": "Foo", "price": "50.5"},
+ 200,
+ {"name": "Foo", "price": 50.5, "description": None, "tax": None},
+ ),
+ (
+ "/items/",
+ {"name": "Foo", "price": "50.5", "description": "Some Foo"},
+ 200,
+ {"name": "Foo", "price": 50.5, "description": "Some Foo", "tax": None},
+ ),
+ (
+ "/items/",
+ {"name": "Foo", "price": "50.5", "description": "Some Foo", "tax": 0.3},
+ 200,
+ {"name": "Foo", "price": 50.5, "description": "Some Foo", "tax": 0.3},
+ ),
+ ("/items/", {"name": "Foo"}, 422, price_missing),
+ ("/items/", {"name": "Foo", "price": "twenty"}, 422, price_not_float),
+ ("/items/", {}, 422, name_price_missing),
+ ],
+)
+def test_post_body(path, body, expected_status, expected_response):
+ response = client.post(path, json=body)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
--- /dev/null
+import pytest
+from starlette.testclient import TestClient
+
+from body_multiple_params.tutorial001 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "Fast API", "version": "0.1.0"},
+ "paths": {
+ "/items/{item_id}": {
+ "put": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Update Item Put",
+ "operationId": "update_item_items__item_id__put",
+ "parameters": [
+ {
+ "required": True,
+ "schema": {
+ "title": "The ID of the item to get",
+ "maximum": 1000.0,
+ "minimum": 0.0,
+ "type": "integer",
+ },
+ "name": "item_id",
+ "in": "path",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Q", "type": "string"},
+ "name": "q",
+ "in": "query",
+ },
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/Item"}
+ }
+ }
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "Item": {
+ "title": "Item",
+ "required": ["name", "price"],
+ "type": "object",
+ "properties": {
+ "name": {"title": "Name", "type": "string"},
+ "price": {"title": "Price", "type": "number"},
+ "description": {"title": "Description", "type": "string"},
+ "tax": {"title": "Tax", "type": "number"},
+ },
+ },
+ "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_openapi_scheme():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == openapi_schema
+
+
+item_id_not_int = {
+ "detail": [
+ {
+ "loc": ["path", "item_id"],
+ "msg": "value is not a valid integer",
+ "type": "type_error.integer",
+ }
+ ]
+}
+
+
+@pytest.mark.parametrize(
+ "path,body,expected_status,expected_response",
+ [
+ (
+ "/items/5?q=bar",
+ {"name": "Foo", "price": 50.5},
+ 200,
+ {
+ "item_id": 5,
+ "item": {
+ "name": "Foo",
+ "price": 50.5,
+ "description": None,
+ "tax": None,
+ },
+ "q": "bar",
+ },
+ ),
+ ("/items/5?q=bar", None, 200, {"item_id": 5, "q": "bar"}),
+ ("/items/5", None, 200, {"item_id": 5}),
+ ("/items/foo", None, 422, item_id_not_int),
+ ],
+)
+def test_post_body(path, body, expected_status, expected_response):
+ response = client.put(path, json=body)
+ print(response.text)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
--- /dev/null
+import pytest
+from starlette.testclient import TestClient
+
+from dependencies.tutorial001 import app
+
+client = TestClient(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": {}}},
+ },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ },
+ },
+ "summary": "Read Items Get",
+ "operationId": "read_items_items__get",
+ "parameters": [
+ {
+ "required": False,
+ "schema": {"title": "Q", "type": "string"},
+ "name": "q",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Skip", "type": "integer", "default": 0},
+ "name": "skip",
+ "in": "query",
+ },
+ {
+ "required": False,
+ "schema": {"title": "Limit", "type": "integer", "default": 100},
+ "name": "limit",
+ "in": "query",
+ },
+ ],
+ }
+ }
+ },
+ "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"},
+ }
+ },
+ },
+ }
+ },
+}
+
+
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ ("/items", 200, {"q": None, "skip": 0, "limit": 100}),
+ ("/items?q=foo", 200, {"q": "foo", "skip": 0, "limit": 100}),
+ ("/items?q=foo&skip=5", 200, {"q": "foo", "skip": 5, "limit": 100}),
+ ("/items?q=foo&skip=5&limit=30", 200, {"q": "foo", "skip": 5, "limit": 30}),
+ ("/openapi.json", 200, openapi_schema),
+ ],
+)
+def test_get(path, expected_status, expected_response):
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
--- /dev/null
+import pytest
+from starlette.testclient import TestClient
+
+from first_steps.tutorial001 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+ "openapi": "3.0.2",
+ "info": {"title": "Fast API", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "get": {
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {"application/json": {"schema": {}}},
+ }
+ },
+ "summary": "Root Get",
+ "operationId": "root__get",
+ }
+ }
+ },
+}
+
+
+@pytest.mark.parametrize(
+ "path,expected_status,expected_response",
+ [
+ ("/", 200, {"message": "Hello World"}),
+ ("/nonexistent", 404, {"detail": "Not Found"}),
+ ("/openapi.json", 200, openapi_schema),
+ ],
+)
+def test_get_path(path, expected_status, expected_response):
+ response = client.get(path)
+ assert response.status_code == expected_status
+ assert response.json() == expected_response
--- /dev/null
+from starlette.testclient import TestClient
+
+from security.tutorial001 import app
+
+client = TestClient(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",
+ "security": [{"OAuth2PasswordBearer": []}],
+ }
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "OAuth2PasswordBearer": {
+ "type": "oauth2",
+ "flows": {"password": {"scopes": {}, "tokenUrl": "/token"}},
+ }
+ }
+ },
+}
+
+
+def test_openapi_scheme():
+ response = client.get("/openapi.json")
+ assert response.status_code == 200
+ assert response.json() == openapi_schema
+
+
+def test_no_token():
+ response = client.get("/items")
+ assert response.status_code == 403
+ assert response.json() == {"detail": "Not authenticated"}
+
+
+def test_token():
+ response = client.get("/items", headers={"Authorization": "Bearer testtoken"})
+ assert response.status_code == 200
+ assert response.json() == {"token": "testtoken"}
+
+
+def test_incorrect_token():
+ response = client.get("/items", headers={"Authorization": "Notexistent testtoken"})
+ assert response.status_code == 403
+ assert response.json() == {"detail": "Not authenticated"}