Skip to content

djosino/clicksign-python-sdk

Repository files navigation

Clicksign Python SDK

versão CI Python TestPyPI PyPI licença documentação API v3

Cliente Python para a Clicksign API v3 (JSON:API). Requer Python >= 3.10, sem dependências de runtime (apenas stdlib). Documentação detalhada em docs/.


Índice


Fluxo completo: docs/WORKFLOW.md — envelope → documento → signatário → ativação passo a passo.

Examples: docs/examples/ — receitas prontas para retry, webhooks, multi-tenant, observabilidade e connection pool.

Contrato do SDK: docs/SDK_CONTRACT.md — timeouts, retry, JSON:API, erros e paginação.

Observabilidade: docs/OBSERVABILITY.md — hooks, log, correlation id, structlog, OpenTelemetry, PII.


Instalação

Versão atual: 0.1.2 (disponível no TestPyPI; PyPI de produção em breve).

pip install --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  clicksign-python-sdk==0.1.2

Quando publicado no PyPI de produção:

pip install clicksign-python-sdk==0.1.2

O módulo importado continua sendo clicksign (import clicksign).

Extras opcionais:

pip install clicksign-python-sdk[httpx]   # connection pooling (alto QPS)
pip install clicksign-python-sdk[async]   # AsyncClicksignClient (requer httpx)

Configuração

Dois padrões principais — escolha com base em como sua aplicação gerencia credenciais:

Padrão Quando usar
configure() + resources Uma API key por processo; scripts; workers simples
ClicksignClient Múltiplas keys; dependências explícitas; código novo

Configuração global (single tenant)

import clicksign
from clicksign import Envelope, Document

clicksign.configure(
    api_key="YOUR_API_KEY",
    environment="sandbox",  # or "production"
)

envelopes = Envelope.list()
envelope = Envelope.create(name="Contrato", locale="pt-BR")
document = Document.create(
    envelope.id,
    filename="contrato.pdf",
    content_base64="...",
)

Cliente explícito (ClicksignClient)

from clicksign import ClicksignClient

client = ClicksignClient(api_key="YOUR_API_KEY", environment="sandbox")

envelopes = client.notarial.envelopes.list()
envelope = client.notarial.envelopes.create(name="Contrato", locale="pt-BR")

# Alias direto (shorthand)
envelope = client.envelopes.retrieve(envelope.id)
envelope.update(name="Contrato atualizado")

Multi-conta

Para apps multi-tenant onde cada requisição/job usa uma API key diferente, use Services para vincular um cliente à thread atual:

from clicksign import Envelope, Services

tenant = Services(api_key="TENANT_API_KEY", environment="sandbox")

with tenant.use():
    Envelope.list()  # roteado pelo cliente deste tenant

Services.use() define cliente HTTP e bulk para a thread. Em runtime async/fiber, prefira ClicksignClient ou AsyncClicksignClient explícito — contexto thread-local pode não se propagar.


Timeouts, retry e instrumentação

Timeouts por requisição

from clicksign import ClicksignClient, RequestOptions

client = ClicksignClient(api_key="YOUR_API_KEY", environment="sandbox")

client.envelopes.create(
    name="Contrato",
    options=RequestOptions(
        read_timeout=30.0,
        open_timeout=5.0,
    ),
)

Timeouts separados: open_timeout (TCP connect), write_timeout (envio do corpo), read_timeout (resposta).

Retry

Padrão 3 retries com full-jitter exponential backoff. Desative por chamada:

client.envelopes.retrieve("uuid", options={"max_retries": 0})

BulkRequirement.create retenta apenas TimeoutError — operações atômicas não são idempotentes.

Correlação de requisições

from clicksign import RequestOptions, correlation_id

client.envelopes.retrieve(
    "uuid",
    options=RequestOptions(headers=correlation_id("req-123")),
)

Em falhas, use error.request_id (header X-Request-Id) para tickets de suporte da Clicksign.

Hooks de instrumentação

import clicksign

clicksign.on_request(lambda e: print(e["method"], e["path"], e["status"]))
clicksign.on_retry(lambda e: print(e["attempt"], e["wait_ms"]))
clicksign.on_error(lambda e: print(e["error"]))

Veja docs/OBSERVABILITY.md para exemplos com structlog, OpenTelemetry e Prometheus, e como evitar PII em handlers on_error.

Log HTTP integrado

export CLICKSIGN_LOG=debug   # ou info, warn, error
clicksign.configure(log="debug")
# Usa stdlib logger "clicksign"; header Authorization nunca é logado.

Início rápido

from clicksign import ClicksignClient

client = ClicksignClient(api_key="YOUR_API_KEY", environment="sandbox")

# Criar envelope
envelope = client.notarial.envelopes.create(name="Contrato NDA", locale="pt-BR")

# Adicionar documento
doc = client.notarial.documents.create(
    envelope.id,
    filename="nda.pdf",
    content_base64="...",
)

# Adicionar signatário
signer = client.notarial.signers.create(
    envelope.id,
    name="Ana Souza",
    email="ana@example.com",
    has_documentation=True,
)

# Requisito de assinatura (atomic)
client.notarial.bulk_requirements.create(
    envelope.id,
    block=lambda ops: ops.add_agree(
        signer_id=signer.id,
        document_id=doc.id,
        role="sign",
    ),
)

# Ativar envelope
envelope.update(status="running")

Fluxo de assinatura (notarial)

1. Envelope

envelope = client.notarial.envelopes.create(name="Contrato", locale="pt-BR")
envelope.update(deadline_at="2025-12-31T23:59:59Z")

2. Documento

import base64

doc = client.notarial.documents.create(
    envelope.id,
    filename="contrato.pdf",
    content_base64=base64.b64encode(open("contrato.pdf", "rb").read()).decode(),
)

3. Signatário

signer = client.notarial.signers.create(
    envelope.id,
    name="João Silva",
    email="joao@example.com",
    has_documentation=True,
    documentation="123.456.789-09",
)

4. Requisitos de assinatura (bulk atômico)

client.notarial.bulk_requirements.create(
    envelope.id,
    block=lambda ops: (
        ops.add_agree(signer_id=signer.id, document_id=doc.id, role="sign"),
        ops.add_provide_evidence(signer_id=signer.id, document_id=doc.id, auth="email"),
    ),
)

5. Ativar

envelope.update(status="running")
# ou: Envelope.activate(envelope.id)  — POST /envelopes/:id/activate

6. Notificar signatários

from clicksign.resources.notarial.signer import Signer

# Um signatário (envelope_id e signer_id obrigatórios)
Signer.notify(envelope.id, signer.id, message="Seu contrato está disponível para assinatura.")

# Todos os pendentes do envelope
envelope.notify(message="Seu contrato está disponível para assinatura.")

Detalhes: docs/WORKFLOW.md (seção 6).

7. Monitorar eventos

from clicksign.resources.notarial.envelope import Envelope

for event in Envelope.list_events(envelope.id):
    print(event.name, event.created)

# Eventos por documento (ex.: read, sign, custom)
from clicksign.resources.notarial.document import Document

for event in Document.list_events(doc.id, envelope_id=envelope.id):
    print(event.name, event.data)

# Callbacks em tempo real: docs/examples/03-webhooks.md

Filtros, ordenação e paginação

# QueryProxy chain
drafts = (
    client.envelopes
    .filter(status="draft")
    .order("-created_at")
    .per(20)
    .with_includes("folder")
    .to_list()
)

# Auto-paginação (itera todas as páginas)
for envelope in client.envelopes.filter(status="running"):
    print(envelope.id)

# page() + per() explícitos
page1 = client.envelopes.page(1).per(10).to_list()

per() máximo 50 — veja docs/PAGINATION.md.

JSON:API sideload (included)

envelopes = client.envelopes.with_includes("folder").to_list()
print(envelopes[0].folder.name)  # sem chamada HTTP extra

Outros recursos

Namespace Resource
client.notarial.envelopes Envelope
client.notarial.documents Document
client.notarial.signers Signer
client.notarial.requirements Requirement
client.notarial.bulk_requirements BulkRequirement
client.notarial.signature_watchers SignatureWatcher
— (nested) EventEnvelope.list_events, Document.list_events, Event.create_for_document
client.webhooks Webhook
client.users User
client.templates / client.template_fields Template, TemplateField
client.memberships / client.groups Membership, Group
client.folders Folder
client.access_control_lists AccessControlList
client.envelope_bulk_creations EnvelopeBulkCreation
client.acceptance_term.whatsapps Whatsapp
client.auto_signature.terms Term

Endpoints sem resource dedicado:

raw = client.raw_request("get", "/beta/feature")
envelope = client.deserialize(raw, Envelope)
print(envelope.last_response.status)

User-Agent e identificação da aplicação

clicksign.set_app_info("My CRM", "2.1.0", "https://example.com")
# User-Agent: clicksign-python/x.y.z Python/3.10.x My_CRM/2.1.0

Telemetria do provider (opt-in)

clicksign.configure(enable_telemetry=True)

Envia métricas de latência anonimizadas (sem API keys nem corpos). Desativado por padrão.


Tratamento de erros

from clicksign.errors import (
    AuthenticationError,
    PermissionError,
    NotFoundError,
    ValidationError,
    RateLimitError,
    ServerError,
    TimeoutError,
)

try:
    client.envelopes.create(name="")
except ValidationError as err:
    print(err.message)          # primeiro detalhe (compatível)
    print(err.error_code)       # código do primeiro erro
    print(err.source_pointer)   # ex: /data/attributes/name
    for api_error in err.api_errors:
        print(api_error.pointer, api_error.detail)
except RateLimitError as err:
    print(err.rate_limit_reset)
except ServerError as err:
    if err.retryable:
        ...
Exceção Status HTTP
AuthenticationError 401
PermissionError 403
NotFoundError 404
ValidationError 400, 422
RateLimitError 429
ServerError 5xx
TimeoutError timeout de rede

Ambientes

Ambiente Base URL
sandbox https://sandbox.clicksign.com/api/v3
production https://app.clicksign.com/api/v3
client = ClicksignClient(api_key="...", environment="production")

Async (FastAPI, asyncio)

Requer pip install clicksign-python-sdk[async]. Receita completa: docs/examples/13-async-fastapi.md.

import asyncio
from clicksign import AsyncClicksignClient

async def main():
    async with AsyncClicksignClient(api_key="YOUR_API_KEY", environment="sandbox") as client:
        envelopes = await client.notarial.envelopes.list()
        async for env in client.envelopes.filter(status="draft"):
            print(env.id)
        envelope = await client.envelopes.retrieve("uuid")
        await envelope.update_async(status="running")
        # Ativar: await client.notarial.envelopes.activate(envelope.id)

asyncio.run(main())
  • Não use Services.use() no asyncio — use AsyncClicksignClient explícito.
  • Paginação: .page(n) só vale com .first(); .to_list() auto-pagina desde a página 1 — docs/PAGINATION.md.

HTTP transport e connection pool

Padrão: UrllibHTTPClient (stdlib, sem connection pool — um handshake TCP/TLS por requisição). Adequado para scripts e baixo QPS.

Alto QPS: instale clicksign-python-sdk[httpx] e injete um cliente compartilhado:

from clicksign import ClicksignClient, HttpxHTTPClient

http = HttpxHTTPClient()  # um por processo / worker
client = ClicksignClient(api_key="...", environment="production", http_client=http)

AsyncClicksignClient usa httpx com connection pool no event loop.

Receita singleton: docs/examples/12-http-connection-pool.md.


Limitações e produção

  • UrllibHTTPClient não reutiliza conexões — use HttpxHTTPClient sob carga.
  • Services.use() usa threading.local() — incompatível com asyncio/trio.
  • Operações bulk atômicas retentam apenas TimeoutError (não são idempotentes).
  • Includes aninhados dependem do que a API retorna; tipos desconhecidos fazem fallback para Resource base.

Veja docs/examples/08-production-limitations.md.


Desenvolvimento

pip install -e ".[dev]"
pytest
pytest --cov=clicksign --cov-report=term-missing
ruff format .
ruff check .
mypy

Licença

MIT — veja LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages