Manipule elegantemente variáveis ​​de ambiente em Python com Pydantic

May 02 2023
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.
Imagem gerada por Jacob Ferus

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 Docker

Nesse 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 -> stre 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 .envarquivo. 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 BaseModelclasse, você pode validar e pós-processar valores usando o Field()método e @validatoro 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 .