committed by
GitHub
1 changed files with 485 additions and 0 deletions
@ -0,0 +1,485 @@ |
|||
# Configurações e Variáveis de Ambiente |
|||
|
|||
Em muitos casos a sua aplicação pode precisar de configurações externas, como chaves secretas, credenciais de banco de dados, credenciais para serviços de email, etc. |
|||
|
|||
A maioria dessas configurações é variável (podem mudar), como URLs de bancos de dados. E muitas delas podem conter dados sensíveis, como tokens secretos. |
|||
|
|||
Por isso é comum prover essas configurações como variáveis de ambiente que são utilizidas pela aplicação. |
|||
|
|||
## Variáveis de Ambiente |
|||
|
|||
!!! dica |
|||
Se você já sabe o que são variáveis de ambiente e como utilizá-las, sinta-se livre para avançar para o próximo tópico. |
|||
|
|||
Uma <a href="https://pt.wikipedia.org/wiki/Variável_de_ambiente" class="external-link" target="_blank">variável de ambiente</a> (abreviada em inglês para "env var") é uma variável definida fora do código Python, no sistema operacional, e pode ser lida pelo seu código Python (ou por outros programas). |
|||
|
|||
Você pode criar e utilizar variáveis de ambiente no terminal, sem precisar utilizar Python: |
|||
|
|||
=== "Linux, macOS, Windows Bash" |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// Você pode criar uma env var MY_NAME usando |
|||
$ export MY_NAME="Wade Wilson" |
|||
|
|||
// E utilizá-la em outros programas, como |
|||
$ echo "Hello $MY_NAME" |
|||
|
|||
Hello Wade Wilson |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
=== "Windows PowerShell" |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// Criando env var MY_NAME |
|||
$ $Env:MY_NAME = "Wade Wilson" |
|||
|
|||
// Usando em outros programas, como |
|||
$ echo "Hello $Env:MY_NAME" |
|||
|
|||
Hello Wade Wilson |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
### Lendo variáveis de ambiente com Python |
|||
|
|||
Você também pode criar variáveis de ambiente fora do Python, no terminal (ou com qualquer outro método), e realizar a leitura delas no Python. |
|||
|
|||
Por exemplo, você pode definir um arquivo `main.py` com o seguinte código: |
|||
|
|||
```Python hl_lines="3" |
|||
import os |
|||
|
|||
name = os.getenv("MY_NAME", "World") |
|||
print(f"Hello {name} from Python") |
|||
``` |
|||
|
|||
!!! dica |
|||
O segundo parâmetro em <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 para o retorno. |
|||
|
|||
Se nenhum valor for informado, `None` é utilizado por padrão, aqui definimos `"World"` como o valor padrão a ser utilizado. |
|||
|
|||
E depois você pode executar esse arquivo: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// Aqui ainda não definimos a env var |
|||
$ python main.py |
|||
|
|||
// Por isso obtemos o valor padrão |
|||
|
|||
Hello World from Python |
|||
|
|||
// Mas se definirmos uma variável de ambiente primeiro |
|||
$ export MY_NAME="Wade Wilson" |
|||
|
|||
// E executarmos o programa novamente |
|||
$ python main.py |
|||
|
|||
// Agora ele pode ler a variável de ambiente |
|||
|
|||
Hello Wade Wilson from Python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Como variáveis de ambiente podem ser definidas fora do código da aplicação, mas acessadas pela aplicação, e não precisam ser armazenadas (versionadas com `git`) junto dos outros arquivos, é comum utilizá-las para guardar configurações. |
|||
|
|||
Você também pode criar uma variável de ambiente específica para uma invocação de um programa, que é acessível somente para esse programa, e somente enquanto ele estiver executando. |
|||
|
|||
Para fazer isso, crie a variável imediatamente antes de iniciar o programa, na mesma linha: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
// Criando uma env var MY_NAME na mesma linha da execução do programa |
|||
$ MY_NAME="Wade Wilson" python main.py |
|||
|
|||
// Agora a aplicação consegue ler a variável de ambiente |
|||
|
|||
Hello Wade Wilson from Python |
|||
|
|||
// E a variável deixa de existir após isso |
|||
$ python main.py |
|||
|
|||
Hello World from Python |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
!!! dica |
|||
Você pode ler mais sobre isso em: <a href="https://12factor.net/pt_br/config" class="external-link" target="_blank">The Twelve-Factor App: Configurações</a>. |
|||
|
|||
### Tipagem e Validação |
|||
|
|||
Essas variáveis de ambiente suportam apenas strings, por serem externas ao Python e por que precisam ser compatíveis com outros programas e o resto do sistema (e até mesmo com outros sistemas operacionais, como Linux, Windows e macOS). |
|||
|
|||
Isso significa que qualquer valor obtido de uma variável de ambiente em Python terá o tipo `str`, e qualquer conversão para um tipo diferente ou validação deve ser realizada no código. |
|||
|
|||
## Pydantic `Settings` |
|||
|
|||
Por sorte, o Pydantic possui uma funcionalidade para lidar com essas configurações vindas de variáveis de ambiente utilizando <a href="https://docs.pydantic.dev/latest/usage/pydantic_settings/" class="external-link" target="_blank">Pydantic: Settings management</a>. |
|||
|
|||
### Instalando `pydantic-settings` |
|||
|
|||
Primeiro, instale o pacote `pydantic-settings`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install pydantic-settings |
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
Ele também está incluído no fastapi quando você instala com a opção `all`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "fastapi[all]" |
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
!!! info |
|||
Na v1 do Pydantic ele estava incluído no pacote principal. Agora ele está distribuido como um pacote independente para que você possa optar por instalar ou não caso você não precise dessa funcionalidade. |
|||
|
|||
### Criando o objeto `Settings` |
|||
|
|||
Importe a classe `BaseSettings` do Pydantic e crie uma nova subclasse, de forma parecida com um modelo do Pydantic. |
|||
|
|||
Os atributos da classe são declarados com anotações de tipo, e possíveis valores padrão, da mesma maneira que os modelos do Pydantic. |
|||
|
|||
Você pode utilizar todas as ferramentas e funcionalidades de validação que são utilizadas nos modelos do Pydantic, como tipos de dados diferentes e validações adicionei com `Field()`. |
|||
|
|||
=== "Pydantic v2" |
|||
|
|||
```Python hl_lines="2 5-8 11" |
|||
{!> ../../../docs_src/settings/tutorial001.py!} |
|||
``` |
|||
|
|||
=== "Pydantic v1" |
|||
|
|||
!!! Info |
|||
Na versão 1 do Pydantic você importaria `BaseSettings` diretamente do módulo `pydantic` em vez do módulo `pydantic_settings`. |
|||
|
|||
```Python hl_lines="2 5-8 11" |
|||
{!> ../../../docs_src/settings/tutorial001_pv1.py!} |
|||
``` |
|||
|
|||
!!! dica |
|||
Se você quiser algo pronto para copiar e colar na sua aplicação, não use esse exemplo, mas sim o exemplo abaixo. |
|||
|
|||
Portanto, quando você cria uma instância da classe `Settings` (nesse caso, o objeto `settings`), o Pydantic lê as variáveis de ambiente sem diferenciar maiúsculas e minúsculas, por isso, uma variável maiúscula `APP_NAME` será usada para o atributo `app_name`. |
|||
|
|||
Depois ele irá converter e validar os dados. Assim, quando você utilizar aquele objeto `settings`, os dados terão o tipo que você declarou (e.g. `items_per_user` será do tipo `int`). |
|||
|
|||
### Usando o objeto `settings` |
|||
|
|||
Depois, Você pode utilizar o novo objeto `settings` na sua aplicação: |
|||
|
|||
```Python hl_lines="18-20" |
|||
{!../../../docs_src/settings/tutorial001.py!} |
|||
``` |
|||
|
|||
### Executando o servidor |
|||
|
|||
No próximo passo, você pode inicializar o servidor passando as configurações em forma de variáveis de ambiente, por exemplo, você poderia definir `ADMIN_EMAIL` e `APP_NAME` da seguinte forma: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ ADMIN_EMAIL="[email protected]" APP_NAME="ChimichangApp" fastapi run main.py |
|||
|
|||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
!!! dica |
|||
Para definir múltiplas variáveis de ambiente para um único comando basta separá-las utilizando espaços, e incluir todas elas antes do comando. |
|||
|
|||
Assim, o atributo `admin_email` seria definido como `"[email protected]"`. |
|||
|
|||
`app_name` seria `"ChimichangApp"`. |
|||
|
|||
E `items_per_user` manteria o valor padrão de `50`. |
|||
|
|||
## Configurações em um módulo separado |
|||
|
|||
Você também pode incluir essas configurações em um arquivo de um módulo separado como visto em [Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=\_blank}. |
|||
|
|||
Por exemplo, você pode adicionar um arquivo `config.py` com: |
|||
|
|||
```Python |
|||
{!../../../docs_src/settings/app01/config.py!} |
|||
``` |
|||
|
|||
E utilizar essa configuração em `main.py`: |
|||
|
|||
```Python hl_lines="3 11-13" |
|||
{!../../../docs_src/settings/app01/main.py!} |
|||
``` |
|||
|
|||
!!! dica |
|||
Você também precisa incluir um arquivo `__init__.py` como visto em [Bigger Applications - Multiple Files](../tutorial/bigger-applications.md){.internal-link target=\_blank}. |
|||
|
|||
## Configurações em uma dependência |
|||
|
|||
Em certas ocasiões, pode ser útil fornecer essas configurações a partir de uma dependência, em vez de definir um objeto global `settings` que é utilizado em toda a aplicação. |
|||
|
|||
Isso é especialmente útil durante os testes, já que é bastante simples sobrescrever uma dependência com suas configurações personalizadas. |
|||
|
|||
### O arquivo de configuração |
|||
|
|||
Baseando-se no exemplo anterior, seu arquivo `config.py` seria parecido com isso: |
|||
|
|||
```Python hl_lines="10" |
|||
{!../../../docs_src/settings/app02/config.py!} |
|||
``` |
|||
|
|||
Perceba que dessa vez não criamos uma instância padrão `settings = Settings()`. |
|||
|
|||
### O arquivo principal da aplicação |
|||
|
|||
Agora criamos a dependência que retorna um novo objeto `config.Settings()`. |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="6 12-13" |
|||
{!> ../../../docs_src/settings/app02_an_py39/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.8+" |
|||
|
|||
```Python hl_lines="6 12-13" |
|||
{!> ../../../docs_src/settings/app02_an/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.8+ non-Annotated" |
|||
|
|||
!!! dica |
|||
Utilize a versão com `Annotated` se possível. |
|||
|
|||
```Python hl_lines="5 11-12" |
|||
{!> ../../../docs_src/settings/app02/main.py!} |
|||
``` |
|||
|
|||
!!! dica |
|||
Vamos discutir sobre `@lru_cache` logo mais. |
|||
|
|||
Por enquanto, você pode considerar `get_settings()` como uma função normal. |
|||
|
|||
E então podemos declarar essas configurações como uma dependência na função de operação da rota e utilizar onde for necessário. |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="17 19-21" |
|||
{!> ../../../docs_src/settings/app02_an_py39/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.8+" |
|||
|
|||
```Python hl_lines="17 19-21" |
|||
{!> ../../../docs_src/settings/app02_an/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.8+ non-Annotated" |
|||
|
|||
!!! dica |
|||
Utilize a versão com `Annotated` se possível. |
|||
|
|||
```Python hl_lines="16 18-20" |
|||
{!> ../../../docs_src/settings/app02/main.py!} |
|||
``` |
|||
|
|||
### Configurações e testes |
|||
|
|||
Então seria muito fácil fornecer uma configuração diferente durante a execução dos testes sobrescrevendo a dependência de `get_settings`: |
|||
|
|||
```Python hl_lines="9-10 13 21" |
|||
{!../../../docs_src/settings/app02/test_main.py!} |
|||
``` |
|||
|
|||
Na sobrescrita da dependência, definimos um novo valor para `admin_email` quando instanciamos um novo objeto `Settings`, e então retornamos esse novo objeto. |
|||
|
|||
Após isso, podemos testar se o valor está sendo utilizado. |
|||
|
|||
## Lendo um arquivo `.env` |
|||
|
|||
Se você tiver muitas configurações que variem bastante, talvez em ambientes distintos, pode ser útil colocá-las em um arquivo e depois lê-las como se fossem variáveis de ambiente. |
|||
|
|||
Essa prática é tão comum que possui um nome, essas variáveis de ambiente normalmente são colocadas em um arquivo `.env`, e esse arquivo é chamado de "dotenv". |
|||
|
|||
!!! dica |
|||
Um arquivo iniciando com um ponto final (`.`) é um arquivo oculto em sistemas baseados em Unix, como Linux e MacOS. |
|||
|
|||
Mas um arquivo dotenv não precisa ter esse nome exato. |
|||
|
|||
Pydantic suporta a leitura desses tipos de arquivos utilizando uma biblioteca externa. Você pode ler mais em <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a>. |
|||
|
|||
!!! dica |
|||
Para que isso funcione você precisa executar `pip install python-dotenv`. |
|||
|
|||
### O arquivo `.env` |
|||
|
|||
Você pode definir um arquivo `.env` com o seguinte conteúdo: |
|||
|
|||
```bash |
|||
ADMIN_EMAIL="[email protected]" |
|||
APP_NAME="ChimichangApp" |
|||
``` |
|||
|
|||
### Obtendo configurações do `.env` |
|||
|
|||
E então adicionar o seguinte código em `config.py`: |
|||
|
|||
=== "Pydantic v2" |
|||
|
|||
```Python hl_lines="9" |
|||
{!> ../../../docs_src/settings/app03_an/config.py!} |
|||
``` |
|||
|
|||
!!! dica |
|||
O atributo `model_config` é usado apenas para configuração do Pydantic. Você pode ler mais em <a href="https://docs.pydantic.dev/latest/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>. |
|||
|
|||
=== "Pydantic v1" |
|||
|
|||
```Python hl_lines="9-10" |
|||
{!> ../../../docs_src/settings/app03_an/config_pv1.py!} |
|||
``` |
|||
|
|||
!!! dica |
|||
A classe `Config` é usada apenas para configuração do Pydantic. Você pode ler mais em <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>. |
|||
|
|||
!!! info |
|||
Na versão 1 do Pydantic a configuração é realizada por uma classe interna `Config`, na versão 2 do Pydantic isso é feito com o atributo `model_config`. Esse atributo recebe um `dict`, para utilizar o autocomplete e checagem de erros do seu editor de texto você pode importar e utilizar `SettingsConfigDict` para definir esse `dict`. |
|||
|
|||
Aqui definimos a configuração `env_file` dentro da classe `Settings` do Pydantic, e definimos o valor como o nome do arquivo dotenv que queremos utilizar. |
|||
|
|||
### Declarando `Settings` apenas uma vez com `lru_cache` |
|||
|
|||
Ler o conteúdo de um arquivo em disco normalmente é uma operação custosa (lenta), então você provavelmente quer fazer isso apenas um vez e reutilizar o mesmo objeto settings depois, em vez de ler os valores a cada requisição. |
|||
|
|||
Mas cada vez que fazemos: |
|||
|
|||
```Python |
|||
Settings() |
|||
``` |
|||
|
|||
um novo objeto `Settings` é instanciado, e durante a instanciação, o arquivo `.env` é lido novamente. |
|||
|
|||
Se a função da dependência fosse apenas: |
|||
|
|||
```Python |
|||
def get_settings(): |
|||
return Settings() |
|||
``` |
|||
|
|||
Iriamos criar um novo objeto a cada requisição, e estaríamos lendo o arquivo `.env` a cada requisição. ⚠️ |
|||
|
|||
Mas como estamos utilizando o decorador `@lru_cache` acima, o objeto `Settings` é criado apenas uma vez, na primeira vez que a função é chamada. ✔️ |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="1 11" |
|||
{!> ../../../docs_src/settings/app03_an_py39/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.8+" |
|||
|
|||
```Python hl_lines="1 11" |
|||
{!> ../../../docs_src/settings/app03_an/main.py!} |
|||
``` |
|||
|
|||
=== "Python 3.8+ non-Annotated" |
|||
|
|||
!!! dica |
|||
Utilize a versão com `Annotated` se possível. |
|||
|
|||
```Python hl_lines="1 10" |
|||
{!> ../../../docs_src/settings/app03/main.py!} |
|||
``` |
|||
|
|||
Dessa forma, todas as chamadas da função `get_settings()` nas dependências das próximas requisições, em vez de executar o código interno de `get_settings()` e instanciar um novo objeto `Settings`, irão retornar o mesmo objeto que foi retornado na primeira chamada, de novo e de novo. |
|||
|
|||
#### Detalhes Técnicos de `lru_cache` |
|||
|
|||
`@lru_cache` modifica a função decorada para retornar o mesmo valor que foi retornado na primeira vez, em vez de calculá-lo novamente, executando o código da função toda vez. |
|||
|
|||
Assim, a função abaixo do decorador é executada uma única vez para cada combinação dos argumentos passados. E os valores retornados para cada combinação de argumentos são sempre reutilizados para cada nova chamada da função com a mesma combinação de argumentos. |
|||
|
|||
Por exemplo, se você definir uma função: |
|||
|
|||
```Python |
|||
@lru_cache |
|||
def say_hi(name: str, salutation: str = "Ms."): |
|||
return f"Hello {salutation} {name}" |
|||
``` |
|||
|
|||
Seu programa poderia executar dessa forma: |
|||
|
|||
```mermaid |
|||
sequenceDiagram |
|||
|
|||
participant code as Código |
|||
participant function as say_hi() |
|||
participant execute as Executar Função |
|||
|
|||
rect rgba(0, 255, 0, .1) |
|||
code ->> function: say_hi(name="Camila") |
|||
function ->> execute: executar código da função |
|||
execute ->> code: retornar o resultado |
|||
end |
|||
|
|||
rect rgba(0, 255, 255, .1) |
|||
code ->> function: say_hi(name="Camila") |
|||
function ->> code: retornar resultado armazenado |
|||
end |
|||
|
|||
rect rgba(0, 255, 0, .1) |
|||
code ->> function: say_hi(name="Rick") |
|||
function ->> execute: executar código da função |
|||
execute ->> code: retornar o resultado |
|||
end |
|||
|
|||
rect rgba(0, 255, 0, .1) |
|||
code ->> function: say_hi(name="Rick", salutation="Mr.") |
|||
function ->> execute: executar código da função |
|||
execute ->> code: retornar o resultado |
|||
end |
|||
|
|||
rect rgba(0, 255, 255, .1) |
|||
code ->> function: say_hi(name="Rick") |
|||
function ->> code: retornar resultado armazenado |
|||
end |
|||
|
|||
rect rgba(0, 255, 255, .1) |
|||
code ->> function: say_hi(name="Camila") |
|||
function ->> code: retornar resultado armazenado |
|||
end |
|||
``` |
|||
|
|||
No caso da nossa dependência `get_settings()`, a função não recebe nenhum argumento, então ela sempre retorna o mesmo valor. |
|||
|
|||
Dessa forma, ela se comporta praticamente como uma variável global, mas ao ser utilizada como uma função de uma dependência, pode facilmente ser sobrescrita durante os testes. |
|||
|
|||
`@lru_cache` é definido no módulo `functools` que faz parte da biblioteca padrão do Python, você pode ler mais sobre esse decorador no link <a href="https://docs.python.org/3/library/functools.html#functools.lru_cache" class="external-link" target="_blank">Python Docs sobre `@lru_cache`</a>. |
|||
|
|||
## Recapitulando |
|||
|
|||
Você pode usar o módulo Pydantic Settings para gerenciar as configurações de sua aplicação, utilizando todo o poder dos modelos Pydantic. |
|||
|
|||
- Utilizar dependências simplifica os testes. |
|||
- Você pode utilizar arquivos .env junto das configurações do Pydantic. |
|||
- Utilizar o decorador `@lru_cache` evita que o arquivo .env seja lido de novo e de novo para cada requisição, enquanto permite que você sobrescreva durante os testes. |
Loading…
Reference in new issue