Browse Source

Merge branch 'master' into ka-GE_translation-init

pull/11897/head
David Kadaria 7 months ago
committed by GitHub
parent
commit
b8b11b8259
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/issue-manager.yml
  2. 2
      .pre-commit-config.yaml
  3. 2
      docs/en/docs/environment-variables.md
  4. 20
      docs/en/docs/release-notes.md
  5. 298
      docs/pt/docs/environment-variables.md
  6. 115
      docs/pt/docs/tutorial/debugging.md
  7. 249
      docs/pt/docs/tutorial/testing.md
  8. 844
      docs/pt/docs/virtual-environments.md
  9. 2
      fastapi/__init__.py
  10. 6
      fastapi/_compat.py
  11. 4
      fastapi/dependencies/utils.py
  12. 4
      pyproject.toml
  13. 4
      requirements-tests.txt
  14. 16
      tests/test_compat.py

2
.github/workflows/issue-manager.yml

@ -27,7 +27,7 @@ jobs:
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: tiangolo/[email protected].0
- uses: tiangolo/[email protected].1
with:
token: ${{ secrets.GITHUB_TOKEN }}
config: >

2
.pre-commit-config.yaml

@ -14,7 +14,7 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.3
rev: v0.6.4
hooks:
- id: ruff
args:

2
docs/en/docs/environment-variables.md

@ -243,8 +243,6 @@ This way, when you type `python` in the terminal, the system will find the Pytho
////
This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one.
So, if you type:
<div class="termy">

20
docs/en/docs/release-notes.md

@ -7,12 +7,32 @@ hide:
## Latest Changes
### Internal
* ➕ Add inline-snapshot for tests. PR [#12189](https://github.com/fastapi/fastapi/pull/12189) by [@tiangolo](https://github.com/tiangolo).
## 0.114.1
### Refactors
* ⚡️ Improve performance in request body parsing with a cache for internal model fields. PR [#12184](https://github.com/fastapi/fastapi/pull/12184) by [@tiangolo](https://github.com/tiangolo).
### Docs
* 📝 Remove duplicate line in docs for `docs/en/docs/environment-variables.md`. PR [#12169](https://github.com/fastapi/fastapi/pull/12169) by [@prometek](https://github.com/prometek).
### Translations
* 🌐 Add Portuguese translation for `docs/pt/docs/virtual-environments.md`. PR [#12163](https://github.com/fastapi/fastapi/pull/12163) by [@marcelomarkus](https://github.com/marcelomarkus).
* 🌐 Add Portuguese translation for `docs/pt/docs/environment-variables.md`. PR [#12162](https://github.com/fastapi/fastapi/pull/12162) by [@marcelomarkus](https://github.com/marcelomarkus).
* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/testing.md`. PR [#12164](https://github.com/fastapi/fastapi/pull/12164) by [@marcelomarkus](https://github.com/marcelomarkus).
* 🌐 Add Portuguese translation for `docs/pt/docs/tutorial/debugging.md`. PR [#12165](https://github.com/fastapi/fastapi/pull/12165) by [@marcelomarkus](https://github.com/marcelomarkus).
* 🌐 Add Korean translation for `docs/ko/docs/project-generation.md`. PR [#12157](https://github.com/fastapi/fastapi/pull/12157) by [@BORA040126](https://github.com/BORA040126).
### Internal
* ⬆ Bump tiangolo/issue-manager from 0.5.0 to 0.5.1. PR [#12173](https://github.com/fastapi/fastapi/pull/12173) by [@dependabot[bot]](https://github.com/apps/dependabot).
* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#12176](https://github.com/fastapi/fastapi/pull/12176) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci).
* 👷 Update `issue-manager.yml`. PR [#12159](https://github.com/fastapi/fastapi/pull/12159) by [@tiangolo](https://github.com/tiangolo).
* ✏️ Fix typo in `fastapi/params.py`. PR [#12143](https://github.com/fastapi/fastapi/pull/12143) by [@surreal30](https://github.com/surreal30).

298
docs/pt/docs/environment-variables.md

@ -0,0 +1,298 @@
# Variáveis de Ambiente
/// tip | "Dica"
Se você já sabe o que são "variáveis de ambiente" e como usá-las, pode pular esta seção.
///
Uma variável de ambiente (também conhecida como "**env var**") é uma variável que existe **fora** do código Python, no **sistema operacional**, e pode ser lida pelo seu código Python (ou por outros programas também).
Variáveis de ambiente podem ser úteis para lidar com **configurações** do aplicativo, como parte da **instalação** do Python, etc.
## Criar e Usar Variáveis de Ambiente
Você pode **criar** e usar variáveis de ambiente no **shell (terminal)**, sem precisar do Python:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
// Você pode criar uma variável de ambiente MY_NAME com
$ export MY_NAME="Wade Wilson"
// Então você pode usá-la com outros programas, como
$ echo "Hello $MY_NAME"
Hello Wade Wilson
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// Criar uma variável de ambiente MY_NAME
$ $Env:MY_NAME = "Wade Wilson"
// Usá-la com outros programas, como
$ echo "Hello $Env:MY_NAME"
Hello Wade Wilson
```
</div>
////
## Ler Variáveis de Ambiente no Python
Você também pode criar variáveis de ambiente **fora** do Python, no terminal (ou com qualquer outro método) e depois **lê-las no Python**.
Por exemplo, você poderia ter um arquivo `main.py` com:
```Python hl_lines="3"
import os
name = os.getenv("MY_NAME", "World")
print(f"Hello {name} from Python")
```
/// tip | "Dica"
O segundo argumento para <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> é o valor padrão a ser retornado.
Se não for fornecido, é `None` por padrão, Aqui fornecemos `"World"` como o valor padrão a ser usado.
///
Então você poderia chamar esse programa Python:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
// Aqui ainda não definimos a variável de ambiente
$ python main.py
// Como não definimos a variável de ambiente, obtemos o valor padrão
Hello World from Python
// Mas se criarmos uma variável de ambiente primeiro
$ export MY_NAME="Wade Wilson"
// E então chamar o programa novamente
$ python main.py
// Agora ele pode ler a variável de ambiente
Hello Wade Wilson from Python
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// Aqui ainda não definimos a variável de ambiente
$ python main.py
// Como não definimos a variável de ambiente, obtemos o valor padrão
Hello World from Python
// Mas se criarmos uma variável de ambiente primeiro
$ $Env:MY_NAME = "Wade Wilson"
// E então chamar o programa novamente
$ python main.py
// Agora ele pode ler a variável de ambiente
Hello Wade Wilson from Python
```
</div>
////
Como as variáveis de ambiente podem ser definidas fora do código, mas podem ser lidas pelo código e não precisam ser armazenadas (com versão no `git`) com o restante dos arquivos, é comum usá-las para configurações ou **definições**.
Você também pode criar uma variável de ambiente apenas para uma **invocação específica do programa**, que só está disponível para aquele programa e apenas pela duração dele.
Para fazer isso, crie-a na mesma linha, antes do próprio programa:
<div class="termy">
```console
// Criar uma variável de ambiente MY_NAME para esta chamada de programa
$ MY_NAME="Wade Wilson" python main.py
// Agora ele pode ler a variável de ambiente
Hello Wade Wilson from Python
// A variável de ambiente não existe mais depois
$ python main.py
Hello World from Python
```
</div>
/// tip | "Dica"
Você pode ler mais sobre isso em <a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: Config</a>.
///
## Tipos e Validação
Essas variáveis de ambiente só podem lidar com **strings de texto**, pois são externas ao Python e precisam ser compatíveis com outros programas e com o resto do sistema (e até mesmo com diferentes sistemas operacionais, como Linux, Windows, macOS).
Isso significa que **qualquer valor** lido em Python de uma variável de ambiente **será uma `str`**, e qualquer conversão para um tipo diferente ou qualquer validação precisa ser feita no código.
Você aprenderá mais sobre como usar variáveis de ambiente para lidar com **configurações do aplicativo** no [Guia do Usuário Avançado - Configurações e Variáveis de Ambiente](./advanced/settings.md){.internal-link target=_blank}.
## Variável de Ambiente `PATH`
Existe uma variável de ambiente **especial** chamada **`PATH`** que é usada pelos sistemas operacionais (Linux, macOS, Windows) para encontrar programas para executar.
O valor da variável `PATH` é uma longa string composta por diretórios separados por dois pontos `:` no Linux e macOS, e por ponto e vírgula `;` no Windows.
Por exemplo, a variável de ambiente `PATH` poderia ter esta aparência:
//// tab | Linux, macOS
```plaintext
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
```
Isso significa que o sistema deve procurar programas nos diretórios:
* `/usr/local/bin`
* `/usr/bin`
* `/bin`
* `/usr/sbin`
* `/sbin`
////
//// tab | Windows
```plaintext
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32
```
Isso significa que o sistema deve procurar programas nos diretórios:
* `C:\Program Files\Python312\Scripts`
* `C:\Program Files\Python312`
* `C:\Windows\System32`
////
Quando você digita um **comando** no terminal, o sistema operacional **procura** o programa em **cada um dos diretórios** listados na variável de ambiente `PATH`.
Por exemplo, quando você digita `python` no terminal, o sistema operacional procura um programa chamado `python` no **primeiro diretório** dessa lista.
Se ele o encontrar, então ele o **usará**. Caso contrário, ele continua procurando nos **outros diretórios**.
### Instalando o Python e Atualizando o `PATH`
Durante a instalação do Python, você pode ser questionado sobre a atualização da variável de ambiente `PATH`.
//// tab | Linux, macOS
Vamos supor que você instale o Python e ele fique em um diretório `/opt/custompython/bin`.
Se você concordar em atualizar a variável de ambiente `PATH`, o instalador adicionará `/opt/custompython/bin` para a variável de ambiente `PATH`.
Poderia parecer assim:
```plaintext
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin
```
Dessa forma, ao digitar `python` no terminal, o sistema encontrará o programa Python em `/opt/custompython/bin` (último diretório) e o utilizará.
////
//// tab | Windows
Digamos que você instala o Python e ele acaba em um diretório `C:\opt\custompython\bin`.
Se você disser sim para atualizar a variável de ambiente `PATH`, o instalador adicionará `C:\opt\custompython\bin` à variável de ambiente `PATH`.
```plaintext
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin
```
Dessa forma, quando você digitar `python` no terminal, o sistema encontrará o programa Python em `C:\opt\custompython\bin` (o último diretório) e o utilizará.
////
Então, se você digitar:
<div class="termy">
```console
$ python
```
</div>
//// tab | Linux, macOS
O sistema **encontrará** o programa `python` em `/opt/custompython/bin` e o executará.
Seria aproximadamente equivalente a digitar:
<div class="termy">
```console
$ /opt/custompython/bin/python
```
</div>
////
//// tab | Windows
O sistema **encontrará** o programa `python` em `C:\opt\custompython\bin\python` e o executará.
Seria aproximadamente equivalente a digitar:
<div class="termy">
```console
$ C:\opt\custompython\bin\python
```
</div>
////
Essas informações serão úteis ao aprender sobre [Ambientes Virtuais](virtual-environments.md){.internal-link target=_blank}.
## Conclusão
Com isso, você deve ter uma compreensão básica do que são **variáveis ​​de ambiente** e como usá-las em Python.
Você também pode ler mais sobre elas na <a href="https://en.wikipedia.org/wiki/Environment_variable" class="external-link" target="_blank">Wikipedia para Variáveis ​​de Ambiente</a>.
Em muitos casos, não é muito óbvio como as variáveis ​​de ambiente seriam úteis e aplicáveis ​​imediatamente. Mas elas continuam aparecendo em muitos cenários diferentes quando você está desenvolvendo, então é bom saber sobre elas.
Por exemplo, você precisará dessas informações na próxima seção, sobre [Ambientes Virtuais](virtual-environments.md).

115
docs/pt/docs/tutorial/debugging.md

@ -0,0 +1,115 @@
# Depuração
Você pode conectar o depurador no seu editor, por exemplo, com o Visual Studio Code ou PyCharm.
## Chamar `uvicorn`
Em seu aplicativo FastAPI, importe e execute `uvicorn` diretamente:
```Python hl_lines="1 15"
{!../../../docs_src/debugging/tutorial001.py!}
```
### Sobre `__name__ == "__main__"`
O objetivo principal de `__name__ == "__main__"` é ter algum código que seja executado quando seu arquivo for chamado com:
<div class="termy">
```console
$ python myapp.py
```
</div>
mas não é chamado quando outro arquivo o importa, como em:
```Python
from myapp import app
```
#### Mais detalhes
Digamos que seu arquivo se chama `myapp.py`.
Se você executá-lo com:
<div class="termy">
```console
$ python myapp.py
```
</div>
então a variável interna `__name__` no seu arquivo, criada automaticamente pelo Python, terá como valor a string `"__main__"`.
Então, a seção:
```Python
uvicorn.run(app, host="0.0.0.0", port=8000)
```
vai executar.
---
Isso não acontecerá se você importar esse módulo (arquivo).
Então, se você tiver outro arquivo `importer.py` com:
```Python
from myapp import app
# Mais um pouco de código
```
nesse caso, a variável criada automaticamente dentro de `myapp.py` não terá a variável `__name__` com o valor `"__main__"`.
Então, a linha:
```Python
uvicorn.run(app, host="0.0.0.0", port=8000)
```
não será executada.
/// info | "Informação"
Para mais informações, consulte <a href="https://docs.python.org/3/library/__main__.html" class="external-link" target="_blank">a documentação oficial do Python</a>.
///
## Execute seu código com seu depurador
Como você está executando o servidor Uvicorn diretamente do seu código, você pode chamar seu programa Python (seu aplicativo FastAPI) diretamente do depurador.
---
Por exemplo, no Visual Studio Code, você pode:
* Ir para o painel "Debug".
* "Add configuration...".
* Selecionar "Python"
* Executar o depurador com a opção "`Python: Current File (Integrated Terminal)`".
Em seguida, ele iniciará o servidor com seu código **FastAPI**, parará em seus pontos de interrupção, etc.
Veja como pode parecer:
<img src="/img/tutorial/debugging/image01.png">
---
Se você usar o Pycharm, você pode:
* Abrir o menu "Executar".
* Selecionar a opção "Depurar...".
* Então um menu de contexto aparece.
* Selecionar o arquivo para depurar (neste caso, `main.py`).
Em seguida, ele iniciará o servidor com seu código **FastAPI**, parará em seus pontos de interrupção, etc.
Veja como pode parecer:
<img src="/img/tutorial/debugging/image02.png">

249
docs/pt/docs/tutorial/testing.md

@ -0,0 +1,249 @@
# Testando
Graças ao <a href="https://www.starlette.io/testclient/" class="external-link" target="_blank">Starlette</a>, testar aplicativos **FastAPI** é fácil e agradável.
Ele é baseado no <a href="https://www.python-httpx.org" class="external-link" target="_blank">HTTPX</a>, que por sua vez é projetado com base em Requests, por isso é muito familiar e intuitivo.
Com ele, você pode usar o <a href="https://docs.pytest.org/" class="external-link" target="_blank">pytest</a> diretamente com **FastAPI**.
## Usando `TestClient`
/// info | "Informação"
Para usar o `TestClient`, primeiro instale o <a href="https://www.python-httpx.org" class="external-link" target="_blank">`httpx`</a>.
Certifique-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e instalá-lo, por exemplo:
```console
$ pip install httpx
```
///
Importe `TestClient`.
Crie um `TestClient` passando seu aplicativo **FastAPI** para ele.
Crie funções com um nome que comece com `test_` (essa é a convenção padrão do `pytest`).
Use o objeto `TestClient` da mesma forma que você faz com `httpx`.
Escreva instruções `assert` simples com as expressões Python padrão que você precisa verificar (novamente, `pytest` padrão).
```Python hl_lines="2 12 15-18"
{!../../../docs_src/app_testing/tutorial001.py!}
```
/// tip | "Dica"
Observe que as funções de teste são `def` normais, não `async def`.
E as chamadas para o cliente também são chamadas normais, não usando `await`.
Isso permite que você use `pytest` diretamente sem complicações.
///
/// note | "Detalhes técnicos"
Você também pode usar `from starlette.testclient import TestClient`.
**FastAPI** fornece o mesmo `starlette.testclient` que `fastapi.testclient` apenas como uma conveniência para você, o desenvolvedor. Mas ele vem diretamente da Starlette.
///
/// tip | "Dica"
Se você quiser chamar funções `async` em seus testes além de enviar solicitações ao seu aplicativo FastAPI (por exemplo, funções de banco de dados assíncronas), dê uma olhada em [Testes assíncronos](../advanced/async-tests.md){.internal-link target=_blank} no tutorial avançado.
///
## Separando testes
Em uma aplicação real, você provavelmente teria seus testes em um arquivo diferente.
E seu aplicativo **FastAPI** também pode ser composto de vários arquivos/módulos, etc.
### Arquivo do aplicativo **FastAPI**
Digamos que você tenha uma estrutura de arquivo conforme descrito em [Aplicativos maiores](bigger-applications.md){.internal-link target=_blank}:
```
.
├── app
│   ├── __init__.py
│   └── main.py
```
No arquivo `main.py` você tem seu aplicativo **FastAPI**:
```Python
{!../../../docs_src/app_testing/main.py!}
```
### Arquivo de teste
Então você poderia ter um arquivo `test_main.py` com seus testes. Ele poderia estar no mesmo pacote Python (o mesmo diretório com um arquivo `__init__.py`):
``` hl_lines="5"
.
├── app
│   ├── __init__.py
│   ├── main.py
│   └── test_main.py
```
Como esse arquivo está no mesmo pacote, você pode usar importações relativas para importar o objeto `app` do módulo `main` (`main.py`):
```Python hl_lines="3"
{!../../../docs_src/app_testing/test_main.py!}
```
...e ter o código para os testes como antes.
## Testando: exemplo estendido
Agora vamos estender este exemplo e adicionar mais detalhes para ver como testar diferentes partes.
### Arquivo de aplicativo **FastAPI** estendido
Vamos continuar com a mesma estrutura de arquivo de antes:
```
.
├── app
│   ├── __init__.py
│   ├── main.py
│   └── test_main.py
```
Digamos que agora o arquivo `main.py` com seu aplicativo **FastAPI** tenha algumas outras **operações de rotas**.
Ele tem uma operação `GET` que pode retornar um erro.
Ele tem uma operação `POST` que pode retornar vários erros.
Ambas as *operações de rotas* requerem um cabeçalho `X-Token`.
//// tab | Python 3.10+
```Python
{!> ../../../docs_src/app_testing/app_b_an_py310/main.py!}
```
////
//// tab | Python 3.9+
```Python
{!> ../../../docs_src/app_testing/app_b_an_py39/main.py!}
```
////
//// tab | Python 3.8+
```Python
{!> ../../../docs_src/app_testing/app_b_an/main.py!}
```
////
//// tab | Python 3.10+ non-Annotated
/// tip | "Dica"
Prefira usar a versão `Annotated` se possível.
///
```Python
{!> ../../../docs_src/app_testing/app_b_py310/main.py!}
```
////
//// tab | Python 3.8+ non-Annotated
/// tip | "Dica"
Prefira usar a versão `Annotated` se possível.
///
```Python
{!> ../../../docs_src/app_testing/app_b/main.py!}
```
////
### Arquivo de teste estendido
Você pode então atualizar `test_main.py` com os testes estendidos:
```Python
{!> ../../../docs_src/app_testing/app_b/test_main.py!}
```
Sempre que você precisar que o cliente passe informações na requisição e não souber como, você pode pesquisar (no Google) como fazer isso no `httpx`, ou até mesmo como fazer isso com `requests`, já que o design do HTTPX é baseado no design do Requests.
Depois é só fazer o mesmo nos seus testes.
Por exemplo:
* Para passar um parâmetro *path* ou *query*, adicione-o à própria URL.
* Para passar um corpo JSON, passe um objeto Python (por exemplo, um `dict`) para o parâmetro `json`.
* Se você precisar enviar *Dados de Formulário* em vez de JSON, use o parâmetro `data`.
* Para passar *headers*, use um `dict` no parâmetro `headers`.
* Para *cookies*, um `dict` no parâmetro `cookies`.
Para mais informações sobre como passar dados para o backend (usando `httpx` ou `TestClient`), consulte a <a href="https://www.python-httpx.org" class="external-link" target="_blank">documentação do HTTPX</a>.
/// info | "Informação"
Observe que o `TestClient` recebe dados que podem ser convertidos para JSON, não para modelos Pydantic.
Se você tiver um modelo Pydantic em seu teste e quiser enviar seus dados para o aplicativo durante o teste, poderá usar o `jsonable_encoder` descrito em [Codificador compatível com JSON](encoder.md){.internal-link target=_blank}.
///
## Execute-o
Depois disso, você só precisa instalar o `pytest`.
Certifique-se de criar um [ambiente virtual](../virtual-environments.md){.internal-link target=_blank}, ativá-lo e instalá-lo, por exemplo:
<div class="termy">
```console
$ pip install pytest
---> 100%
```
</div>
Ele detectará os arquivos e os testes automaticamente, os executará e informará os resultados para você.
Execute os testes com:
<div class="termy">
```console
$ pytest
================ test session starts ================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: /home/user/code/superawesome-cli/app
plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1
collected 6 items
---> 100%
test_main.py <span style="color: green; white-space: pre;">...... [100%]</span>
<span style="color: green;">================= 1 passed in 0.03s =================</span>
```
</div>

844
docs/pt/docs/virtual-environments.md

@ -0,0 +1,844 @@
# Ambientes Virtuais
Ao trabalhar em projetos Python, você provavelmente deve usar um **ambiente virtual** (ou um mecanismo similar) para isolar os pacotes que você instala para cada projeto.
/// info | "Informação"
Se você já sabe sobre ambientes virtuais, como criá-los e usá-los, talvez seja melhor pular esta seção. 🤓
///
/// tip | "Dica"
Um **ambiente virtual** é diferente de uma **variável de ambiente**.
Uma **variável de ambiente** é uma variável no sistema que pode ser usada por programas.
Um **ambiente virtual** é um diretório com alguns arquivos.
///
/// info | "Informação"
Esta página lhe ensinará como usar **ambientes virtuais** e como eles funcionam.
Se você estiver pronto para adotar uma **ferramenta que gerencia tudo** para você (incluindo a instalação do Python), experimente <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>.
///
## Criar um Projeto
Primeiro, crie um diretório para seu projeto.
O que normalmente faço é criar um diretório chamado `code` dentro do meu diretório home/user.
E dentro disso eu crio um diretório por projeto.
<div class="termy">
```console
// Vá para o diretório inicial
$ cd
// Crie um diretório para todos os seus projetos de código
$ mkdir code
// Entre nesse diretório de código
$ cd code
// Crie um diretório para este projeto
$ mkdir awesome-project
// Entre no diretório do projeto
$ cd awesome-project
```
</div>
## Crie um ambiente virtual
Ao começar a trabalhar em um projeto Python **pela primeira vez**, crie um ambiente virtual **<abbr title="existem outras opções, esta é uma diretriz simples">dentro do seu projeto</abbr>**.
/// tip | "Dica"
Você só precisa fazer isso **uma vez por projeto**, não toda vez que trabalhar.
///
//// tab | `venv`
Para criar um ambiente virtual, você pode usar o módulo `venv` que vem com o Python.
<div class="termy">
```console
$ python -m venv .venv
```
</div>
/// details | O que esse comando significa
* `python`: usa o programa chamado `python`
* `-m`: chama um módulo como um script, nós diremos a ele qual módulo vem em seguida
* `venv`: usa o módulo chamado `venv` que normalmente vem instalado com o Python
* `.venv`: cria o ambiente virtual no novo diretório `.venv`
///
////
//// tab | `uv`
Se você tiver o <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a> instalado, poderá usá-lo para criar um ambiente virtual.
<div class="termy">
```console
$ uv venv
```
</div>
/// tip | "Dica"
Por padrão, `uv` criará um ambiente virtual em um diretório chamado `.venv`.
Mas você pode personalizá-lo passando um argumento adicional com o nome do diretório.
///
////
Esse comando cria um novo ambiente virtual em um diretório chamado `.venv`.
/// details | `.venv` ou outro nome
Você pode criar o ambiente virtual em um diretório diferente, mas há uma convenção para chamá-lo de `.venv`.
///
## Ative o ambiente virtual
Ative o novo ambiente virtual para que qualquer comando Python que você executar ou pacote que você instalar o utilize.
/// tip | "Dica"
Faça isso **toda vez** que iniciar uma **nova sessão de terminal** para trabalhar no projeto.
///
//// tab | Linux, macOS
<div class="termy">
```console
$ source .venv/bin/activate
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
$ .venv\Scripts\Activate.ps1
```
</div>
////
//// tab | Windows Bash
Ou se você usa o Bash para Windows (por exemplo, <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
<div class="termy">
```console
$ source .venv/Scripts/activate
```
</div>
////
/// tip | "Dica"
Toda vez que você instalar um **novo pacote** naquele ambiente, **ative** o ambiente novamente.
Isso garante que, se você usar um **programa de terminal (<abbr title="interface de linha de comando">CLI</abbr>)** instalado por esse pacote, você usará aquele do seu ambiente virtual e não qualquer outro que possa ser instalado globalmente, provavelmente com uma versão diferente do que você precisa.
///
## Verifique se o ambiente virtual está ativo
Verifique se o ambiente virtual está ativo (o comando anterior funcionou).
/// tip | "Dica"
Isso é **opcional**, mas é uma boa maneira de **verificar** se tudo está funcionando conforme o esperado e se você está usando o ambiente virtual pretendido.
///
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
$ which python
/home/user/code/awesome-project/.venv/bin/python
```
</div>
Se ele mostrar o binário `python` em `.venv/bin/python`, dentro do seu projeto (neste caso `awesome-project`), então funcionou. 🎉
////
//// tab | Windows PowerShell
<div class="termy">
```console
$ Get-Command python
C:\Users\user\code\awesome-project\.venv\Scripts\python
```
</div>
Se ele mostrar o binário `python` em `.venv\Scripts\python`, dentro do seu projeto (neste caso `awesome-project`), então funcionou. 🎉
////
## Atualizar `pip`
/// tip | "Dica"
Se você usar <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>, você o usará para instalar coisas em vez do `pip`, então não precisará atualizar o `pip`. 😎
///
Se você estiver usando `pip` para instalar pacotes (ele vem por padrão com o Python), você deve **atualizá-lo** para a versão mais recente.
Muitos erros exóticos durante a instalação de um pacote são resolvidos apenas atualizando o `pip` primeiro.
/// tip | "Dica"
Normalmente, você faria isso **uma vez**, logo após criar o ambiente virtual.
///
Certifique-se de que o ambiente virtual esteja ativo (com o comando acima) e execute:
<div class="termy">
```console
$ python -m pip install --upgrade pip
---> 100%
```
</div>
## Adicionar `.gitignore`
Se você estiver usando **Git** (você deveria), adicione um arquivo `.gitignore` para excluir tudo em seu `.venv` do Git.
/// tip | "Dica"
Se você usou <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a> para criar o ambiente virtual, ele já fez isso para você, você pode pular esta etapa. 😎
///
/// tip | "Dica"
Faça isso **uma vez**, logo após criar o ambiente virtual.
///
<div class="termy">
```console
$ echo "*" > .venv/.gitignore
```
</div>
/// details | O que esse comando significa
* `echo "*"`: irá "imprimir" o texto `*` no terminal (a próxima parte muda isso um pouco)
* `>`: qualquer coisa impressa no terminal pelo comando à esquerda de `>` não deve ser impressa, mas sim escrita no arquivo que vai à direita de `>`
* `.gitignore`: o nome do arquivo onde o texto deve ser escrito
E `*` para Git significa "tudo". Então, ele ignorará tudo no diretório `.venv`.
Esse comando criará um arquivo `.gitignore` com o conteúdo:
```gitignore
*
```
///
## Instalar Pacotes
Após ativar o ambiente, você pode instalar pacotes nele.
/// tip | "Dica"
Faça isso **uma vez** ao instalar ou atualizar os pacotes que seu projeto precisa.
Se precisar atualizar uma versão ou adicionar um novo pacote, você **fará isso novamente**.
///
### Instalar pacotes diretamente
Se estiver com pressa e não quiser usar um arquivo para declarar os requisitos de pacote do seu projeto, você pode instalá-los diretamente.
/// tip | "Dica"
É uma (muito) boa ideia colocar os pacotes e versões que seu programa precisa em um arquivo (por exemplo `requirements.txt` ou `pyproject.toml`).
///
//// tab | `pip`
<div class="termy">
```console
$ pip install "fastapi[standard]"
---> 100%
```
</div>
////
//// tab | `uv`
Se você tem o <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
<div class="termy">
```console
$ uv pip install "fastapi[standard]"
---> 100%
```
</div>
////
### Instalar a partir de `requirements.txt`
Se você tiver um `requirements.txt`, agora poderá usá-lo para instalar seus pacotes.
//// tab | `pip`
<div class="termy">
```console
$ pip install -r requirements.txt
---> 100%
```
</div>
////
//// tab | `uv`
Se você tem o <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">`uv`</a>:
<div class="termy">
```console
$ uv pip install -r requirements.txt
---> 100%
```
</div>
////
/// details | `requirements.txt`
Um `requirements.txt` com alguns pacotes poderia se parecer com:
```requirements.txt
fastapi[standard]==0.113.0
pydantic==2.8.0
```
///
## Execute seu programa
Depois de ativar o ambiente virtual, você pode executar seu programa, e ele usará o Python dentro do seu ambiente virtual com os pacotes que você instalou lá.
<div class="termy">
```console
$ python main.py
Hello World
```
</div>
## Configure seu editor
Você provavelmente usaria um editor. Certifique-se de configurá-lo para usar o mesmo ambiente virtual que você criou (ele provavelmente o detectará automaticamente) para que você possa obter erros de preenchimento automático e em linha.
Por exemplo:
* <a href="https://code.visualstudio.com/docs/python/environments#_select-and-activate-an-environment" class="external-link" target="_blank">VS Code</a>
* <a href="https://www.jetbrains.com/help/pycharm/creating-virtual-environment.html" class="external-link" target="_blank">PyCharm</a>
/// tip | "Dica"
Normalmente, você só precisa fazer isso **uma vez**, ao criar o ambiente virtual.
///
## Desativar o ambiente virtual
Quando terminar de trabalhar no seu projeto, você pode **desativar** o ambiente virtual.
<div class="termy">
```console
$ deactivate
```
</div>
Dessa forma, quando você executar `python`, ele não tentará executá-lo naquele ambiente virtual com os pacotes instalados nele.
## Pronto para trabalhar
Agora você está pronto para começar a trabalhar no seu projeto.
/// tip | "Dica"
Você quer entender o que é tudo isso acima?
Continue lendo. 👇🤓
///
## Por que ambientes virtuais
Para trabalhar com o FastAPI, você precisa instalar o <a href="https://www.python.org/" class="external-link" target="_blank">Python</a>.
Depois disso, você precisará **instalar** o FastAPI e quaisquer outros **pacotes** que queira usar.
Para instalar pacotes, você normalmente usaria o comando `pip` que vem com o Python (ou alternativas semelhantes).
No entanto, se você usar `pip` diretamente, os pacotes serão instalados no seu **ambiente Python global** (a instalação global do Python).
### O Problema
Então, qual é o problema em instalar pacotes no ambiente global do Python?
Em algum momento, você provavelmente acabará escrevendo muitos programas diferentes que dependem de **pacotes diferentes**. E alguns desses projetos em que você trabalha dependerão de **versões diferentes** do mesmo pacote. 😱
Por exemplo, você pode criar um projeto chamado `philosophers-stone`, este programa depende de outro pacote chamado **`harry`, usando a versão `1`**. Então, você precisa instalar `harry`.
```mermaid
flowchart LR
stone(philosophers-stone) -->|requires| harry-1[harry v1]
```
Então, em algum momento depois, você cria outro projeto chamado `prisoner-of-azkaban`, e esse projeto também depende de `harry`, mas esse projeto precisa do **`harry` versão `3`**.
```mermaid
flowchart LR
azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3]
```
Mas agora o problema é que, se você instalar os pacotes globalmente (no ambiente global) em vez de em um **ambiente virtual** local, você terá que escolher qual versão do `harry` instalar.
Se você quiser executar `philosophers-stone`, precisará primeiro instalar `harry` versão `1`, por exemplo com:
<div class="termy">
```console
$ pip install "harry==1"
```
</div>
E então você acabaria com `harry` versão `1` instalado em seu ambiente Python global.
```mermaid
flowchart LR
subgraph global[global env]
harry-1[harry v1]
end
subgraph stone-project[philosophers-stone project]
stone(philosophers-stone) -->|requires| harry-1
end
```
Mas se você quiser executar `prisoner-of-azkaban`, você precisará desinstalar `harry` versão `1` e instalar `harry` versão `3` (ou apenas instalar a versão `3` desinstalaria automaticamente a versão `1`).
<div class="termy">
```console
$ pip install "harry==3"
```
</div>
E então você acabaria com `harry` versão `3` instalado em seu ambiente Python global.
E se você tentar executar `philosophers-stone` novamente, há uma chance de que **não funcione** porque ele precisa de `harry` versão `1`.
```mermaid
flowchart LR
subgraph global[global env]
harry-1[<strike>harry v1</strike>]
style harry-1 fill:#ccc,stroke-dasharray: 5 5
harry-3[harry v3]
end
subgraph stone-project[philosophers-stone project]
stone(philosophers-stone) -.-x|⛔️| harry-1
end
subgraph azkaban-project[prisoner-of-azkaban project]
azkaban(prisoner-of-azkaban) --> |requires| harry-3
end
```
/// tip | "Dica"
É muito comum em pacotes Python tentar ao máximo **evitar alterações drásticas** em **novas versões**, mas é melhor prevenir do que remediar e instalar versões mais recentes intencionalmente e, quando possível, executar os testes para verificar se tudo está funcionando corretamente.
///
Agora, imagine isso com **muitos** outros **pacotes** dos quais todos os seus **projetos dependem**. Isso é muito difícil de gerenciar. E você provavelmente acabaria executando alguns projetos com algumas **versões incompatíveis** dos pacotes, e não saberia por que algo não está funcionando.
Além disso, dependendo do seu sistema operacional (por exemplo, Linux, Windows, macOS), ele pode ter vindo com o Python já instalado. E, nesse caso, provavelmente tinha alguns pacotes pré-instalados com algumas versões específicas **necessárias para o seu sistema**. Se você instalar pacotes no ambiente global do Python, poderá acabar **quebrando** alguns dos programas que vieram com seu sistema operacional.
## Onde os pacotes são instalados
Quando você instala o Python, ele cria alguns diretórios com alguns arquivos no seu computador.
Alguns desses diretórios são os responsáveis ​​por ter todos os pacotes que você instala.
Quando você executa:
<div class="termy">
```console
// Não execute isso agora, é apenas um exemplo 🤓
$ pip install "fastapi[standard]"
---> 100%
```
</div>
Isso fará o download de um arquivo compactado com o código FastAPI, normalmente do <a href="https://pypi.org/project/fastapi/" class="external-link" target="_blank">PyPI</a>.
Ele também fará o **download** de arquivos para outros pacotes dos quais o FastAPI depende.
Em seguida, ele **extrairá** todos esses arquivos e os colocará em um diretório no seu computador.
Por padrão, ele colocará os arquivos baixados e extraídos no diretório que vem com a instalação do Python, que é o **ambiente global**.
## O que são ambientes virtuais
A solução para os problemas de ter todos os pacotes no ambiente global é usar um **ambiente virtual para cada projeto** em que você trabalha.
Um ambiente virtual é um **diretório**, muito semelhante ao global, onde você pode instalar os pacotes para um projeto.
Dessa forma, cada projeto terá seu próprio ambiente virtual (diretório `.venv`) com seus próprios pacotes.
```mermaid
flowchart TB
subgraph stone-project[philosophers-stone project]
stone(philosophers-stone) --->|requires| harry-1
subgraph venv1[.venv]
harry-1[harry v1]
end
end
subgraph azkaban-project[prisoner-of-azkaban project]
azkaban(prisoner-of-azkaban) --->|requires| harry-3
subgraph venv2[.venv]
harry-3[harry v3]
end
end
stone-project ~~~ azkaban-project
```
## O que significa ativar um ambiente virtual
Quando você ativa um ambiente virtual, por exemplo com:
//// tab | Linux, macOS
<div class="termy">
```console
$ source .venv/bin/activate
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
$ .venv\Scripts\Activate.ps1
```
</div>
////
//// tab | Windows Bash
Ou se você usa o Bash para Windows (por exemplo, <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>):
<div class="termy">
```console
$ source .venv/Scripts/activate
```
</div>
////
Esse comando criará ou modificará algumas [variáveis ​​de ambiente](environment-variables.md){.internal-link target=_blank} que estarão disponíveis para os próximos comandos.
Uma dessas variáveis ​​é a variável `PATH`.
/// tip | "Dica"
Você pode aprender mais sobre a variável de ambiente `PATH` na seção [Variáveis ​​de ambiente](environment-variables.md#path-environment-variable){.internal-link target=_blank}.
///
A ativação de um ambiente virtual adiciona seu caminho `.venv/bin` (no Linux e macOS) ou `.venv\Scripts` (no Windows) à variável de ambiente `PATH`.
Digamos que antes de ativar o ambiente, a variável `PATH` estava assim:
//// tab | Linux, macOS
```plaintext
/usr/bin:/bin:/usr/sbin:/sbin
```
Isso significa que o sistema procuraria programas em:
* `/usr/bin`
* `/bin`
* `/usr/sbin`
* `/sbin`
////
//// tab | Windows
```plaintext
C:\Windows\System32
```
Isso significa que o sistema procuraria programas em:
* `C:\Windows\System32`
////
Após ativar o ambiente virtual, a variável `PATH` ficaria mais ou menos assim:
//// tab | Linux, macOS
```plaintext
/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin
```
Isso significa que o sistema agora começará a procurar primeiro por programas em:
```plaintext
/home/user/code/awesome-project/.venv/bin
```
antes de procurar nos outros diretórios.
Então, quando você digita `python` no terminal, o sistema encontrará o programa Python em
```plaintext
/home/user/code/awesome-project/.venv/bin/python
```
e usa esse.
////
//// tab | Windows
```plaintext
C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32
```
Isso significa que o sistema agora começará a procurar primeiro por programas em:
```plaintext
C:\Users\user\code\awesome-project\.venv\Scripts
```
antes de procurar nos outros diretórios.
Então, quando você digita `python` no terminal, o sistema encontrará o programa Python em
```plaintext
C:\Users\user\code\awesome-project\.venv\Scripts\python
```
e usa esse.
////
Um detalhe importante é que ele colocará o caminho do ambiente virtual no **início** da variável `PATH`. O sistema o encontrará **antes** de encontrar qualquer outro Python disponível. Dessa forma, quando você executar `python`, ele usará o Python **do ambiente virtual** em vez de qualquer outro `python` (por exemplo, um `python` de um ambiente global).
Ativar um ambiente virtual também muda algumas outras coisas, mas esta é uma das mais importantes.
## Verificando um ambiente virtual
Ao verificar se um ambiente virtual está ativo, por exemplo com:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
$ which python
/home/user/code/awesome-project/.venv/bin/python
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
$ Get-Command python
C:\Users\user\code\awesome-project\.venv\Scripts\python
```
</div>
////
Isso significa que o programa `python` que será usado é aquele **no ambiente virtual**.
você usa `which` no Linux e macOS e `Get-Command` no Windows PowerShell.
A maneira como esse comando funciona é que ele vai e verifica na variável de ambiente `PATH`, passando por **cada caminho em ordem**, procurando pelo programa chamado `python`. Uma vez que ele o encontre, ele **mostrará o caminho** para esse programa.
A parte mais importante é que quando você chama ``python`, esse é exatamente o "`python`" que será executado.
Assim, você pode confirmar se está no ambiente virtual correto.
/// tip | "Dica"
É fácil ativar um ambiente virtual, obter um Python e então **ir para outro projeto**.
E o segundo projeto **não funcionaria** porque você está usando o **Python incorreto**, de um ambiente virtual para outro projeto.
É útil poder verificar qual `python` está sendo usado. 🤓
///
## Por que desativar um ambiente virtual
Por exemplo, você pode estar trabalhando em um projeto `philosophers-stone`, **ativar esse ambiente virtual**, instalar pacotes e trabalhar com esse ambiente.
E então você quer trabalhar em **outro projeto** `prisoner-of-azkaban`.
Você vai para aquele projeto:
<div class="termy">
```console
$ cd ~/code/prisoner-of-azkaban
```
</div>
Se você não desativar o ambiente virtual para `philosophers-stone`, quando você executar `python` no terminal, ele tentará usar o Python de `philosophers-stone`.
<div class="termy">
```console
$ cd ~/code/prisoner-of-azkaban
$ python main.py
// Erro ao importar o Sirius, ele não está instalado 😱
Traceback (most recent call last):
File "main.py", line 1, in <module>
import sirius
```
</div>
Mas se você desativar o ambiente virtual e ativar o novo para `prisoner-of-askaban`, quando você executar `python`, ele usará o Python do ambiente virtual em `prisoner-of-azkaban`.
<div class="termy">
```console
$ cd ~/code/prisoner-of-azkaban
// Você não precisa estar no diretório antigo para desativar, você pode fazer isso de onde estiver, mesmo depois de ir para o outro projeto 😎
$ deactivate
// Ative o ambiente virtual em prisoner-of-azkaban/.venv 🚀
$ source .venv/bin/activate
// Agora, quando você executar o python, ele encontrará o pacote sirius instalado neste ambiente virtual ✨
$ python main.py
Eu juro solenemente 🐺
```
</div>
## Alternativas
Este é um guia simples para você começar e lhe ensinar como tudo funciona **por baixo**.
Existem muitas **alternativas** para gerenciar ambientes virtuais, dependências de pacotes (requisitos) e projetos.
Quando estiver pronto e quiser usar uma ferramenta para **gerenciar todo o projeto**, dependências de pacotes, ambientes virtuais, etc., sugiro que você experimente o <a href="https://github.com/astral-sh/uv" class="external-link" target="_blank">uv</a>.
`uv` pode fazer muitas coisas, ele pode:
* **Instalar o Python** para você, incluindo versões diferentes
* Gerenciar o **ambiente virtual** para seus projetos
* Instalar **pacotes**
* Gerenciar **dependências e versões** de pacotes para seu projeto
* Certifique-se de ter um conjunto **exato** de pacotes e versões para instalar, incluindo suas dependências, para que você possa ter certeza de que pode executar seu projeto em produção exatamente da mesma forma que em seu computador durante o desenvolvimento, isso é chamado de **bloqueio**
* E muitas outras coisas
## Conclusão
Se você leu e entendeu tudo isso, agora **você sabe muito mais** sobre ambientes virtuais do que muitos desenvolvedores por aí. 🤓
Saber esses detalhes provavelmente será útil no futuro, quando você estiver depurando algo que parece complexo, mas você saberá **como tudo funciona**. 😎

2
fastapi/__init__.py

@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.114.0"
__version__ = "0.114.1"
from starlette import status as status

6
fastapi/_compat.py

@ -2,6 +2,7 @@ from collections import deque
from copy import copy
from dataclasses import dataclass, is_dataclass
from enum import Enum
from functools import lru_cache
from typing import (
Any,
Callable,
@ -649,3 +650,8 @@ def is_uploadfile_sequence_annotation(annotation: Any) -> bool:
is_uploadfile_or_nonable_uploadfile_annotation(sub_annotation)
for sub_annotation in get_args(annotation)
)
@lru_cache
def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]:
return get_model_fields(model)

4
fastapi/dependencies/utils.py

@ -32,8 +32,8 @@ from fastapi._compat import (
evaluate_forwardref,
field_annotation_is_scalar,
get_annotation_from_field_info,
get_cached_model_fields,
get_missing_field_error,
get_model_fields,
is_bytes_field,
is_bytes_sequence_field,
is_scalar_field,
@ -810,7 +810,7 @@ async def request_body_to_args(
fields_to_extract: List[ModelField] = body_fields
if single_not_embedded_field and lenient_issubclass(first_field.type_, BaseModel):
fields_to_extract = get_model_fields(first_field.type_)
fields_to_extract = get_cached_model_fields(first_field.type_)
if isinstance(received_body, FormData):
body_to_process = await _extract_form_body(fields_to_extract, received_body)

4
pyproject.toml

@ -241,3 +241,7 @@ known-third-party = ["fastapi", "pydantic", "starlette"]
[tool.ruff.lint.pyupgrade]
# Preserve types, even if a file imports `from __future__ import annotations`.
keep-runtime-typing = true
[tool.inline-snapshot]
# default-flags=["fix"]
# default-flags=["create"]

4
requirements-tests.txt

@ -3,7 +3,7 @@
pytest >=7.1.3,<8.0.0
coverage[toml] >= 6.5.0,< 8.0
mypy ==1.8.0
ruff ==0.6.3
ruff ==0.6.4
dirty-equals ==0.6.0
# TODO: once removing databases from tutorial, upgrade SQLAlchemy
# probably when including SQLModel
@ -14,7 +14,7 @@ anyio[trio] >=3.2.1,<4.0.0
PyJWT==2.8.0
pyyaml >=5.3.1,<7.0.0
passlib[bcrypt] >=1.7.2,<2.0.0
inline-snapshot==0.13.0
# types
types-ujson ==5.7.0.1
types-orjson ==3.6.2

16
tests/test_compat.py

@ -5,6 +5,7 @@ from fastapi._compat import (
ModelField,
Undefined,
_get_model_config,
get_cached_model_fields,
get_model_fields,
is_bytes_sequence_annotation,
is_scalar_field,
@ -102,3 +103,18 @@ def test_is_pv1_scalar_field():
fields = get_model_fields(Model)
assert not is_scalar_field(fields[0])
def test_get_model_fields_cached():
class Model(BaseModel):
foo: str
non_cached_fields = get_model_fields(Model)
non_cached_fields2 = get_model_fields(Model)
cached_fields = get_cached_model_fields(Model)
cached_fields2 = get_cached_model_fields(Model)
for f1, f2 in zip(cached_fields, cached_fields2):
assert f1 is f2
assert non_cached_fields is not non_cached_fields2
assert cached_fields is cached_fields2

Loading…
Cancel
Save