Manipule elegantemente variáveis de ambiente em Python com Pydantic

Digitar em Python não é um requisito, mas ao lidar com entradas de diferentes fontes, pode ser um verdadeiro alívio. As variáveis de ambiente são uma abordagem obrigatória para injetar variáveis em programas, especialmente em implantações no mundo real, pois são usadas em quase todos os lugares. Já escrevi um artigo sobre como você pode lidar com essas variáveis em Python e Docker:
Variáveis de ambiente em Python e DockerNesse artigo, usei o método básico de acessar variáveis de ambiente com o os
-module:
# get the environment variable MY_ENV_VARIABLE
# or return None if it doesn't exist
my_env_variable = os.getenv("MY_ENV_VARIABLE")
Entra Pydantic
Pydantic é uma biblioteca Python que permite a validação de dados usando dicas de tipo em Python. É comumente usado em conjunto com a estrutura da Web FastAPI , onde é usado para validar os dados das solicitações recebidas. Além disso, ele também pode ser usado em outras situações, uma das quais exploraremos aqui (ou seja, variáveis de ambiente).
Se você já usou dicas de tipo antes, será fácil começar a usar o Pydantic. Vamos começar instalando a biblioteca:
pip install pydantic
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class SomeData(BaseModel):
name: str
age: int
@app.post("/data/")
async def post_data(data: SomeData):
print(data)
print(data.dict())
Este servidor web é iniciado com uvicorn main:app
(supondo que o nome do arquivo seja main.py
). Vamos enviar alguns pedidos para ver como o Pydantic funciona:
import requests
requests.post("http://localhost:8000/data/", json={"name": "John", "age": 42})
# => SERVER OUTPUT:
# name='John' age=42
# {'name': 'John', 'age': 42}
requests.post("http://localhost:8000/data/", json={"name": 5, "age": "42"})
# => SERVER OUTPUT:
# name='5' age=42
# {'name': '5', 'age': 42}
requests.post("http://localhost:8000/data/", json={"name": "John", "age": { "foo": "bar"}})
# => SERVER ERROR:
# HTTP Status 422 Unprocessable Entity
Na segunda solicitação, os valores não são os tipos corretos, mas são convertidos para o formato correto, ou seja, int -> str
e str -> int
. Se esse comportamento for indesejável, é possível usar tipos estritos .
Por fim, na última requisição, os dados são não processáveis (não é possível converter o objeto JSON para int) e o status HTTP 422, entidade não processável, é retornado.
Passando do FastAPI, podemos usar a mesma funcionalidade no caso de gerenciamento de variáveis de ambiente. Neste caso, em vez de BaseModel
, BaseSettings
é usado.
Aqui vou carregar variáveis de um .env
arquivo. Para obter mais informações sobre esse arquivo, confira meu artigo anterior . O arquivo contém o seguinte:
NAME = "John"
AGE = 42
pip install python-dotenv
from pydantic import BaseSettings
class Envs(BaseSettings):
name: str
age: int
class Config:
env_file = '.env'
envs = Envs()
print(envs)
# => name='John' age=42
from pydantic import BaseSettings
class Envs(BaseSettings):
name: str
age: int
envs = Envs(_env_file='.env')
print(envs)
# => name='John' age=42
...
class Config:
case_sensitive = True
tipos complexos
Vamos criar um novo .env
-file ( .env2
):
my_array = '["hi", "how", "are", "you?"]'
my_object = '{"some_key": "some_value", "another_key": ["a", "b", "c"]}'
nested = '{"a": "a", "b": 1}'
from pydantic import BaseSettings, BaseModel
from typing import List
class NestedEnvs(BaseModel):
a: str
b: int
class Envs(BaseSettings):
my_array: List[str]
my_object: dict
nested: NestedEnvs
envs = Envs(_env_file='.env2')
print(envs)
# => my_array=['hi', 'how', 'are', 'you?'] my_object={'some_key': 'some_value', 'another_key': ['a', 'b', 'c']} nested=NestedEnvs(a='a', b=1)
Tipos de dados complexos são interpretados como JSON, portanto, você deve colocar o objeto JSON entre aspas simples e, em seguida, usar aspas duplas para valores e chaves dentro do objeto JSON. Consequentemente, isso não funcionará:
my_array = "['hi', 'how', 'are', 'you?']"
class Envs(BaseSettings):
...
class Config:
env_nested_delimiter = '__'
Em seguida, estenda o .env
-file:
my_array = '["hi", "how", "are", "you?"]'
my_object = '{"some_key": "some_value", "another_key": ["a", "b", "c"]}'
my_object__some_other_property = 'some_other_value'
nested = '{"a": "a", "b": 1}'
...
my_object={'some_key': 'some_value', 'another_key': ['a', 'b', 'c'], 'some_other_property': 'some_other_value'}
...
Valores padrão
Se uma variável de ambiente estiver faltando, é trivial adicionar um valor padrão:
from pydantic import BaseSettings
class Envs(BaseSettings):
some_property: str = "default value"
envs = Envs()
print(envs)
# => some_property='default value'
Assim como a BaseModel
classe, você pode validar e pós-processar valores usando o Field()
método e @validator
o decorador. Vamos dar um exemplo:
from pydantic import BaseSettings, Field, validator
class Envs(BaseSettings):
between_0_and_10: int = Field(..., ge=0, le=10)
prime: int
@validator('prime')
def is_prime(cls, n: int) -> int:
if n <= 1:
raise ValueError("Prime numbers must be greater than 1")
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
raise ValueError(f"{n} is not a prime number.")
return n
envs = Envs(_env_file='.env3')
print(envs)
Conclusão
Embora as variáveis de ambiente sejam originalmente armazenadas como strings, muitas vezes é desejável convertê-las em tipos de dados específicos ou mais complexos. Pydantic fornece uma maneira perfeita de fazer exatamente isso, resultando em uma abordagem concisa e padronizada para gerenciar variáveis de ambiente. Há muito mais no Pydantic do que foi abordado aqui, e recomendo que você leia a documentação oficial para saber mais.
Se você gostou deste artigo:
- Palmas, isso vai me ajudar a entender o que meus leitores gostam e querem mais.
- Siga ou assine, se quiser ler meus próximos artigos, novos toda semana!
- Se você está procurando mais conteúdo, confira minhas listas de leitura em AI , Python ou Data Science .