andco-sdk · Python

Cliente confidencial, OAuth y API de recursos para backends de Python.

bash
uv add andco-sdk
bash
pip install andco-sdk
bash
poetry add andco-sdk

Requiere Python 3.11 o superior. Es un SDK de backend: cliente confidencial, sin navegador y sin presentaciones.

Sigue el mismo vocabulario que el SDK de JavaScript pero es idiomático en Python: lanza excepciones en vez de devolver resultados, usa snake_case y es asíncrono sobre httpx.

AndcoClient

python
AndcoClient(
    *,
    client_id: str,
    client_secret: str | None = None,
    auth_endpoint: str = "https://auth.andco.cl",
    api_endpoint: str = "https://api.andco.cl",
    api_version: str = "_",
    resource: str = "https://api.andco.cl",
    transport: TransportMode = "auto",
    http_transport: httpx.AsyncBaseTransport | None = None,
)

Un valor: configuración más capacidad, construido de forma síncrona y sin sesión. Una instancia de módulo es segura entre peticiones concurrentes precisamente porque no existe el campo donde el token de una persona podría filtrarse a la respuesta de otra.

http_transport existe para los tests: permite inyectar un transporte de httpx sin tocar la red.

with_session

python
with_session(credentials: AndcoSession | str | Credentials) -> BoundAndcoClient

Acepta una sesión, un token suelto o cualquier cosa que resuelva uno. El token suelto está aceptado porque es de lejos la forma más común en un servidor, y exigir un envoltorio ponía las mismas tres líneas al inicio de cada manejador.

BoundAndcoClient tiene rest y la configuración de su dueño.

AndcoOAuth

Disponible como client.oauth.

python
async create_authorization_request(
    *,
    redirect_to: str,
    scopes: list[str] | None = None,
    resource: str | None = None,
    org_id: str | None = None,
    authorization_details: list[dict] | None = None,
    login_hint: str | None = None,
    state: str | None = None,
) -> AndcoAuthorizationRequest

async exchange_callback(*, callback_url: str, request: AndcoAuthorizationRequest) -> AndcoSession
async refresh(refresh_token: str) -> AndcoSession
def endpoint(name: str) -> str
property is_confidential: bool

La decisión entre petición directa y PAR ocurre dentro de create_authorization_request, así que quien llama nunca la toma. Se usa PAR cuando la petición lleva login_hint o authorization_details, o cuando la URL quedaría demasiado larga.

exchange_callback valida el callback contra la transacción —origen, state, antigüedad— antes de intercambiar nada.

AndcoSession

python
@dataclass(frozen=True, slots=True)
class AndcoSession:
    access_token: str
    refresh_token: str | None
    expires_at: float          # segundos desde la época Unix
    scopes: tuple[str, ...] = ()
    token_type: str = "bearer"
    user: AndcoUser | None = None

    @property
    def is_expired(self) -> bool: ...

is_expired incluye un margen, de modo que un token a punto de vencer se renueva antes de salir a la red.

AndcoAuthorizationRequest

python
@dataclass(frozen=True, slots=True)
class AndcoAuthorizationRequest:
    authorization_url: str
    code_verifier: str
    state: str
    redirect_to: str

Es lo que hay que guardar entre la redirección y la vuelta. El code_verifier no puede viajar en la URL y no se puede reconstruir.

AndcoRest

python
async get(path: str, *, model: type[Model] | None = None, **params) -> Any
async post(path: str, body: Any = None, *, model: type[Model] | None = None) -> Any
async request(...) -> Any

model valida y tipa la respuesta cuando quieres un objeto en vez de un diccionario.

Errores

python
class AndcoError(Exception):
    code: str
    status_code: int | None
    details: dict | None

class AndcoAuthorizationError(AndcoError): ...

Excepciones y no resultados: es lo que se espera de una biblioteca de Python, y try / except ya es el mecanismo del lenguaje para esto.

Webhooks

python
from andco_sdk import (
    ANDCO_WEBHOOK_EVENT_ID_HEADER,
    ANDCO_WEBHOOK_SIGNATURE_HEADER,
    ANDCO_WEBHOOK_TIMESTAMP_HEADER,
    verify_webhook,
)

trusted = verify_webhook(
    body=raw,
    signature=headers.get(ANDCO_WEBHOOK_SIGNATURE_HEADER),
    event_id=headers.get(ANDCO_WEBHOOK_EVENT_ID_HEADER),
    timestamp=headers.get(ANDCO_WEBHOOK_TIMESTAMP_HEADER),
    secret=settings.ANDCO_WEBHOOK_SECRET,
)

La comparación es de tiempo constante y una entrega fuera de la ventana de tolerancia se rechaza aunque su firma sea válida. body tiene que ser los bytes crudos.

Modelos

python
from andco_sdk import (
    ANDCO_RESOURCE,
    AndcoCompanyAddress,
    AndcoCompanyIdentity,
    AndcoGrantAuthorization,
)

Tipos de los recursos de empresa y de las contribuciones de autorización, para que no los redeclares.

Qué no hace

No presenta autorizaciones, no abre ventanas, no guarda sesión y no tiene almacenamiento. Todo eso es del lado del navegador, y un backend de Python no está ahí.