committed by
GitHub
90 changed files with 4418 additions and 139 deletions
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 44 KiB |
@ -0,0 +1,154 @@ |
|||
# Cookie Parameter Models |
|||
|
|||
If you have a group of **cookies** that are related, you can create a **Pydantic model** to declare them. 🍪 |
|||
|
|||
This would allow you to **re-use the model** in **multiple places** and also to declare validations and metadata for all the parameters at once. 😎 |
|||
|
|||
/// note |
|||
|
|||
This is supported since FastAPI version `0.115.0`. 🤓 |
|||
|
|||
/// |
|||
|
|||
/// tip |
|||
|
|||
This same technique applies to `Query`, `Cookie`, and `Header`. 😎 |
|||
|
|||
/// |
|||
|
|||
## Cookies with a Pydantic Model |
|||
|
|||
Declare the **cookie** parameters that you need in a **Pydantic model**, and then declare the parameter as `Cookie`: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python hl_lines="9-12 16" |
|||
{!> ../../../docs_src/cookie_param_models/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="9-12 16" |
|||
{!> ../../../docs_src/cookie_param_models/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="10-13 17" |
|||
{!> ../../../docs_src/cookie_param_models/tutorial001_an.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.10+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="7-10 14" |
|||
{!> ../../../docs_src/cookie_param_models/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="9-12 16" |
|||
{!> ../../../docs_src/cookie_param_models/tutorial001.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
**FastAPI** will **extract** the data for **each field** from the **cookies** received in the request and give you the Pydantic model you defined. |
|||
|
|||
## Check the Docs |
|||
|
|||
You can see the defined cookies in the docs UI at `/docs`: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/cookie-param-models/image01.png"> |
|||
</div> |
|||
|
|||
/// info |
|||
|
|||
Have in mind that, as **browsers handle cookies** in special ways and behind the scenes, they **don't** easily allow **JavaScript** to touch them. |
|||
|
|||
If you go to the **API docs UI** at `/docs` you will be able to see the **documentation** for cookies for your *path operations*. |
|||
|
|||
But even if you **fill the data** and click "Execute", because the docs UI works with **JavaScript**, the cookies won't be sent, and you will see an **error** message as if you didn't write any values. |
|||
|
|||
/// |
|||
|
|||
## Forbid Extra Cookies |
|||
|
|||
In some special use cases (probably not very common), you might want to **restrict** the cookies that you want to receive. |
|||
|
|||
Your API now has the power to control its own <abbr title="This is a joke, just in case. It has nothing to do with cookie consents, but it's funny that even the API can now reject the poor cookies. Have a cookie. 🍪">cookie consent</abbr>. 🤪🍪 |
|||
|
|||
You can use Pydantic's model configuration to `forbid` any `extra` fields: |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/cookie_param_models/tutorial002_an_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="11" |
|||
{!> ../../../docs_src/cookie_param_models/tutorial002_an.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/cookie_param_models/tutorial002.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
If a client tries to send some **extra cookies**, they will receive an **error** response. |
|||
|
|||
Poor cookie banners with all their effort to get your consent for the <abbr title="This is another joke. Don't pay attention to me. Have some coffee for your cookie. ☕">API to reject it</abbr>. 🍪 |
|||
|
|||
For example, if the client tries to send a `santa_tracker` cookie with a value of `good-list-please`, the client will receive an **error** response telling them that the `santa_tracker` <abbr title="Santa disapproves the lack of cookies. 🎅 Okay, no more cookie jokes.">cookie is not allowed</abbr>: |
|||
|
|||
```json |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "extra_forbidden", |
|||
"loc": ["cookie", "santa_tracker"], |
|||
"msg": "Extra inputs are not permitted", |
|||
"input": "good-list-please", |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
## Summary |
|||
|
|||
You can use **Pydantic models** to declare <abbr title="Have a last cookie before you go. 🍪">**cookies**</abbr> in **FastAPI**. 😎 |
@ -0,0 +1,184 @@ |
|||
# Header Parameter Models |
|||
|
|||
If you have a group of related **header parameters**, you can create a **Pydantic model** to declare them. |
|||
|
|||
This would allow you to **re-use the model** in **multiple places** and also to declare validations and metadata for all the parameters at once. 😎 |
|||
|
|||
/// note |
|||
|
|||
This is supported since FastAPI version `0.115.0`. 🤓 |
|||
|
|||
/// |
|||
|
|||
## Header Parameters with a Pydantic Model |
|||
|
|||
Declare the **header parameters** that you need in a **Pydantic model**, and then declare the parameter as `Header`: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python hl_lines="9-14 18" |
|||
{!> ../../../docs_src/header_param_models/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="9-14 18" |
|||
{!> ../../../docs_src/header_param_models/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="10-15 19" |
|||
{!> ../../../docs_src/header_param_models/tutorial001_an.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.10+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="7-12 16" |
|||
{!> ../../../docs_src/header_param_models/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="9-14 18" |
|||
{!> ../../../docs_src/header_param_models/tutorial001_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="7-12 16" |
|||
{!> ../../../docs_src/header_param_models/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
**FastAPI** will **extract** the data for **each field** from the **headers** in the request and give you the Pydantic model you defined. |
|||
|
|||
## Check the Docs |
|||
|
|||
You can see the required headers in the docs UI at `/docs`: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/header-param-models/image01.png"> |
|||
</div> |
|||
|
|||
## Forbid Extra Headers |
|||
|
|||
In some special use cases (probably not very common), you might want to **restrict** the headers that you want to receive. |
|||
|
|||
You can use Pydantic's model configuration to `forbid` any `extra` fields: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/header_param_models/tutorial002_an_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/header_param_models/tutorial002_an_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="11" |
|||
{!> ../../../docs_src/header_param_models/tutorial002_an.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.10+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="8" |
|||
{!> ../../../docs_src/header_param_models/tutorial002_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/header_param_models/tutorial002_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/header_param_models/tutorial002.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
If a client tries to send some **extra headers**, they will receive an **error** response. |
|||
|
|||
For example, if the client tries to send a `tool` header with a value of `plumbus`, they will receive an **error** response telling them that the header parameter `tool` is not allowed: |
|||
|
|||
```json |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "extra_forbidden", |
|||
"loc": ["header", "tool"], |
|||
"msg": "Extra inputs are not permitted", |
|||
"input": "plumbus", |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
## Summary |
|||
|
|||
You can use **Pydantic models** to declare **headers** in **FastAPI**. 😎 |
@ -0,0 +1,196 @@ |
|||
# Query Parameter Models |
|||
|
|||
If you have a group of **query parameters** that are related, you can create a **Pydantic model** to declare them. |
|||
|
|||
This would allow you to **re-use the model** in **multiple places** and also to declare validations and metadata for all the parameters at once. 😎 |
|||
|
|||
/// note |
|||
|
|||
This is supported since FastAPI version `0.115.0`. 🤓 |
|||
|
|||
/// |
|||
|
|||
## Query Parameters with a Pydantic Model |
|||
|
|||
Declare the **query parameters** that you need in a **Pydantic model**, and then declare the parameter as `Query`: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python hl_lines="9-13 17" |
|||
{!> ../../../docs_src/query_param_models/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="8-12 16" |
|||
{!> ../../../docs_src/query_param_models/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="10-14 18" |
|||
{!> ../../../docs_src/query_param_models/tutorial001_an.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.10+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="9-13 17" |
|||
{!> ../../../docs_src/query_param_models/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="8-12 16" |
|||
{!> ../../../docs_src/query_param_models/tutorial001_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="9-13 17" |
|||
{!> ../../../docs_src/query_param_models/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
**FastAPI** will **extract** the data for **each field** from the **query parameters** in the request and give you the Pydantic model you defined. |
|||
|
|||
## Check the Docs |
|||
|
|||
You can see the query parameters in the docs UI at `/docs`: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/query-param-models/image01.png"> |
|||
</div> |
|||
|
|||
## Forbid Extra Query Parameters |
|||
|
|||
In some special use cases (probably not very common), you might want to **restrict** the query parameters that you want to receive. |
|||
|
|||
You can use Pydantic's model configuration to `forbid` any `extra` fields: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/query_param_models/tutorial002_an_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/query_param_models/tutorial002_an_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="11" |
|||
{!> ../../../docs_src/query_param_models/tutorial002_an.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.10+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/query_param_models/tutorial002_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/query_param_models/tutorial002_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefer to use the `Annotated` version if possible. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="11" |
|||
{!> ../../../docs_src/query_param_models/tutorial002.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
If a client tries to send some **extra** data in the **query parameters**, they will receive an **error** response. |
|||
|
|||
For example, if the client tries to send a `tool` query parameter with a value of `plumbus`, like: |
|||
|
|||
```http |
|||
https://example.com/items/?limit=10&tool=plumbus |
|||
``` |
|||
|
|||
They will receive an **error** response telling them that the query parameter `tool` is not allowed: |
|||
|
|||
```json |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "extra_forbidden", |
|||
"loc": ["query", "tool"], |
|||
"msg": "Extra inputs are not permitted", |
|||
"input": "plumbus" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
## Summary |
|||
|
|||
You can use **Pydantic models** to declare **query parameters** in **FastAPI**. 😎 |
|||
|
|||
/// tip |
|||
|
|||
Spoiler alert: you can also use Pydantic models to declare cookies and headers, but you will read about that later in the tutorial. 🤫 |
|||
|
|||
/// |
@ -0,0 +1,597 @@ |
|||
# Introductie tot Python Types |
|||
|
|||
Python biedt ondersteuning voor optionele "type hints" (ook wel "type annotaties" genoemd). |
|||
|
|||
Deze **"type hints"** of annotaties zijn een speciale syntax waarmee het <abbr title="bijvoorbeeld: str, int, float, bool">type</abbr> van een variabele kan worden gedeclareerd. |
|||
|
|||
Door types voor je variabelen te declareren, kunnen editors en hulpmiddelen je beter ondersteunen. |
|||
|
|||
Dit is slechts een **korte tutorial/opfrisser** over Python type hints. Het behandelt enkel het minimum dat nodig is om ze te gebruiken met **FastAPI**... en dat is relatief weinig. |
|||
|
|||
**FastAPI** is helemaal gebaseerd op deze type hints, ze geven veel voordelen. |
|||
|
|||
Maar zelfs als je **FastAPI** nooit gebruikt, heb je er baat bij om er iets over te leren. |
|||
|
|||
/// note |
|||
|
|||
Als je een Python expert bent en alles al weet over type hints, sla dan dit hoofdstuk over. |
|||
|
|||
/// |
|||
|
|||
## Motivatie |
|||
|
|||
Laten we beginnen met een eenvoudig voorbeeld: |
|||
|
|||
```Python |
|||
{!../../../docs_src/python_types/tutorial001.py!} |
|||
``` |
|||
|
|||
Het aanroepen van dit programma leidt tot het volgende resultaat: |
|||
|
|||
``` |
|||
John Doe |
|||
``` |
|||
|
|||
De functie voert het volgende uit: |
|||
|
|||
* Neem een `first_name` en een `last_name` |
|||
* Converteer de eerste letter van elk naar een hoofdletter met `title()`. |
|||
`` |
|||
* <abbr title="Voegt ze samen, als één. Met de inhoud van de een na de ander.">Voeg samen</abbr> met een spatie in het midden. |
|||
|
|||
```Python hl_lines="2" |
|||
{!../../../docs_src/python_types/tutorial001.py!} |
|||
``` |
|||
|
|||
### Bewerk het |
|||
|
|||
Dit is een heel eenvoudig programma. |
|||
|
|||
Maar stel je nu voor dat je het vanaf nul zou moeten maken. |
|||
|
|||
Op een gegeven moment zou je aan de definitie van de functie zijn begonnen, je had de parameters klaar... |
|||
|
|||
Maar dan moet je “die methode die de eerste letter naar hoofdletters converteert” aanroepen. |
|||
|
|||
Was het `upper`? Was het `uppercase`? `first_uppercase`? `capitalize`? |
|||
|
|||
Dan roep je de hulp in van je oude programmeursvriend, (automatische) code aanvulling in je editor. |
|||
|
|||
Je typt de eerste parameter van de functie, `first_name`, dan een punt (`.`) en drukt dan op `Ctrl+Spatie` om de aanvulling te activeren. |
|||
|
|||
Maar helaas krijg je niets bruikbaars: |
|||
|
|||
<img src="/img/python-types/image01.png"> |
|||
|
|||
### Types toevoegen |
|||
|
|||
Laten we een enkele regel uit de vorige versie aanpassen. |
|||
|
|||
We zullen precies dit fragment, de parameters van de functie, wijzigen van: |
|||
|
|||
```Python |
|||
first_name, last_name |
|||
``` |
|||
|
|||
naar: |
|||
|
|||
```Python |
|||
first_name: str, last_name: str |
|||
``` |
|||
|
|||
Dat is alles. |
|||
|
|||
Dat zijn de "type hints": |
|||
|
|||
```Python hl_lines="1" |
|||
{!../../../docs_src/python_types/tutorial002.py!} |
|||
``` |
|||
|
|||
Dit is niet hetzelfde als het declareren van standaardwaarden zoals bij: |
|||
|
|||
```Python |
|||
first_name="john", last_name="doe" |
|||
``` |
|||
|
|||
Het is iets anders. |
|||
|
|||
We gebruiken dubbele punten (`:`), geen gelijkheidstekens (`=`). |
|||
|
|||
Het toevoegen van type hints verandert normaal gesproken niet wat er gebeurt in je programma t.o.v. wat er zonder type hints zou gebeuren. |
|||
|
|||
Maar stel je voor dat je weer bezig bent met het maken van een functie, maar deze keer met type hints. |
|||
|
|||
Op hetzelfde moment probeer je de automatische aanvulling te activeren met `Ctrl+Spatie` en je ziet: |
|||
|
|||
<img src="/img/python-types/image02.png"> |
|||
|
|||
Nu kun je de opties bekijken en er doorheen scrollen totdat je de optie vindt die “een belletje doet rinkelen”: |
|||
|
|||
<img src="/img/python-types/image03.png"> |
|||
|
|||
### Meer motivatie |
|||
|
|||
Bekijk deze functie, deze heeft al type hints: |
|||
|
|||
```Python hl_lines="1" |
|||
{!../../../docs_src/python_types/tutorial003.py!} |
|||
``` |
|||
|
|||
Omdat de editor de types van de variabelen kent, krijgt u niet alleen aanvulling, maar ook controles op fouten: |
|||
|
|||
<img src="/img/python-types/image04.png"> |
|||
|
|||
Nu weet je hoe je het moet oplossen, converteer `age` naar een string met `str(age)`: |
|||
|
|||
```Python hl_lines="2" |
|||
{!../../../docs_src/python_types/tutorial004.py!} |
|||
``` |
|||
|
|||
## Types declareren |
|||
|
|||
Je hebt net de belangrijkste plek om type hints te declareren gezien. Namelijk als functieparameters. |
|||
|
|||
Dit is ook de belangrijkste plek waar je ze gebruikt met **FastAPI**. |
|||
|
|||
### Eenvoudige types |
|||
|
|||
Je kunt alle standaard Python types declareren, niet alleen `str`. |
|||
|
|||
Je kunt bijvoorbeeld het volgende gebruiken: |
|||
|
|||
* `int` |
|||
* `float` |
|||
* `bool` |
|||
* `bytes` |
|||
|
|||
```Python hl_lines="1" |
|||
{!../../../docs_src/python_types/tutorial005.py!} |
|||
``` |
|||
|
|||
### Generieke types met typeparameters |
|||
|
|||
Er zijn enkele datastructuren die andere waarden kunnen bevatten, zoals `dict`, `list`, `set` en `tuple` en waar ook de interne waarden hun eigen type kunnen hebben. |
|||
|
|||
Deze types die interne types hebben worden “**generieke**” types genoemd. Het is mogelijk om ze te declareren, zelfs met hun interne types. |
|||
|
|||
Om deze types en de interne types te declareren, kun je de standaard Python module `typing` gebruiken. Deze module is speciaal gemaakt om deze type hints te ondersteunen. |
|||
|
|||
#### Nieuwere versies van Python |
|||
|
|||
De syntax met `typing` is **verenigbaar** met alle versies, van Python 3.6 tot aan de nieuwste, inclusief Python 3.9, Python 3.10, enz. |
|||
|
|||
Naarmate Python zich ontwikkelt, worden **nieuwere versies**, met verbeterde ondersteuning voor deze type annotaties, beschikbaar. In veel gevallen hoef je niet eens de `typing` module te importeren en te gebruiken om de type annotaties te declareren. |
|||
|
|||
Als je een recentere versie van Python kunt kiezen voor je project, kun je profiteren van die extra eenvoud. |
|||
|
|||
In alle documentatie staan voorbeelden die compatibel zijn met elke versie van Python (als er een verschil is). |
|||
|
|||
Bijvoorbeeld “**Python 3.6+**” betekent dat het compatibel is met Python 3.6 of hoger (inclusief 3.7, 3.8, 3.9, 3.10, etc). En “**Python 3.9+**” betekent dat het compatibel is met Python 3.9 of hoger (inclusief 3.10, etc). |
|||
|
|||
Als je de **laatste versies van Python** kunt gebruiken, gebruik dan de voorbeelden voor de laatste versie, die hebben de **beste en eenvoudigste syntax**, bijvoorbeeld “**Python 3.10+**”. |
|||
|
|||
#### List |
|||
|
|||
Laten we bijvoorbeeld een variabele definiëren als een `list` van `str`. |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
Declareer de variabele met dezelfde dubbele punt (`:`) syntax. |
|||
|
|||
Als type, vul `list` in. |
|||
|
|||
Doordat de list een type is dat enkele interne types bevat, zet je ze tussen vierkante haakjes: |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/python_types/tutorial006_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
Van `typing`, importeer `List` (met een hoofdletter `L`): |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/python_types/tutorial006.py!} |
|||
``` |
|||
|
|||
Declareer de variabele met dezelfde dubbele punt (`:`) syntax. |
|||
|
|||
Zet als type de `List` die je hebt geïmporteerd uit `typing`. |
|||
|
|||
Doordat de list een type is dat enkele interne types bevat, zet je ze tussen vierkante haakjes: |
|||
|
|||
```Python hl_lines="4" |
|||
{!> ../../../docs_src/python_types/tutorial006.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
/// info |
|||
|
|||
De interne types tussen vierkante haakjes worden “typeparameters” genoemd. |
|||
|
|||
In dit geval is `str` de typeparameter die wordt doorgegeven aan `List` (of `list` in Python 3.9 en hoger). |
|||
|
|||
/// |
|||
|
|||
Dat betekent: “de variabele `items` is een `list`, en elk van de items in deze list is een `str`”. |
|||
|
|||
/// tip |
|||
|
|||
Als je Python 3.9 of hoger gebruikt, hoef je `List` niet te importeren uit `typing`, je kunt in plaats daarvan hetzelfde reguliere `list` type gebruiken. |
|||
|
|||
/// |
|||
|
|||
Door dat te doen, kan je editor ondersteuning bieden, zelfs tijdens het verwerken van items uit de list: |
|||
|
|||
<img src="/img/python-types/image05.png"> |
|||
|
|||
Zonder types is dat bijna onmogelijk om te bereiken. |
|||
|
|||
Merk op dat de variabele `item` een van de elementen is in de lijst `items`. |
|||
|
|||
Toch weet de editor dat het een `str` is, en biedt daar vervolgens ondersteuning voor aan. |
|||
|
|||
#### Tuple en Set |
|||
|
|||
Je kunt hetzelfde doen om `tuple`s en `set`s te declareren: |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/python_types/tutorial007_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial007.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
Dit betekent: |
|||
|
|||
* De variabele `items_t` is een `tuple` met 3 items, een `int`, nog een `int`, en een `str`. |
|||
* De variabele `items_s` is een `set`, en elk van de items is van het type `bytes`. |
|||
|
|||
#### Dict |
|||
|
|||
Om een `dict` te definiëren, geef je 2 typeparameters door, gescheiden door komma's. |
|||
|
|||
De eerste typeparameter is voor de sleutels (keys) van de `dict`. |
|||
|
|||
De tweede typeparameter is voor de waarden (values) van het `dict`: |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/python_types/tutorial008_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial008.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
Dit betekent: |
|||
|
|||
* De variabele `prices` is een `dict`: |
|||
* De sleutels van dit `dict` zijn van het type `str` (bijvoorbeeld de naam van elk item). |
|||
* De waarden van dit `dict` zijn van het type `float` (bijvoorbeeld de prijs van elk item). |
|||
|
|||
#### Union |
|||
|
|||
Je kunt een variable declareren die van **verschillende types** kan zijn, bijvoorbeeld een `int` of een `str`. |
|||
|
|||
In Python 3.6 en hoger (inclusief Python 3.10) kun je het `Union`-type van `typing` gebruiken en de mogelijke types die je wilt accepteren, tussen de vierkante haakjes zetten. |
|||
|
|||
In Python 3.10 is er ook een **nieuwe syntax** waarin je de mogelijke types kunt scheiden door een <abbr title='ook wel "bitwise of operator" genoemd, maar die betekenis is hier niet relevant'>verticale balk (`|`)</abbr>. |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/python_types/tutorial008b_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial008b.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
In beide gevallen betekent dit dat `item` een `int` of een `str` kan zijn. |
|||
|
|||
#### Mogelijk `None` |
|||
|
|||
Je kunt declareren dat een waarde een type kan hebben, zoals `str`, maar dat het ook `None` kan zijn. |
|||
|
|||
In Python 3.6 en hoger (inclusief Python 3.10) kun je het declareren door `Optional` te importeren en te gebruiken vanuit de `typing`-module. |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!../../../docs_src/python_types/tutorial009.py!} |
|||
``` |
|||
|
|||
Door `Optional[str]` te gebruiken in plaats van alleen `str`, kan de editor je helpen fouten te detecteren waarbij je ervan uit zou kunnen gaan dat een waarde altijd een `str` is, terwijl het in werkelijkheid ook `None` zou kunnen zijn. |
|||
|
|||
`Optional[EenType]` is eigenlijk een snelkoppeling voor `Union[EenType, None]`, ze zijn equivalent. |
|||
|
|||
Dit betekent ook dat je in Python 3.10 `EenType | None` kunt gebruiken: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python hl_lines="1" |
|||
{!> ../../../docs_src/python_types/tutorial009_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial009.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ alternative |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial009b.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
#### Gebruik van `Union` of `Optional` |
|||
|
|||
Als je een Python versie lager dan 3.10 gebruikt, is dit een tip vanuit mijn **subjectieve** standpunt: |
|||
|
|||
* 🚨 Vermijd het gebruik van `Optional[EenType]`. |
|||
* Gebruik in plaats daarvan **`Union[EenType, None]`** ✨. |
|||
|
|||
Beide zijn gelijkwaardig en onderliggend zijn ze hetzelfde, maar ik zou `Union` aanraden in plaats van `Optional` omdat het woord “**optional**” lijkt te impliceren dat de waarde optioneel is, en het eigenlijk betekent “het kan `None` zijn”, zelfs als het niet optioneel is en nog steeds vereist is. |
|||
|
|||
Ik denk dat `Union[SomeType, None]` explicieter is over wat het betekent. |
|||
|
|||
Het gaat alleen om de woorden en naamgeving. Maar die naamgeving kan invloed hebben op hoe jij en je teamgenoten over de code denken. |
|||
|
|||
Laten we als voorbeeld deze functie nemen: |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!../../../docs_src/python_types/tutorial009c.py!} |
|||
``` |
|||
|
|||
De parameter `name` is gedefinieerd als `Optional[str]`, maar is **niet optioneel**, je kunt de functie niet aanroepen zonder de parameter: |
|||
|
|||
```Python |
|||
say_hi() # Oh, nee, dit geeft een foutmelding! 😱 |
|||
``` |
|||
|
|||
De `name` parameter is **nog steeds vereist** (niet *optioneel*) omdat het geen standaardwaarde heeft. Toch accepteert `name` `None` als waarde: |
|||
|
|||
```Python |
|||
say_hi(name=None) # Dit werkt, None is geldig 🎉 |
|||
``` |
|||
|
|||
Het goede nieuws is dat als je eenmaal Python 3.10 gebruikt, je je daar geen zorgen meer over hoeft te maken, omdat je dan gewoon `|` kunt gebruiken om unions van types te definiëren: |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!../../../docs_src/python_types/tutorial009c_py310.py!} |
|||
``` |
|||
|
|||
Dan hoef je je geen zorgen te maken over namen als `Optional` en `Union`. 😎 |
|||
|
|||
#### Generieke typen |
|||
|
|||
De types die typeparameters in vierkante haakjes gebruiken, worden **Generieke types** of **Generics** genoemd, bijvoorbeeld: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
Je kunt dezelfde ingebouwde types gebruiken als generics (met vierkante haakjes en types erin): |
|||
|
|||
* `list` |
|||
* `tuple` |
|||
* `set` |
|||
* `dict` |
|||
|
|||
Hetzelfde als bij Python 3.8, uit de `typing`-module: |
|||
|
|||
* `Union` |
|||
* `Optional` (hetzelfde als bij Python 3.8) |
|||
* ...en anderen. |
|||
|
|||
In Python 3.10 kun je , als alternatief voor de generieke `Union` en `Optional`, de <abbr title='ook wel "bitwise or operator" genoemd, maar die betekenis is hier niet relevant'>verticale lijn (`|`)</abbr> gebruiken om unions van typen te voorzien, dat is veel beter en eenvoudiger. |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
Je kunt dezelfde ingebouwde types gebruiken als generieke types (met vierkante haakjes en types erin): |
|||
|
|||
* `list` |
|||
* `tuple` |
|||
* `set` |
|||
* `dict` |
|||
|
|||
En hetzelfde als met Python 3.8, vanuit de `typing`-module: |
|||
|
|||
* `Union` |
|||
* `Optional` |
|||
* ...en anderen. |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
* `List` |
|||
* `Tuple` |
|||
* `Set` |
|||
* `Dict` |
|||
* `Union` |
|||
* `Optional` |
|||
* ...en anderen. |
|||
|
|||
//// |
|||
|
|||
### Klassen als types |
|||
|
|||
Je kunt een klasse ook declareren als het type van een variabele. |
|||
|
|||
Stel dat je een klasse `Person` hebt, met een naam: |
|||
|
|||
```Python hl_lines="1-3" |
|||
{!../../../docs_src/python_types/tutorial010.py!} |
|||
``` |
|||
|
|||
Vervolgens kun je een variabele van het type `Persoon` declareren: |
|||
|
|||
```Python hl_lines="6" |
|||
{!../../../docs_src/python_types/tutorial010.py!} |
|||
``` |
|||
|
|||
Dan krijg je ook nog eens volledige editorondersteuning: |
|||
|
|||
<img src="/img/python-types/image06.png"> |
|||
|
|||
Merk op dat dit betekent dat "`one_person` een **instantie** is van de klasse `Person`". |
|||
|
|||
Dit betekent niet dat `one_person` de **klasse** is met de naam `Person`. |
|||
|
|||
## Pydantic modellen |
|||
|
|||
<a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic</a> is een Python-pakket voor het uitvoeren van datavalidatie. |
|||
|
|||
Je declareert de "vorm" van de data als klassen met attributen. |
|||
|
|||
Elk attribuut heeft een type. |
|||
|
|||
Vervolgens maak je een instantie van die klasse met een aantal waarden en het valideert de waarden, converteert ze naar het juiste type (als dat het geval is) en geeft je een object met alle data terug. |
|||
|
|||
Daarnaast krijg je volledige editorondersteuning met dat resulterende object. |
|||
|
|||
Een voorbeeld uit de officiële Pydantic-documentatie: |
|||
|
|||
//// tab | Python 3.10+ |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/python_types/tutorial011_py310.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/python_types/tutorial011_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python |
|||
{!> ../../../docs_src/python_types/tutorial011.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
/// info |
|||
|
|||
Om meer te leren over <a href="https://docs.pydantic.dev/" class="external-link" target="_blank">Pydantic, bekijk de documentatie</a>. |
|||
|
|||
/// |
|||
|
|||
**FastAPI** is volledig gebaseerd op Pydantic. |
|||
|
|||
Je zult veel meer van dit alles in de praktijk zien in de [Tutorial - Gebruikershandleiding](tutorial/index.md){.internal-link target=_blank}. |
|||
|
|||
/// tip |
|||
|
|||
Pydantic heeft een speciaal gedrag wanneer je `Optional` of `Union[EenType, None]` gebruikt zonder een standaardwaarde, je kunt er meer over lezen in de Pydantic-documentatie over <a href="https://docs.pydantic.dev/2.3/usage/models/#required-fields" class="external-link" target="_blank">Verplichte optionele velden</a>. |
|||
|
|||
/// |
|||
|
|||
## Type Hints met Metadata Annotaties |
|||
|
|||
Python heeft ook een functie waarmee je **extra <abbr title="Data over de data, in dit geval informatie over het type, bijvoorbeeld een beschrijving.">metadata</abbr>** in deze type hints kunt toevoegen met behulp van `Annotated`. |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
In Python 3.9 is `Annotated` onderdeel van de standaardpakket, dus je kunt het importeren vanuit `typing`. |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial013_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
In versies lager dan Python 3.9 importeer je `Annotated` vanuit `typing_extensions`. |
|||
|
|||
Het wordt al geïnstalleerd met **FastAPI**. |
|||
|
|||
```Python hl_lines="1 4" |
|||
{!> ../../../docs_src/python_types/tutorial013.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
Python zelf doet niets met deze `Annotated` en voor editors en andere hulpmiddelen is het type nog steeds een `str`. |
|||
|
|||
Maar je kunt deze ruimte in `Annotated` gebruiken om **FastAPI** te voorzien van extra metadata over hoe je wilt dat je applicatie zich gedraagt. |
|||
|
|||
Het belangrijkste om te onthouden is dat **de eerste *typeparameter*** die je doorgeeft aan `Annotated` het **werkelijke type** is. De rest is gewoon metadata voor andere hulpmiddelen. |
|||
|
|||
Voor nu hoef je alleen te weten dat `Annotated` bestaat en dat het standaard Python is. 😎 |
|||
|
|||
Later zul je zien hoe **krachtig** het kan zijn. |
|||
|
|||
/// tip |
|||
|
|||
Het feit dat dit **standaard Python** is, betekent dat je nog steeds de **best mogelijke ontwikkelaarservaring** krijgt in je editor, met de hulpmiddelen die je gebruikt om je code te analyseren en te refactoren, enz. ✨ |
|||
|
|||
Daarnaast betekent het ook dat je code zeer verenigbaar zal zijn met veel andere Python-hulpmiddelen en -pakketten. 🚀 |
|||
|
|||
/// |
|||
|
|||
## Type hints in **FastAPI** |
|||
|
|||
**FastAPI** maakt gebruik van type hints om verschillende dingen te doen. |
|||
|
|||
Met **FastAPI** declareer je parameters met type hints en krijg je: |
|||
|
|||
* **Editor ondersteuning**. |
|||
* **Type checks**. |
|||
|
|||
...en **FastAPI** gebruikt dezelfde declaraties om: |
|||
|
|||
* **Vereisten te definïeren **: van request pad parameters, query parameters, headers, bodies, dependencies, enz. |
|||
* **Data te converteren**: van de request naar het vereiste type. |
|||
* **Data te valideren**: afkomstig van elke request: |
|||
* **Automatische foutmeldingen** te genereren die naar de client worden geretourneerd wanneer de data ongeldig is. |
|||
* De API met OpenAPI te **documenteren**: |
|||
* die vervolgens wordt gebruikt door de automatische interactieve documentatie gebruikersinterfaces. |
|||
|
|||
Dit klinkt misschien allemaal abstract. Maak je geen zorgen. Je ziet dit allemaal in actie in de [Tutorial - Gebruikershandleiding](tutorial/index.md){.internal-link target=_blank}. |
|||
|
|||
Het belangrijkste is dat door standaard Python types te gebruiken, op één plek (in plaats van meer klassen, decorators, enz. toe te voegen), **FastAPI** een groot deel van het werk voor je doet. |
|||
|
|||
/// info |
|||
|
|||
Als je de hele tutorial al hebt doorgenomen en terug bent gekomen om meer te weten te komen over types, is een goede bron <a href="https://mypy.readthedocs.io/en/latest/cheat_sheet_py3.html" class="external-link" target="_blank">het "cheat sheet" van `mypy`</a>. |
|||
|
|||
/// |
@ -0,0 +1,192 @@ |
|||
# HTTP Basic Auth |
|||
|
|||
Para os casos mais simples, você pode utilizar o HTTP Basic Auth. |
|||
|
|||
No HTTP Basic Auth, a aplicação espera um cabeçalho que contém um usuário e uma senha. |
|||
|
|||
Caso ela não receba, ela retorna um erro HTTP 401 "Unauthorized" (*Não Autorizado*). |
|||
|
|||
E retorna um cabeçalho `WWW-Authenticate` com o valor `Basic`, e um parâmetro opcional `realm`. |
|||
|
|||
Isso sinaliza ao navegador para mostrar o prompt integrado para um usuário e senha. |
|||
|
|||
Então, quando você digitar o usuário e senha, o navegador os envia automaticamente no cabeçalho. |
|||
|
|||
## HTTP Basic Auth Simples |
|||
|
|||
* Importe `HTTPBasic` e `HTTPBasicCredentials`. |
|||
* Crie um "esquema `security`" utilizando `HTTPBasic`. |
|||
* Utilize o `security` com uma dependência em sua *operação de rota*. |
|||
* Isso retorna um objeto do tipo `HTTPBasicCredentials`: |
|||
* Isto contém o `username` e o `password` enviado. |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="4 8 12" |
|||
{!> ../../../docs_src/security/tutorial006_an_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="2 7 11" |
|||
{!> ../../../docs_src/security/tutorial006_an.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ non-Annotated |
|||
|
|||
/// tip | "Dica" |
|||
|
|||
Prefira utilizar a versão `Annotated` se possível. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="2 6 10" |
|||
{!> ../../../docs_src/security/tutorial006.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
Quando você tentar abrir a URL pela primeira vez (ou clicar no botão "Executar" nos documentos) o navegador vai pedir pelo seu usuário e senha: |
|||
|
|||
<img src="/img/tutorial/security/image12.png"> |
|||
|
|||
## Verifique o usuário |
|||
|
|||
Aqui está um exemplo mais completo. |
|||
|
|||
Utilize uma dependência para verificar se o usuário e a senha estão corretos. |
|||
|
|||
Para isso, utilize o módulo padrão do Python <a href="https://docs.python.org/3/library/secrets.html" class="external-link" target="_blank">`secrets`</a> para verificar o usuário e senha. |
|||
|
|||
O `secrets.compare_digest()` necessita receber `bytes` ou `str` que possuem apenas caracteres ASCII (os em Inglês). Isso significa que não funcionaria com caracteres como o `á`, como em `Sebastián`. |
|||
|
|||
Para lidar com isso, primeiramente nós convertemos o `username` e o `password` para `bytes`, codificando-os com UTF-8. |
|||
|
|||
Então nós podemos utilizar o `secrets.compare_digest()` para garantir que o `credentials.username` é `"stanleyjobson"`, e que o `credentials.password` é `"swordfish"`. |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="1 12-24" |
|||
{!> ../../../docs_src/security/tutorial007_an_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="1 12-24" |
|||
{!> ../../../docs_src/security/tutorial007_an.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ non-Annotated |
|||
|
|||
/// tip | "Dica" |
|||
|
|||
Prefira utilizar a versão `Annotated` se possível. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="1 11-21" |
|||
{!> ../../../docs_src/security/tutorial007.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
Isso seria parecido com: |
|||
|
|||
```Python |
|||
if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): |
|||
# Return some error |
|||
... |
|||
``` |
|||
|
|||
Porém ao utilizar o `secrets.compare_digest()`, isso estará seguro contra um tipo de ataque chamado "ataque de temporização (timing attacks)". |
|||
|
|||
### Ataques de Temporização |
|||
|
|||
Mas o que é um "ataque de temporização"? |
|||
|
|||
Vamos imaginar que alguns invasores estão tentando adivinhar o usuário e a senha. |
|||
|
|||
E eles enviam uma requisição com um usuário `johndoe` e uma senha `love123`. |
|||
|
|||
Então o código Python em sua aplicação seria equivalente a algo como: |
|||
|
|||
```Python |
|||
if "johndoe" == "stanleyjobson" and "love123" == "swordfish": |
|||
... |
|||
``` |
|||
|
|||
Mas no exato momento que o Python compara o primeiro `j` em `johndoe` contra o primeiro `s` em `stanleyjobson`, ele retornará `False`, porque ele já sabe que aquelas duas strings não são a mesma, pensando que "não existe a necessidade de desperdiçar mais poder computacional comparando o resto das letras". E a sua aplicação dirá "Usuário ou senha incorretos". |
|||
|
|||
Mas então os invasores vão tentar com o usuário `stanleyjobsox` e a senha `love123`. |
|||
|
|||
E a sua aplicação faz algo como: |
|||
|
|||
```Python |
|||
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": |
|||
... |
|||
``` |
|||
|
|||
O Python terá que comparar todo o `stanleyjobso` tanto em `stanleyjobsox` como em `stanleyjobson` antes de perceber que as strings não são a mesma. Então isso levará alguns microsegundos a mais para retornar "Usuário ou senha incorretos". |
|||
|
|||
#### O tempo para responder ajuda os invasores |
|||
|
|||
Neste ponto, ao perceber que o servidor demorou alguns microsegundos a mais para enviar o retorno "Usuário ou senha incorretos", os invasores irão saber que eles acertaram _alguma coisa_, algumas das letras iniciais estavam certas. |
|||
|
|||
E eles podem tentar de novo sabendo que provavelmente é algo mais parecido com `stanleyjobsox` do que com `johndoe`. |
|||
|
|||
#### Um ataque "profissional" |
|||
|
|||
Claro, os invasores não tentariam tudo isso de forma manual, eles escreveriam um programa para fazer isso, possivelmente com milhares ou milhões de testes por segundo. E obteriam apenas uma letra a mais por vez. |
|||
|
|||
Mas fazendo isso, em alguns minutos ou horas os invasores teriam adivinhado o usuário e senha corretos, com a "ajuda" da nossa aplicação, apenas usando o tempo levado para responder. |
|||
|
|||
#### Corrija com o `secrets.compare_digest()` |
|||
|
|||
Mas em nosso código nós estamos utilizando o `secrets.compare_digest()`. |
|||
|
|||
Resumindo, levará o mesmo tempo para comparar `stanleyjobsox` com `stanleyjobson` do que comparar `johndoe` com `stanleyjobson`. E o mesmo para a senha. |
|||
|
|||
Deste modo, ao utilizar `secrets.compare_digest()` no código de sua aplicação, ela esterá a salvo contra toda essa gama de ataques de segurança. |
|||
|
|||
|
|||
### Retorne o erro |
|||
|
|||
Depois de detectar que as credenciais estão incorretas, retorne um `HTTPException` com o status 401 (o mesmo retornado quando nenhuma credencial foi informada) e adicione o cabeçalho `WWW-Authenticate` para fazer com que o navegador mostre o prompt de login novamente: |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="26-30" |
|||
{!> ../../../docs_src/security/tutorial007_an_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="26-30" |
|||
{!> ../../../docs_src/security/tutorial007_an.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ non-Annotated |
|||
|
|||
/// tip | "Dica" |
|||
|
|||
Prefira utilizar a versão `Annotated` se possível. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="23-27" |
|||
{!> ../../../docs_src/security/tutorial007.py!} |
|||
``` |
|||
|
|||
//// |
@ -0,0 +1,134 @@ |
|||
# Modelos de Formulários |
|||
|
|||
Você pode utilizar **Modelos Pydantic** para declarar **campos de formulários** no FastAPI. |
|||
|
|||
/// info | "Informação" |
|||
|
|||
Para utilizar formulários, instale primeiramente o <a href="https://github.com/Kludex/python-multipart" class="external-link" target="_blank">`python-multipart`</a>. |
|||
|
|||
Certifique-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo, e então instalar. Por exemplo: |
|||
|
|||
```console |
|||
$ pip install python-multipart |
|||
``` |
|||
|
|||
/// |
|||
|
|||
/// note | "Nota" |
|||
|
|||
Isto é suportado desde a versão `0.113.0` do FastAPI. 🤓 |
|||
|
|||
/// |
|||
|
|||
## Modelos Pydantic para Formulários |
|||
|
|||
Você precisa apenas declarar um **modelo Pydantic** com os campos que deseja receber como **campos de formulários**, e então declarar o parâmetro como um `Form`: |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="9-11 15" |
|||
{!> ../../../docs_src/request_form_models/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="8-10 14" |
|||
{!> ../../../docs_src/request_form_models/tutorial001_an.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ non-Annotated |
|||
|
|||
/// tip | "Dica" |
|||
|
|||
Prefira utilizar a versão `Annotated` se possível. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="7-9 13" |
|||
{!> ../../../docs_src/request_form_models/tutorial001.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
O **FastAPI** irá **extrair** as informações para **cada campo** dos **dados do formulário** na requisição e dar para você o modelo Pydantic que você definiu. |
|||
|
|||
## Confira os Documentos |
|||
|
|||
Você pode verificar na UI de documentação em `/docs`: |
|||
|
|||
<div class="screenshot"> |
|||
<img src="/img/tutorial/request-form-models/image01.png"> |
|||
</div> |
|||
|
|||
## Proibir Campos Extras de Formulários |
|||
|
|||
Em alguns casos de uso especiais (provavelmente não muito comum), você pode desejar **restringir** os campos do formulário para aceitar apenas os declarados no modelo Pydantic. E **proibir** qualquer campo **extra**. |
|||
|
|||
/// note | "Nota" |
|||
|
|||
Isso é suportado deste a versão `0.114.0` do FastAPI. 🤓 |
|||
|
|||
/// |
|||
|
|||
Você pode utilizar a configuração de modelo do Pydantic para `proibir` qualquer campo `extra`: |
|||
|
|||
//// tab | Python 3.9+ |
|||
|
|||
```Python hl_lines="12" |
|||
{!> ../../../docs_src/request_form_models/tutorial002_an_py39.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ |
|||
|
|||
```Python hl_lines="11" |
|||
{!> ../../../docs_src/request_form_models/tutorial002_an.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
//// tab | Python 3.8+ non-Annotated |
|||
|
|||
/// tip |
|||
|
|||
Prefira utilizar a versão `Annotated` se possível. |
|||
|
|||
/// |
|||
|
|||
```Python hl_lines="10" |
|||
{!> ../../../docs_src/request_form_models/tutorial002.py!} |
|||
``` |
|||
|
|||
//// |
|||
|
|||
Caso um cliente tente enviar informações adicionais, ele receberá um retorno de **erro**. |
|||
|
|||
Por exemplo, se o cliente tentar enviar os campos de formulário: |
|||
|
|||
* `username`: `Rick` |
|||
* `password`: `Portal Gun` |
|||
* `extra`: `Mr. Poopybutthole` |
|||
|
|||
Ele receberá um retorno de erro informando-o que o campo `extra` não é permitido: |
|||
|
|||
```json |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "extra_forbidden", |
|||
"loc": ["body", "extra"], |
|||
"msg": "Extra inputs are not permitted", |
|||
"input": "Mr. Poopybutthole" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
## Resumo |
|||
|
|||
Você pode utilizar modelos Pydantic para declarar campos de formulários no FastAPI. 😎 |
@ -1,84 +1,28 @@ |
|||
# 项目生成 - 模板 |
|||
|
|||
项目生成器一般都会提供很多初始设置、安全措施、数据库,甚至还准备好了第一个 API 端点,能帮助您快速上手。 |
|||
|
|||
项目生成器的设置通常都很主观,您可以按需更新或修改,但对于您的项目来说,它是非常好的起点。 |
|||
|
|||
## 全栈 FastAPI + PostgreSQL |
|||
|
|||
GitHub:<a href="https://github.com/tiangolo/full-stack-fastapi-postgresql" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-fastapi-postgresql</a> |
|||
|
|||
### 全栈 FastAPI + PostgreSQL - 功能 |
|||
|
|||
* 完整的 **Docker** 集成(基于 Docker) |
|||
* Docker Swarm 开发模式 |
|||
* **Docker Compose** 本地开发集成与优化 |
|||
* **生产可用**的 Python 网络服务器,使用 Uvicorn 或 Gunicorn |
|||
* Python <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">**FastAPI**</a> 后端: |
|||
* * **速度快**:可与 **NodeJS** 和 **Go** 比肩的极高性能(归功于 Starlette 和 Pydantic) |
|||
* **直观**:强大的编辑器支持,处处皆可<abbr title="也叫自动完成、智能感知">自动补全</abbr>,减少调试时间 |
|||
* **简单**:易学、易用,阅读文档所需时间更短 |
|||
* **简短**:代码重复最小化,每次参数声明都可以实现多个功能 |
|||
* **健壮**: 生产级别的代码,还有自动交互文档 |
|||
* **基于标准**:完全兼容并基于 API 开放标准:<a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> 和 <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a> |
|||
* <a href="https://fastapi.tiangolo.com/features/" class="external-link" target="_blank">**更多功能**</a>包括自动验证、序列化、交互文档、OAuth2 JWT 令牌身份验证等 |
|||
* **安全密码**,默认使用密码哈希 |
|||
* **JWT 令牌**身份验证 |
|||
* **SQLAlchemy** 模型(独立于 Flask 扩展,可直接用于 Celery Worker) |
|||
* 基础的用户模型(可按需修改或删除) |
|||
* **Alembic** 迁移 |
|||
* **CORS**(跨域资源共享) |
|||
* **Celery** Worker 可从后端其它部分有选择地导入并使用模型和代码 |
|||
* REST 后端测试基于 Pytest,并与 Docker 集成,可独立于数据库实现完整的 API 交互测试。因为是在 Docker 中运行,每次都可从头构建新的数据存储(使用 ElasticSearch、MongoDB、CouchDB 等数据库,仅测试 API 运行) |
|||
* Python 与 **Jupyter Kernels** 集成,用于远程或 Docker 容器内部开发,使用 Atom Hydrogen 或 Visual Studio Code 的 Jupyter 插件 |
|||
* **Vue** 前端: |
|||
* 由 Vue CLI 生成 |
|||
* **JWT 身份验证**处理 |
|||
* 登录视图 |
|||
* 登录后显示主仪表盘视图 |
|||
* 主仪表盘支持用户创建与编辑 |
|||
* 用户信息编辑 |
|||
* **Vuex** |
|||
* **Vue-router** |
|||
* **Vuetify** 美化组件 |
|||
* **TypeScript** |
|||
* 基于 **Nginx** 的 Docker 服务器(优化了 Vue-router 配置) |
|||
* Docker 多阶段构建,无需保存或提交编译的代码 |
|||
* 在构建时运行前端测试(可禁用) |
|||
* 尽量模块化,开箱即用,但仍可使用 Vue CLI 重新生成或创建所需项目,或复用所需内容 |
|||
* 使用 **PGAdmin** 管理 PostgreSQL 数据库,可轻松替换为 PHPMyAdmin 或 MySQL |
|||
* 使用 **Flower** 监控 Celery 任务 |
|||
* 使用 **Traefik** 处理前后端负载平衡,可把前后端放在同一个域下,按路径分隔,但在不同容器中提供服务 |
|||
* Traefik 集成,包括自动生成 Let's Encrypt **HTTPS** 凭证 |
|||
* GitLab **CI**(持续集成),包括前后端测试 |
|||
|
|||
## 全栈 FastAPI + Couchbase |
|||
|
|||
GitHub:<a href="https://github.com/tiangolo/full-stack-fastapi-couchbase" class="external-link" target="_blank">https://github.com/tiangolo/full-stack-fastapi-couchbase</a> |
|||
|
|||
⚠️ **警告** ⚠️ |
|||
|
|||
如果您想从头开始创建新项目,建议使用以下备选方案。 |
|||
|
|||
例如,项目生成器<a href="https://github.com/tiangolo/full-stack-fastapi-postgresql" class="external-link" target="_blank">全栈 FastAPI + PostgreSQL </a>会更适用,这个项目的维护积极,用的人也多,还包括了所有新功能和改进内容。 |
|||
|
|||
当然,您也可以放心使用这个基于 Couchbase 的生成器,它也能正常使用。就算用它生成项目也没有任何问题(为了更好地满足需求,您可以自行更新这个项目)。 |
|||
|
|||
详见资源仓库中的文档。 |
|||
|
|||
## 全栈 FastAPI + MongoDB |
|||
|
|||
……敬请期待,得看我有没有时间做这个项目。😅 🎉 |
|||
|
|||
## FastAPI + spaCy 机器学习模型 |
|||
|
|||
GitHub:<a href="https://github.com/microsoft/cookiecutter-spacy-fastapi" class="external-link" target="_blank">https://github.com/microsoft/cookiecutter-spacy-fastapi</a> |
|||
|
|||
### FastAPI + spaCy 机器学习模型 - 功能 |
|||
|
|||
* 集成 **spaCy** NER 模型 |
|||
* 内置 **Azure 认知搜索**请求格式 |
|||
* **生产可用**的 Python 网络服务器,使用 Uvicorn 与 Gunicorn |
|||
* 内置 **Azure DevOps** Kubernetes (AKS) CI/CD 开发 |
|||
* **多语**支持,可在项目设置时选择 spaCy 内置的语言 |
|||
* 不仅局限于 spaCy,可**轻松扩展**至其它模型框架(Pytorch、TensorFlow) |
|||
# FastAPI全栈模板 |
|||
|
|||
模板通常带有特定的设置,而且被设计为灵活和可定制的。这允许您根据项目的需求修改和调整它们,使它们成为一个很好的起点。🏁 |
|||
|
|||
您可以使用此模板开始,因为它包含了许多已经为您完成的初始设置、安全性、数据库和一些API端点。 |
|||
|
|||
代码仓: <a href="https://github.com/fastapi/full-stack-fastapi-template" class="external-link" target="_blank">Full Stack FastAPI Template</a> |
|||
|
|||
## FastAPI全栈模板 - 技术栈和特性 |
|||
|
|||
- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) 用于Python后端API. |
|||
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com) 用于Python和SQL数据库的集成(ORM)。 |
|||
- 🔍 [Pydantic](https://docs.pydantic.dev) FastAPI的依赖项之一,用于数据验证和配置管理。 |
|||
- 💾 [PostgreSQL](https://www.postgresql.org) 作为SQL数据库。 |
|||
- 🚀 [React](https://react.dev) 用于前端。 |
|||
- 💃 使用了TypeScript、hooks、Vite和其他一些现代化的前端技术栈。 |
|||
- 🎨 [Chakra UI](https://chakra-ui.com) 用于前端组件。 |
|||
- 🤖 一个自动化生成的前端客户端。 |
|||
- 🧪 Playwright用于端到端测试。 |
|||
- 🦇 支持暗黑主题(Dark mode)。 |
|||
- 🐋 [Docker Compose](https://www.docker.com) 用于开发环境和生产环境。 |
|||
- 🔒 默认使用密码哈希来保证安全。 |
|||
- 🔑 JWT令牌用于权限验证。 |
|||
- 📫 使用邮箱来进行密码恢复。 |
|||
- ✅ 单元测试用了[Pytest](https://pytest.org). |
|||
- 📞 [Traefik](https://traefik.io) 用于反向代理和负载均衡。 |
|||
- 🚢 部署指南(Docker Compose)包含了如何起一个Traefik前端代理来自动化HTTPS认证。 |
|||
- 🏭 CI(持续集成)和 CD(持续部署)基于GitHub Actions。 |
|||
|
@ -0,0 +1,17 @@ |
|||
from typing import Union |
|||
|
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
session_id: str |
|||
fatebook_tracker: Union[str, None] = None |
|||
googall_tracker: Union[str, None] = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Cookies = Cookie()): |
|||
return cookies |
@ -0,0 +1,18 @@ |
|||
from typing import Union |
|||
|
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
from typing_extensions import Annotated |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
session_id: str |
|||
fatebook_tracker: Union[str, None] = None |
|||
googall_tracker: Union[str, None] = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Annotated[Cookies, Cookie()]): |
|||
return cookies |
@ -0,0 +1,17 @@ |
|||
from typing import Annotated |
|||
|
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
session_id: str |
|||
fatebook_tracker: str | None = None |
|||
googall_tracker: str | None = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Annotated[Cookies, Cookie()]): |
|||
return cookies |
@ -0,0 +1,17 @@ |
|||
from typing import Annotated, Union |
|||
|
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
session_id: str |
|||
fatebook_tracker: Union[str, None] = None |
|||
googall_tracker: Union[str, None] = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Annotated[Cookies, Cookie()]): |
|||
return cookies |
@ -0,0 +1,15 @@ |
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
session_id: str |
|||
fatebook_tracker: str | None = None |
|||
googall_tracker: str | None = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Cookies = Cookie()): |
|||
return cookies |
@ -0,0 +1,19 @@ |
|||
from typing import Union |
|||
|
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
session_id: str |
|||
fatebook_tracker: Union[str, None] = None |
|||
googall_tracker: Union[str, None] = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Cookies = Cookie()): |
|||
return cookies |
@ -0,0 +1,20 @@ |
|||
from typing import Union |
|||
|
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
from typing_extensions import Annotated |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
session_id: str |
|||
fatebook_tracker: Union[str, None] = None |
|||
googall_tracker: Union[str, None] = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Annotated[Cookies, Cookie()]): |
|||
return cookies |
@ -0,0 +1,19 @@ |
|||
from typing import Annotated |
|||
|
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
session_id: str |
|||
fatebook_tracker: str | None = None |
|||
googall_tracker: str | None = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Annotated[Cookies, Cookie()]): |
|||
return cookies |
@ -0,0 +1,19 @@ |
|||
from typing import Annotated, Union |
|||
|
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
session_id: str |
|||
fatebook_tracker: Union[str, None] = None |
|||
googall_tracker: Union[str, None] = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Annotated[Cookies, Cookie()]): |
|||
return cookies |
@ -0,0 +1,20 @@ |
|||
from typing import Union |
|||
|
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
session_id: str |
|||
fatebook_tracker: Union[str, None] = None |
|||
googall_tracker: Union[str, None] = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Cookies = Cookie()): |
|||
return cookies |
@ -0,0 +1,21 @@ |
|||
from typing import Union |
|||
|
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
from typing_extensions import Annotated |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
session_id: str |
|||
fatebook_tracker: Union[str, None] = None |
|||
googall_tracker: Union[str, None] = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Annotated[Cookies, Cookie()]): |
|||
return cookies |
@ -0,0 +1,20 @@ |
|||
from typing import Annotated |
|||
|
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
session_id: str |
|||
fatebook_tracker: str | None = None |
|||
googall_tracker: str | None = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Annotated[Cookies, Cookie()]): |
|||
return cookies |
@ -0,0 +1,20 @@ |
|||
from typing import Annotated, Union |
|||
|
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
session_id: str |
|||
fatebook_tracker: Union[str, None] = None |
|||
googall_tracker: Union[str, None] = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Annotated[Cookies, Cookie()]): |
|||
return cookies |
@ -0,0 +1,18 @@ |
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
session_id: str |
|||
fatebook_tracker: str | None = None |
|||
googall_tracker: str | None = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Cookies = Cookie()): |
|||
return cookies |
@ -0,0 +1,17 @@ |
|||
from fastapi import Cookie, FastAPI |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class Cookies(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
session_id: str |
|||
fatebook_tracker: str | None = None |
|||
googall_tracker: str | None = None |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(cookies: Cookies = Cookie()): |
|||
return cookies |
@ -0,0 +1,19 @@ |
|||
from typing import List, Union |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: Union[str, None] = None |
|||
traceparent: Union[str, None] = None |
|||
x_tag: List[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: CommonHeaders = Header()): |
|||
return headers |
@ -0,0 +1,20 @@ |
|||
from typing import List, Union |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
from typing_extensions import Annotated |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: Union[str, None] = None |
|||
traceparent: Union[str, None] = None |
|||
x_tag: List[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: Annotated[CommonHeaders, Header()]): |
|||
return headers |
@ -0,0 +1,19 @@ |
|||
from typing import Annotated |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: str | None = None |
|||
traceparent: str | None = None |
|||
x_tag: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: Annotated[CommonHeaders, Header()]): |
|||
return headers |
@ -0,0 +1,19 @@ |
|||
from typing import Annotated, Union |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: Union[str, None] = None |
|||
traceparent: Union[str, None] = None |
|||
x_tag: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: Annotated[CommonHeaders, Header()]): |
|||
return headers |
@ -0,0 +1,17 @@ |
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: str | None = None |
|||
traceparent: str | None = None |
|||
x_tag: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: CommonHeaders = Header()): |
|||
return headers |
@ -0,0 +1,19 @@ |
|||
from typing import Union |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: Union[str, None] = None |
|||
traceparent: Union[str, None] = None |
|||
x_tag: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: CommonHeaders = Header()): |
|||
return headers |
@ -0,0 +1,21 @@ |
|||
from typing import List, Union |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: Union[str, None] = None |
|||
traceparent: Union[str, None] = None |
|||
x_tag: List[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: CommonHeaders = Header()): |
|||
return headers |
@ -0,0 +1,22 @@ |
|||
from typing import List, Union |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
from typing_extensions import Annotated |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: Union[str, None] = None |
|||
traceparent: Union[str, None] = None |
|||
x_tag: List[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: Annotated[CommonHeaders, Header()]): |
|||
return headers |
@ -0,0 +1,21 @@ |
|||
from typing import Annotated |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: str | None = None |
|||
traceparent: str | None = None |
|||
x_tag: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: Annotated[CommonHeaders, Header()]): |
|||
return headers |
@ -0,0 +1,21 @@ |
|||
from typing import Annotated, Union |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: Union[str, None] = None |
|||
traceparent: Union[str, None] = None |
|||
x_tag: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: Annotated[CommonHeaders, Header()]): |
|||
return headers |
@ -0,0 +1,22 @@ |
|||
from typing import List, Union |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: Union[str, None] = None |
|||
traceparent: Union[str, None] = None |
|||
x_tag: List[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: CommonHeaders = Header()): |
|||
return headers |
@ -0,0 +1,23 @@ |
|||
from typing import List, Union |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
from typing_extensions import Annotated |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: Union[str, None] = None |
|||
traceparent: Union[str, None] = None |
|||
x_tag: List[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: Annotated[CommonHeaders, Header()]): |
|||
return headers |
@ -0,0 +1,22 @@ |
|||
from typing import Annotated |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: str | None = None |
|||
traceparent: str | None = None |
|||
x_tag: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: Annotated[CommonHeaders, Header()]): |
|||
return headers |
@ -0,0 +1,22 @@ |
|||
from typing import Annotated, Union |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: Union[str, None] = None |
|||
traceparent: Union[str, None] = None |
|||
x_tag: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: Annotated[CommonHeaders, Header()]): |
|||
return headers |
@ -0,0 +1,20 @@ |
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: str | None = None |
|||
traceparent: str | None = None |
|||
x_tag: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: CommonHeaders = Header()): |
|||
return headers |
@ -0,0 +1,22 @@ |
|||
from typing import Union |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: Union[str, None] = None |
|||
traceparent: Union[str, None] = None |
|||
x_tag: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: CommonHeaders = Header()): |
|||
return headers |
@ -0,0 +1,19 @@ |
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: str | None = None |
|||
traceparent: str | None = None |
|||
x_tag: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: CommonHeaders = Header()): |
|||
return headers |
@ -0,0 +1,21 @@ |
|||
from typing import Union |
|||
|
|||
from fastapi import FastAPI, Header |
|||
from pydantic import BaseModel |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class CommonHeaders(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
host: str |
|||
save_data: bool |
|||
if_modified_since: Union[str, None] = None |
|||
traceparent: Union[str, None] = None |
|||
x_tag: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(headers: CommonHeaders = Header()): |
|||
return headers |
@ -0,0 +1,19 @@ |
|||
from typing import List |
|||
|
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
from typing_extensions import Literal |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: List[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: FilterParams = Query()): |
|||
return filter_query |
@ -0,0 +1,19 @@ |
|||
from typing import List |
|||
|
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
from typing_extensions import Annotated, Literal |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: List[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: Annotated[FilterParams, Query()]): |
|||
return filter_query |
@ -0,0 +1,18 @@ |
|||
from typing import Annotated, Literal |
|||
|
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: Annotated[FilterParams, Query()]): |
|||
return filter_query |
@ -0,0 +1,17 @@ |
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
from typing_extensions import Annotated, Literal |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: Annotated[FilterParams, Query()]): |
|||
return filter_query |
@ -0,0 +1,18 @@ |
|||
from typing import Literal |
|||
|
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: FilterParams = Query()): |
|||
return filter_query |
@ -0,0 +1,17 @@ |
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
from typing_extensions import Literal |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: FilterParams = Query()): |
|||
return filter_query |
@ -0,0 +1,21 @@ |
|||
from typing import List |
|||
|
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
from typing_extensions import Literal |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: List[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: FilterParams = Query()): |
|||
return filter_query |
@ -0,0 +1,21 @@ |
|||
from typing import List |
|||
|
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
from typing_extensions import Annotated, Literal |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: List[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: Annotated[FilterParams, Query()]): |
|||
return filter_query |
@ -0,0 +1,20 @@ |
|||
from typing import Annotated, Literal |
|||
|
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: Annotated[FilterParams, Query()]): |
|||
return filter_query |
@ -0,0 +1,19 @@ |
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
from typing_extensions import Annotated, Literal |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: Annotated[FilterParams, Query()]): |
|||
return filter_query |
@ -0,0 +1,22 @@ |
|||
from typing import List |
|||
|
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
from typing_extensions import Literal |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: List[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: FilterParams = Query()): |
|||
return filter_query |
@ -0,0 +1,22 @@ |
|||
from typing import List |
|||
|
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
from typing_extensions import Annotated, Literal |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: List[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: Annotated[FilterParams, Query()]): |
|||
return filter_query |
@ -0,0 +1,21 @@ |
|||
from typing import Annotated, Literal |
|||
|
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: Annotated[FilterParams, Query()]): |
|||
return filter_query |
@ -0,0 +1,20 @@ |
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
from typing_extensions import Annotated, Literal |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: Annotated[FilterParams, Query()]): |
|||
return filter_query |
@ -0,0 +1,21 @@ |
|||
from typing import Literal |
|||
|
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: FilterParams = Query()): |
|||
return filter_query |
@ -0,0 +1,20 @@ |
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
from typing_extensions import Literal |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
class Config: |
|||
extra = "forbid" |
|||
|
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: FilterParams = Query()): |
|||
return filter_query |
@ -0,0 +1,20 @@ |
|||
from typing import Literal |
|||
|
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: FilterParams = Query()): |
|||
return filter_query |
@ -0,0 +1,19 @@ |
|||
from fastapi import FastAPI, Query |
|||
from pydantic import BaseModel, Field |
|||
from typing_extensions import Literal |
|||
|
|||
app = FastAPI() |
|||
|
|||
|
|||
class FilterParams(BaseModel): |
|||
model_config = {"extra": "forbid"} |
|||
|
|||
limit: int = Field(100, gt=0, le=100) |
|||
offset: int = Field(0, ge=0) |
|||
order_by: Literal["created_at", "updated_at"] = "created_at" |
|||
tags: list[str] = [] |
|||
|
|||
|
|||
@app.get("/items/") |
|||
async def read_items(filter_query: FilterParams = Query()): |
|||
return filter_query |
@ -0,0 +1,39 @@ |
|||
import subprocess |
|||
import time |
|||
|
|||
import httpx |
|||
from playwright.sync_api import Playwright, sync_playwright |
|||
|
|||
|
|||
# Run playwright codegen to generate the code below, copy paste the sections in run() |
|||
def run(playwright: Playwright) -> None: |
|||
browser = playwright.chromium.launch(headless=False) |
|||
# Update the viewport manually |
|||
context = browser.new_context(viewport={"width": 960, "height": 1080}) |
|||
browser = playwright.chromium.launch(headless=False) |
|||
context = browser.new_context() |
|||
page = context.new_page() |
|||
page.goto("http://localhost:8000/docs") |
|||
page.get_by_role("link", name="/items/").click() |
|||
# Manually add the screenshot |
|||
page.screenshot(path="docs/en/docs/img/tutorial/cookie-param-models/image01.png") |
|||
|
|||
# --------------------- |
|||
context.close() |
|||
browser.close() |
|||
|
|||
|
|||
process = subprocess.Popen( |
|||
["fastapi", "run", "docs_src/cookie_param_models/tutorial001.py"] |
|||
) |
|||
try: |
|||
for _ in range(3): |
|||
try: |
|||
response = httpx.get("http://localhost:8000/docs") |
|||
except httpx.ConnectError: |
|||
time.sleep(1) |
|||
break |
|||
with sync_playwright() as playwright: |
|||
run(playwright) |
|||
finally: |
|||
process.terminate() |
@ -0,0 +1,38 @@ |
|||
import subprocess |
|||
import time |
|||
|
|||
import httpx |
|||
from playwright.sync_api import Playwright, sync_playwright |
|||
|
|||
|
|||
# Run playwright codegen to generate the code below, copy paste the sections in run() |
|||
def run(playwright: Playwright) -> None: |
|||
browser = playwright.chromium.launch(headless=False) |
|||
# Update the viewport manually |
|||
context = browser.new_context(viewport={"width": 960, "height": 1080}) |
|||
page = context.new_page() |
|||
page.goto("http://localhost:8000/docs") |
|||
page.get_by_role("button", name="GET /items/ Read Items").click() |
|||
page.get_by_role("button", name="Try it out").click() |
|||
# Manually add the screenshot |
|||
page.screenshot(path="docs/en/docs/img/tutorial/header-param-models/image01.png") |
|||
|
|||
# --------------------- |
|||
context.close() |
|||
browser.close() |
|||
|
|||
|
|||
process = subprocess.Popen( |
|||
["fastapi", "run", "docs_src/header_param_models/tutorial001.py"] |
|||
) |
|||
try: |
|||
for _ in range(3): |
|||
try: |
|||
response = httpx.get("http://localhost:8000/docs") |
|||
except httpx.ConnectError: |
|||
time.sleep(1) |
|||
break |
|||
with sync_playwright() as playwright: |
|||
run(playwright) |
|||
finally: |
|||
process.terminate() |
@ -0,0 +1,41 @@ |
|||
import subprocess |
|||
import time |
|||
|
|||
import httpx |
|||
from playwright.sync_api import Playwright, sync_playwright |
|||
|
|||
|
|||
# Run playwright codegen to generate the code below, copy paste the sections in run() |
|||
def run(playwright: Playwright) -> None: |
|||
browser = playwright.chromium.launch(headless=False) |
|||
# Update the viewport manually |
|||
context = browser.new_context(viewport={"width": 960, "height": 1080}) |
|||
browser = playwright.chromium.launch(headless=False) |
|||
context = browser.new_context() |
|||
page = context.new_page() |
|||
page.goto("http://localhost:8000/docs") |
|||
page.get_by_role("button", name="GET /items/ Read Items").click() |
|||
page.get_by_role("button", name="Try it out").click() |
|||
page.get_by_role("heading", name="Servers").click() |
|||
# Manually add the screenshot |
|||
page.screenshot(path="docs/en/docs/img/tutorial/query-param-models/image01.png") |
|||
|
|||
# --------------------- |
|||
context.close() |
|||
browser.close() |
|||
|
|||
|
|||
process = subprocess.Popen( |
|||
["fastapi", "run", "docs_src/query_param_models/tutorial001.py"] |
|||
) |
|||
try: |
|||
for _ in range(3): |
|||
try: |
|||
response = httpx.get("http://localhost:8000/docs") |
|||
except httpx.ConnectError: |
|||
time.sleep(1) |
|||
break |
|||
with sync_playwright() as playwright: |
|||
run(playwright) |
|||
finally: |
|||
process.terminate() |
@ -0,0 +1,205 @@ |
|||
import importlib |
|||
|
|||
import pytest |
|||
from dirty_equals import IsDict |
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
|
|||
from tests.utils import needs_py39, needs_py310 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="client", |
|||
params=[ |
|||
"tutorial001", |
|||
pytest.param("tutorial001_py310", marks=needs_py310), |
|||
"tutorial001_an", |
|||
pytest.param("tutorial001_an_py39", marks=needs_py39), |
|||
pytest.param("tutorial001_an_py310", marks=needs_py310), |
|||
], |
|||
) |
|||
def get_client(request: pytest.FixtureRequest): |
|||
mod = importlib.import_module(f"docs_src.cookie_param_models.{request.param}") |
|||
|
|||
client = TestClient(mod.app) |
|||
return client |
|||
|
|||
|
|||
def test_cookie_param_model(client: TestClient): |
|||
with client as c: |
|||
c.cookies.set("session_id", "123") |
|||
c.cookies.set("fatebook_tracker", "456") |
|||
c.cookies.set("googall_tracker", "789") |
|||
response = c.get("/items/") |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"session_id": "123", |
|||
"fatebook_tracker": "456", |
|||
"googall_tracker": "789", |
|||
} |
|||
|
|||
|
|||
def test_cookie_param_model_defaults(client: TestClient): |
|||
with client as c: |
|||
c.cookies.set("session_id", "123") |
|||
response = c.get("/items/") |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"session_id": "123", |
|||
"fatebook_tracker": None, |
|||
"googall_tracker": None, |
|||
} |
|||
|
|||
|
|||
def test_cookie_param_model_invalid(client: TestClient): |
|||
response = client.get("/items/") |
|||
assert response.status_code == 422 |
|||
assert response.json() == snapshot( |
|||
IsDict( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "missing", |
|||
"loc": ["cookie", "session_id"], |
|||
"msg": "Field required", |
|||
"input": {}, |
|||
} |
|||
] |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "value_error.missing", |
|||
"loc": ["cookie", "session_id"], |
|||
"msg": "field required", |
|||
} |
|||
] |
|||
} |
|||
) |
|||
) |
|||
|
|||
|
|||
def test_cookie_param_model_extra(client: TestClient): |
|||
with client as c: |
|||
c.cookies.set("session_id", "123") |
|||
c.cookies.set("extra", "track-me-here-too") |
|||
response = c.get("/items/") |
|||
assert response.status_code == 200 |
|||
assert response.json() == snapshot( |
|||
{"session_id": "123", "fatebook_tracker": None, "googall_tracker": None} |
|||
) |
|||
|
|||
|
|||
def test_openapi_schema(client: TestClient): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/items/": { |
|||
"get": { |
|||
"summary": "Read Items", |
|||
"operationId": "read_items_items__get", |
|||
"parameters": [ |
|||
{ |
|||
"name": "session_id", |
|||
"in": "cookie", |
|||
"required": True, |
|||
"schema": {"type": "string", "title": "Session Id"}, |
|||
}, |
|||
{ |
|||
"name": "fatebook_tracker", |
|||
"in": "cookie", |
|||
"required": False, |
|||
"schema": IsDict( |
|||
{ |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "Fatebook Tracker", |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"type": "string", |
|||
"title": "Fatebook Tracker", |
|||
} |
|||
), |
|||
}, |
|||
{ |
|||
"name": "googall_tracker", |
|||
"in": "cookie", |
|||
"required": False, |
|||
"schema": IsDict( |
|||
{ |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "Googall Tracker", |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"type": "string", |
|||
"title": "Googall Tracker", |
|||
} |
|||
), |
|||
}, |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": {"application/json": {"schema": {}}}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
@ -0,0 +1,233 @@ |
|||
import importlib |
|||
|
|||
import pytest |
|||
from dirty_equals import IsDict |
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
|
|||
from tests.utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="client", |
|||
params=[ |
|||
pytest.param("tutorial002", marks=needs_pydanticv2), |
|||
pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), |
|||
pytest.param("tutorial002_an", marks=needs_pydanticv2), |
|||
pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]), |
|||
pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), |
|||
pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]), |
|||
pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), |
|||
pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]), |
|||
pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]), |
|||
pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), |
|||
], |
|||
) |
|||
def get_client(request: pytest.FixtureRequest): |
|||
mod = importlib.import_module(f"docs_src.cookie_param_models.{request.param}") |
|||
|
|||
client = TestClient(mod.app) |
|||
return client |
|||
|
|||
|
|||
def test_cookie_param_model(client: TestClient): |
|||
with client as c: |
|||
c.cookies.set("session_id", "123") |
|||
c.cookies.set("fatebook_tracker", "456") |
|||
c.cookies.set("googall_tracker", "789") |
|||
response = c.get("/items/") |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"session_id": "123", |
|||
"fatebook_tracker": "456", |
|||
"googall_tracker": "789", |
|||
} |
|||
|
|||
|
|||
def test_cookie_param_model_defaults(client: TestClient): |
|||
with client as c: |
|||
c.cookies.set("session_id", "123") |
|||
response = c.get("/items/") |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"session_id": "123", |
|||
"fatebook_tracker": None, |
|||
"googall_tracker": None, |
|||
} |
|||
|
|||
|
|||
def test_cookie_param_model_invalid(client: TestClient): |
|||
response = client.get("/items/") |
|||
assert response.status_code == 422 |
|||
assert response.json() == snapshot( |
|||
IsDict( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "missing", |
|||
"loc": ["cookie", "session_id"], |
|||
"msg": "Field required", |
|||
"input": {}, |
|||
} |
|||
] |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "value_error.missing", |
|||
"loc": ["cookie", "session_id"], |
|||
"msg": "field required", |
|||
} |
|||
] |
|||
} |
|||
) |
|||
) |
|||
|
|||
|
|||
def test_cookie_param_model_extra(client: TestClient): |
|||
with client as c: |
|||
c.cookies.set("session_id", "123") |
|||
c.cookies.set("extra", "track-me-here-too") |
|||
response = c.get("/items/") |
|||
assert response.status_code == 422 |
|||
assert response.json() == snapshot( |
|||
IsDict( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "extra_forbidden", |
|||
"loc": ["cookie", "extra"], |
|||
"msg": "Extra inputs are not permitted", |
|||
"input": "track-me-here-too", |
|||
} |
|||
] |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "value_error.extra", |
|||
"loc": ["cookie", "extra"], |
|||
"msg": "extra fields not permitted", |
|||
} |
|||
] |
|||
} |
|||
) |
|||
) |
|||
|
|||
|
|||
def test_openapi_schema(client: TestClient): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/items/": { |
|||
"get": { |
|||
"summary": "Read Items", |
|||
"operationId": "read_items_items__get", |
|||
"parameters": [ |
|||
{ |
|||
"name": "session_id", |
|||
"in": "cookie", |
|||
"required": True, |
|||
"schema": {"type": "string", "title": "Session Id"}, |
|||
}, |
|||
{ |
|||
"name": "fatebook_tracker", |
|||
"in": "cookie", |
|||
"required": False, |
|||
"schema": IsDict( |
|||
{ |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "Fatebook Tracker", |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"type": "string", |
|||
"title": "Fatebook Tracker", |
|||
} |
|||
), |
|||
}, |
|||
{ |
|||
"name": "googall_tracker", |
|||
"in": "cookie", |
|||
"required": False, |
|||
"schema": IsDict( |
|||
{ |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "Googall Tracker", |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"type": "string", |
|||
"title": "Googall Tracker", |
|||
} |
|||
), |
|||
}, |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": {"application/json": {"schema": {}}}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
@ -0,0 +1,238 @@ |
|||
import importlib |
|||
|
|||
import pytest |
|||
from dirty_equals import IsDict |
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
|
|||
from tests.utils import needs_py39, needs_py310 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="client", |
|||
params=[ |
|||
"tutorial001", |
|||
pytest.param("tutorial001_py39", marks=needs_py39), |
|||
pytest.param("tutorial001_py310", marks=needs_py310), |
|||
"tutorial001_an", |
|||
pytest.param("tutorial001_an_py39", marks=needs_py39), |
|||
pytest.param("tutorial001_an_py310", marks=needs_py310), |
|||
], |
|||
) |
|||
def get_client(request: pytest.FixtureRequest): |
|||
mod = importlib.import_module(f"docs_src.header_param_models.{request.param}") |
|||
|
|||
client = TestClient(mod.app) |
|||
return client |
|||
|
|||
|
|||
def test_header_param_model(client: TestClient): |
|||
response = client.get( |
|||
"/items/", |
|||
headers=[ |
|||
("save-data", "true"), |
|||
("if-modified-since", "yesterday"), |
|||
("traceparent", "123"), |
|||
("x-tag", "one"), |
|||
("x-tag", "two"), |
|||
], |
|||
) |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"host": "testserver", |
|||
"save_data": True, |
|||
"if_modified_since": "yesterday", |
|||
"traceparent": "123", |
|||
"x_tag": ["one", "two"], |
|||
} |
|||
|
|||
|
|||
def test_header_param_model_defaults(client: TestClient): |
|||
response = client.get("/items/", headers=[("save-data", "true")]) |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"host": "testserver", |
|||
"save_data": True, |
|||
"if_modified_since": None, |
|||
"traceparent": None, |
|||
"x_tag": [], |
|||
} |
|||
|
|||
|
|||
def test_header_param_model_invalid(client: TestClient): |
|||
response = client.get("/items/") |
|||
assert response.status_code == 422 |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
IsDict( |
|||
{ |
|||
"type": "missing", |
|||
"loc": ["header", "save_data"], |
|||
"msg": "Field required", |
|||
"input": { |
|||
"x_tag": [], |
|||
"host": "testserver", |
|||
"accept": "*/*", |
|||
"accept-encoding": "gzip, deflate", |
|||
"connection": "keep-alive", |
|||
"user-agent": "testclient", |
|||
}, |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"type": "value_error.missing", |
|||
"loc": ["header", "save_data"], |
|||
"msg": "field required", |
|||
} |
|||
) |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_header_param_model_extra(client: TestClient): |
|||
response = client.get( |
|||
"/items/", headers=[("save-data", "true"), ("tool", "plumbus")] |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"host": "testserver", |
|||
"save_data": True, |
|||
"if_modified_since": None, |
|||
"traceparent": None, |
|||
"x_tag": [], |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_openapi_schema(client: TestClient): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/items/": { |
|||
"get": { |
|||
"summary": "Read Items", |
|||
"operationId": "read_items_items__get", |
|||
"parameters": [ |
|||
{ |
|||
"name": "host", |
|||
"in": "header", |
|||
"required": True, |
|||
"schema": {"type": "string", "title": "Host"}, |
|||
}, |
|||
{ |
|||
"name": "save_data", |
|||
"in": "header", |
|||
"required": True, |
|||
"schema": {"type": "boolean", "title": "Save Data"}, |
|||
}, |
|||
{ |
|||
"name": "if_modified_since", |
|||
"in": "header", |
|||
"required": False, |
|||
"schema": IsDict( |
|||
{ |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "If Modified Since", |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"type": "string", |
|||
"title": "If Modified Since", |
|||
} |
|||
), |
|||
}, |
|||
{ |
|||
"name": "traceparent", |
|||
"in": "header", |
|||
"required": False, |
|||
"schema": IsDict( |
|||
{ |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "Traceparent", |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"type": "string", |
|||
"title": "Traceparent", |
|||
} |
|||
), |
|||
}, |
|||
{ |
|||
"name": "x_tag", |
|||
"in": "header", |
|||
"required": False, |
|||
"schema": { |
|||
"type": "array", |
|||
"items": {"type": "string"}, |
|||
"default": [], |
|||
"title": "X Tag", |
|||
}, |
|||
}, |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": {"application/json": {"schema": {}}}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
@ -0,0 +1,249 @@ |
|||
import importlib |
|||
|
|||
import pytest |
|||
from dirty_equals import IsDict |
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
|
|||
from tests.utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="client", |
|||
params=[ |
|||
pytest.param("tutorial002", marks=needs_pydanticv2), |
|||
pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), |
|||
pytest.param("tutorial002_an", marks=needs_pydanticv2), |
|||
pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]), |
|||
pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), |
|||
pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]), |
|||
pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), |
|||
pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]), |
|||
pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]), |
|||
pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), |
|||
], |
|||
) |
|||
def get_client(request: pytest.FixtureRequest): |
|||
mod = importlib.import_module(f"docs_src.header_param_models.{request.param}") |
|||
|
|||
client = TestClient(mod.app) |
|||
client.headers.clear() |
|||
return client |
|||
|
|||
|
|||
def test_header_param_model(client: TestClient): |
|||
response = client.get( |
|||
"/items/", |
|||
headers=[ |
|||
("save-data", "true"), |
|||
("if-modified-since", "yesterday"), |
|||
("traceparent", "123"), |
|||
("x-tag", "one"), |
|||
("x-tag", "two"), |
|||
], |
|||
) |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == { |
|||
"host": "testserver", |
|||
"save_data": True, |
|||
"if_modified_since": "yesterday", |
|||
"traceparent": "123", |
|||
"x_tag": ["one", "two"], |
|||
} |
|||
|
|||
|
|||
def test_header_param_model_defaults(client: TestClient): |
|||
response = client.get("/items/", headers=[("save-data", "true")]) |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"host": "testserver", |
|||
"save_data": True, |
|||
"if_modified_since": None, |
|||
"traceparent": None, |
|||
"x_tag": [], |
|||
} |
|||
|
|||
|
|||
def test_header_param_model_invalid(client: TestClient): |
|||
response = client.get("/items/") |
|||
assert response.status_code == 422 |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
IsDict( |
|||
{ |
|||
"type": "missing", |
|||
"loc": ["header", "save_data"], |
|||
"msg": "Field required", |
|||
"input": {"x_tag": [], "host": "testserver"}, |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"type": "value_error.missing", |
|||
"loc": ["header", "save_data"], |
|||
"msg": "field required", |
|||
} |
|||
) |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_header_param_model_extra(client: TestClient): |
|||
response = client.get( |
|||
"/items/", headers=[("save-data", "true"), ("tool", "plumbus")] |
|||
) |
|||
assert response.status_code == 422, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
IsDict( |
|||
{ |
|||
"type": "extra_forbidden", |
|||
"loc": ["header", "tool"], |
|||
"msg": "Extra inputs are not permitted", |
|||
"input": "plumbus", |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"type": "value_error.extra", |
|||
"loc": ["header", "tool"], |
|||
"msg": "extra fields not permitted", |
|||
} |
|||
) |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_openapi_schema(client: TestClient): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/items/": { |
|||
"get": { |
|||
"summary": "Read Items", |
|||
"operationId": "read_items_items__get", |
|||
"parameters": [ |
|||
{ |
|||
"name": "host", |
|||
"in": "header", |
|||
"required": True, |
|||
"schema": {"type": "string", "title": "Host"}, |
|||
}, |
|||
{ |
|||
"name": "save_data", |
|||
"in": "header", |
|||
"required": True, |
|||
"schema": {"type": "boolean", "title": "Save Data"}, |
|||
}, |
|||
{ |
|||
"name": "if_modified_since", |
|||
"in": "header", |
|||
"required": False, |
|||
"schema": IsDict( |
|||
{ |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "If Modified Since", |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"type": "string", |
|||
"title": "If Modified Since", |
|||
} |
|||
), |
|||
}, |
|||
{ |
|||
"name": "traceparent", |
|||
"in": "header", |
|||
"required": False, |
|||
"schema": IsDict( |
|||
{ |
|||
"anyOf": [{"type": "string"}, {"type": "null"}], |
|||
"title": "Traceparent", |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"type": "string", |
|||
"title": "Traceparent", |
|||
} |
|||
), |
|||
}, |
|||
{ |
|||
"name": "x_tag", |
|||
"in": "header", |
|||
"required": False, |
|||
"schema": { |
|||
"type": "array", |
|||
"items": {"type": "string"}, |
|||
"default": [], |
|||
"title": "X Tag", |
|||
}, |
|||
}, |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": {"application/json": {"schema": {}}}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
@ -0,0 +1,260 @@ |
|||
import importlib |
|||
|
|||
import pytest |
|||
from dirty_equals import IsDict |
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
|
|||
from tests.utils import needs_py39, needs_py310 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="client", |
|||
params=[ |
|||
"tutorial001", |
|||
pytest.param("tutorial001_py39", marks=needs_py39), |
|||
pytest.param("tutorial001_py310", marks=needs_py310), |
|||
"tutorial001_an", |
|||
pytest.param("tutorial001_an_py39", marks=needs_py39), |
|||
pytest.param("tutorial001_an_py310", marks=needs_py310), |
|||
], |
|||
) |
|||
def get_client(request: pytest.FixtureRequest): |
|||
mod = importlib.import_module(f"docs_src.query_param_models.{request.param}") |
|||
|
|||
client = TestClient(mod.app) |
|||
return client |
|||
|
|||
|
|||
def test_query_param_model(client: TestClient): |
|||
response = client.get( |
|||
"/items/", |
|||
params={ |
|||
"limit": 10, |
|||
"offset": 5, |
|||
"order_by": "updated_at", |
|||
"tags": ["tag1", "tag2"], |
|||
}, |
|||
) |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"limit": 10, |
|||
"offset": 5, |
|||
"order_by": "updated_at", |
|||
"tags": ["tag1", "tag2"], |
|||
} |
|||
|
|||
|
|||
def test_query_param_model_defaults(client: TestClient): |
|||
response = client.get("/items/") |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"limit": 100, |
|||
"offset": 0, |
|||
"order_by": "created_at", |
|||
"tags": [], |
|||
} |
|||
|
|||
|
|||
def test_query_param_model_invalid(client: TestClient): |
|||
response = client.get( |
|||
"/items/", |
|||
params={ |
|||
"limit": 150, |
|||
"offset": -1, |
|||
"order_by": "invalid", |
|||
}, |
|||
) |
|||
assert response.status_code == 422 |
|||
assert response.json() == snapshot( |
|||
IsDict( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "less_than_equal", |
|||
"loc": ["query", "limit"], |
|||
"msg": "Input should be less than or equal to 100", |
|||
"input": "150", |
|||
"ctx": {"le": 100}, |
|||
}, |
|||
{ |
|||
"type": "greater_than_equal", |
|||
"loc": ["query", "offset"], |
|||
"msg": "Input should be greater than or equal to 0", |
|||
"input": "-1", |
|||
"ctx": {"ge": 0}, |
|||
}, |
|||
{ |
|||
"type": "literal_error", |
|||
"loc": ["query", "order_by"], |
|||
"msg": "Input should be 'created_at' or 'updated_at'", |
|||
"input": "invalid", |
|||
"ctx": {"expected": "'created_at' or 'updated_at'"}, |
|||
}, |
|||
] |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "value_error.number.not_le", |
|||
"loc": ["query", "limit"], |
|||
"msg": "ensure this value is less than or equal to 100", |
|||
"ctx": {"limit_value": 100}, |
|||
}, |
|||
{ |
|||
"type": "value_error.number.not_ge", |
|||
"loc": ["query", "offset"], |
|||
"msg": "ensure this value is greater than or equal to 0", |
|||
"ctx": {"limit_value": 0}, |
|||
}, |
|||
{ |
|||
"type": "value_error.const", |
|||
"loc": ["query", "order_by"], |
|||
"msg": "unexpected value; permitted: 'created_at', 'updated_at'", |
|||
"ctx": { |
|||
"given": "invalid", |
|||
"permitted": ["created_at", "updated_at"], |
|||
}, |
|||
}, |
|||
] |
|||
} |
|||
) |
|||
) |
|||
|
|||
|
|||
def test_query_param_model_extra(client: TestClient): |
|||
response = client.get( |
|||
"/items/", |
|||
params={ |
|||
"limit": 10, |
|||
"offset": 5, |
|||
"order_by": "updated_at", |
|||
"tags": ["tag1", "tag2"], |
|||
"tool": "plumbus", |
|||
}, |
|||
) |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"limit": 10, |
|||
"offset": 5, |
|||
"order_by": "updated_at", |
|||
"tags": ["tag1", "tag2"], |
|||
} |
|||
|
|||
|
|||
def test_openapi_schema(client: TestClient): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/items/": { |
|||
"get": { |
|||
"summary": "Read Items", |
|||
"operationId": "read_items_items__get", |
|||
"parameters": [ |
|||
{ |
|||
"name": "limit", |
|||
"in": "query", |
|||
"required": False, |
|||
"schema": { |
|||
"type": "integer", |
|||
"maximum": 100, |
|||
"exclusiveMinimum": 0, |
|||
"default": 100, |
|||
"title": "Limit", |
|||
}, |
|||
}, |
|||
{ |
|||
"name": "offset", |
|||
"in": "query", |
|||
"required": False, |
|||
"schema": { |
|||
"type": "integer", |
|||
"minimum": 0, |
|||
"default": 0, |
|||
"title": "Offset", |
|||
}, |
|||
}, |
|||
{ |
|||
"name": "order_by", |
|||
"in": "query", |
|||
"required": False, |
|||
"schema": { |
|||
"enum": ["created_at", "updated_at"], |
|||
"type": "string", |
|||
"default": "created_at", |
|||
"title": "Order By", |
|||
}, |
|||
}, |
|||
{ |
|||
"name": "tags", |
|||
"in": "query", |
|||
"required": False, |
|||
"schema": { |
|||
"type": "array", |
|||
"items": {"type": "string"}, |
|||
"default": [], |
|||
"title": "Tags", |
|||
}, |
|||
}, |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": {"application/json": {"schema": {}}}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
@ -0,0 +1,282 @@ |
|||
import importlib |
|||
|
|||
import pytest |
|||
from dirty_equals import IsDict |
|||
from fastapi.testclient import TestClient |
|||
from inline_snapshot import snapshot |
|||
|
|||
from tests.utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
name="client", |
|||
params=[ |
|||
pytest.param("tutorial002", marks=needs_pydanticv2), |
|||
pytest.param("tutorial002_py39", marks=[needs_py39, needs_pydanticv2]), |
|||
pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), |
|||
pytest.param("tutorial002_an", marks=needs_pydanticv2), |
|||
pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]), |
|||
pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), |
|||
pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]), |
|||
pytest.param("tutorial002_pv1_py39", marks=[needs_py39, needs_pydanticv1]), |
|||
pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), |
|||
pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]), |
|||
pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]), |
|||
pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), |
|||
], |
|||
) |
|||
def get_client(request: pytest.FixtureRequest): |
|||
mod = importlib.import_module(f"docs_src.query_param_models.{request.param}") |
|||
|
|||
client = TestClient(mod.app) |
|||
return client |
|||
|
|||
|
|||
def test_query_param_model(client: TestClient): |
|||
response = client.get( |
|||
"/items/", |
|||
params={ |
|||
"limit": 10, |
|||
"offset": 5, |
|||
"order_by": "updated_at", |
|||
"tags": ["tag1", "tag2"], |
|||
}, |
|||
) |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"limit": 10, |
|||
"offset": 5, |
|||
"order_by": "updated_at", |
|||
"tags": ["tag1", "tag2"], |
|||
} |
|||
|
|||
|
|||
def test_query_param_model_defaults(client: TestClient): |
|||
response = client.get("/items/") |
|||
assert response.status_code == 200 |
|||
assert response.json() == { |
|||
"limit": 100, |
|||
"offset": 0, |
|||
"order_by": "created_at", |
|||
"tags": [], |
|||
} |
|||
|
|||
|
|||
def test_query_param_model_invalid(client: TestClient): |
|||
response = client.get( |
|||
"/items/", |
|||
params={ |
|||
"limit": 150, |
|||
"offset": -1, |
|||
"order_by": "invalid", |
|||
}, |
|||
) |
|||
assert response.status_code == 422 |
|||
assert response.json() == snapshot( |
|||
IsDict( |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "less_than_equal", |
|||
"loc": ["query", "limit"], |
|||
"msg": "Input should be less than or equal to 100", |
|||
"input": "150", |
|||
"ctx": {"le": 100}, |
|||
}, |
|||
{ |
|||
"type": "greater_than_equal", |
|||
"loc": ["query", "offset"], |
|||
"msg": "Input should be greater than or equal to 0", |
|||
"input": "-1", |
|||
"ctx": {"ge": 0}, |
|||
}, |
|||
{ |
|||
"type": "literal_error", |
|||
"loc": ["query", "order_by"], |
|||
"msg": "Input should be 'created_at' or 'updated_at'", |
|||
"input": "invalid", |
|||
"ctx": {"expected": "'created_at' or 'updated_at'"}, |
|||
}, |
|||
] |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"type": "value_error.number.not_le", |
|||
"loc": ["query", "limit"], |
|||
"msg": "ensure this value is less than or equal to 100", |
|||
"ctx": {"limit_value": 100}, |
|||
}, |
|||
{ |
|||
"type": "value_error.number.not_ge", |
|||
"loc": ["query", "offset"], |
|||
"msg": "ensure this value is greater than or equal to 0", |
|||
"ctx": {"limit_value": 0}, |
|||
}, |
|||
{ |
|||
"type": "value_error.const", |
|||
"loc": ["query", "order_by"], |
|||
"msg": "unexpected value; permitted: 'created_at', 'updated_at'", |
|||
"ctx": { |
|||
"given": "invalid", |
|||
"permitted": ["created_at", "updated_at"], |
|||
}, |
|||
}, |
|||
] |
|||
} |
|||
) |
|||
) |
|||
|
|||
|
|||
def test_query_param_model_extra(client: TestClient): |
|||
response = client.get( |
|||
"/items/", |
|||
params={ |
|||
"limit": 10, |
|||
"offset": 5, |
|||
"order_by": "updated_at", |
|||
"tags": ["tag1", "tag2"], |
|||
"tool": "plumbus", |
|||
}, |
|||
) |
|||
assert response.status_code == 422 |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"detail": [ |
|||
IsDict( |
|||
{ |
|||
"type": "extra_forbidden", |
|||
"loc": ["query", "tool"], |
|||
"msg": "Extra inputs are not permitted", |
|||
"input": "plumbus", |
|||
} |
|||
) |
|||
| IsDict( |
|||
# TODO: remove when deprecating Pydantic v1 |
|||
{ |
|||
"type": "value_error.extra", |
|||
"loc": ["query", "tool"], |
|||
"msg": "extra fields not permitted", |
|||
} |
|||
) |
|||
] |
|||
} |
|||
) |
|||
|
|||
|
|||
def test_openapi_schema(client: TestClient): |
|||
response = client.get("/openapi.json") |
|||
assert response.status_code == 200, response.text |
|||
assert response.json() == snapshot( |
|||
{ |
|||
"openapi": "3.1.0", |
|||
"info": {"title": "FastAPI", "version": "0.1.0"}, |
|||
"paths": { |
|||
"/items/": { |
|||
"get": { |
|||
"summary": "Read Items", |
|||
"operationId": "read_items_items__get", |
|||
"parameters": [ |
|||
{ |
|||
"name": "limit", |
|||
"in": "query", |
|||
"required": False, |
|||
"schema": { |
|||
"type": "integer", |
|||
"maximum": 100, |
|||
"exclusiveMinimum": 0, |
|||
"default": 100, |
|||
"title": "Limit", |
|||
}, |
|||
}, |
|||
{ |
|||
"name": "offset", |
|||
"in": "query", |
|||
"required": False, |
|||
"schema": { |
|||
"type": "integer", |
|||
"minimum": 0, |
|||
"default": 0, |
|||
"title": "Offset", |
|||
}, |
|||
}, |
|||
{ |
|||
"name": "order_by", |
|||
"in": "query", |
|||
"required": False, |
|||
"schema": { |
|||
"enum": ["created_at", "updated_at"], |
|||
"type": "string", |
|||
"default": "created_at", |
|||
"title": "Order By", |
|||
}, |
|||
}, |
|||
{ |
|||
"name": "tags", |
|||
"in": "query", |
|||
"required": False, |
|||
"schema": { |
|||
"type": "array", |
|||
"items": {"type": "string"}, |
|||
"default": [], |
|||
"title": "Tags", |
|||
}, |
|||
}, |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "Successful Response", |
|||
"content": {"application/json": {"schema": {}}}, |
|||
}, |
|||
"422": { |
|||
"description": "Validation Error", |
|||
"content": { |
|||
"application/json": { |
|||
"schema": { |
|||
"$ref": "#/components/schemas/HTTPValidationError" |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
}, |
|||
"components": { |
|||
"schemas": { |
|||
"HTTPValidationError": { |
|||
"properties": { |
|||
"detail": { |
|||
"items": { |
|||
"$ref": "#/components/schemas/ValidationError" |
|||
}, |
|||
"type": "array", |
|||
"title": "Detail", |
|||
} |
|||
}, |
|||
"type": "object", |
|||
"title": "HTTPValidationError", |
|||
}, |
|||
"ValidationError": { |
|||
"properties": { |
|||
"loc": { |
|||
"items": { |
|||
"anyOf": [{"type": "string"}, {"type": "integer"}] |
|||
}, |
|||
"type": "array", |
|||
"title": "Location", |
|||
}, |
|||
"msg": {"type": "string", "title": "Message"}, |
|||
"type": {"type": "string", "title": "Error Type"}, |
|||
}, |
|||
"type": "object", |
|||
"required": ["loc", "msg", "type"], |
|||
"title": "ValidationError", |
|||
}, |
|||
} |
|||
}, |
|||
} |
|||
) |
Loading…
Reference in new issue