Skip to main content

catworld-sdk

Cliente oficial Python para a API Catworld.

Instalação

pip install catworld-sdk

Início rápido

from catworld import CatworldClient

with CatworldClient("https://seu-catworld.exemplo.com", "cw_live_...") as client:
    result = client.query("SELECT * FROM banco_horas", dataset_id="<dataset-id>")
    df = result.dataframe
    print(df.head())

Autenticação

Passe a URL base do seu Catworld e um token de API gerado na interface:

client = CatworldClient(
    base_url="https://seu-catworld.exemplo.com",
    token="cw_live_...",
    timeout=30,  # timeout padrão das requisições HTTP (segundos)
)

Use como context manager (with) para fechar a conexão automaticamente, ou chame client.close() manualmente.

Métodos

query(sql, dataset_id=None, project_id=None, timeout=30, limit=10000)

Executa uma query SQL somente leitura.

Contexto de schema — passe dataset_id ou project_id para que o servidor resolva automaticamente tabelas não qualificadas para o schema correto:

# Com dataset_id: resolve tabelas do schema desse dataset
result = client.query("SELECT * FROM banco_horas", dataset_id="<dataset-id>")

# Com project_id: resolve tabelas de qualquer dataset do projeto
result = client.query("SELECT * FROM banco_horas", project_id="<project-id>")

# Sem contexto: você precisa qualificar manualmente com schema.tabela
result = client.query("SELECT * FROM meu_schema.banco_horas")

Se a mesma tabela existir em mais de um schema do contexto informado, o servidor retorna erro pedindo que você qualifique com schema.tabela.

Retorno:

query retorna um QueryResult: ele funciona como dict para compatibilidade, mas tambem expoe .rows, .columns e .dataframe.

result = client.query("SELECT * FROM banco_horas", dataset_id="<dataset-id>")

rows = result.rows
columns = result.columns
df = result.dataframe

Para usar .dataframe, instale o extra com pandas:

pip install "catworld-sdk[dataframe]"

Parâmetros:

Parâmetro Tipo Padrão Descrição
sql str — Query SQL (apenas SELECT/WITH)
dataset_id str None ID do dataset para resolver schemas
project_id str None ID do projeto para resolver schemas
timeout int 30 Timeout da query no servidor (segundos, máx. 120)
limit int 10000 Número máximo de linhas retornadas

upload(path, dataset_id, mode="replace", key_column=None, table_id=None, column_types=None, wait=False, poll_interval=2, timeout=None, skip_preflight=False)

Envia um arquivo para um dataset.

result = client.upload(
    "dados.xlsx",
    dataset_id="<dataset-id>",
    table_id="<table-id>",  # recomendado sempre que o nome do arquivo pode variar
    mode="replace",         # "replace" sobrescreve, "append" adiciona, "upsert" atualiza/insere
    key_column="id",        # obrigatório para mode="upsert"
    wait=True,               # recomendado — veja abaixo
)
print(result["status"], result["rowCount"])

⚠️ Por padrão (wait=False) este método NÃO informa se a importação deu certo. Ele retorna assim que o arquivo é enfileirado — preview e import rodam em background, e um erro de schema, por exemplo, só aparece depois, olhando get_upload(upload_id) ou o painel. Recomendamos fortemente wait=True em qualquer script que precise saber o resultado: com isso, o método bloqueia até o import terminar (poll em GET /api/v1/uploads/{id}) e levanta UploadError com a mensagem de erro real do servidor se falhar (ex: "Schema incompatível. Esperado: ...") — em vez de falhar em silêncio.

O arquivo pode ser .csv, .xlsx ou .xls.

Pré-checagens automáticas (desde 0.6.0) — sempre que table_id é informado e mode é "append" ou "upsert", upload() valida localmente antes de enviar qualquer byte:

  • mode="upsert" sem key_column falha na hora, sem gastar banda.
  • Para arquivos .csv, os cabeçalhos são comparados com o schema físico da tabela (mesma checagem de check_append_compat) — pega o erro mais comum de append/upsert de graça. Para .xlsx/.xls essa parte é pulada (chame check_append_compat manualmente se quiser essa cobertura).
  • Em mode="upsert", também confere que key_column existe na tabela e ainda é única nela hoje (check_upsert_ready) — sem isso, uma chave que já não é única na tabela de destino (por exemplo, populada antes por um append) faria o upsert mesclar essas duplicatas silenciosamente pra sempre.

Passe skip_preflight=True para pular essas checagens (por exemplo, se alguma delas der falso positivo no seu caso).

Parâmetros:

Parâmetro Tipo Padrão Descrição
path str | Path — Caminho do arquivo local
dataset_id str — ID do dataset de destino
mode str "replace" Modo de importação: replace, append ou upsert
key_column str None Coluna chave para mode="upsert"
table_id str None Tabela de destino. Sem isso, o nome vem do nome do arquivo — passe sempre que o nome puder variar entre execuções
column_types dict[str, str] None Sobrepõe o tipo SQL auto-detectado para colunas específicas — veja abaixo
wait bool False Recomendado True. Bloqueia até o import terminar e levanta exceção se falhar
poll_interval float 2 Intervalo de polling em segundos (só com wait=True)
timeout float None Tempo máximo de espera em segundos (só com wait=True); None = sem limite
skip_preflight bool False Pula as pré-checagens locais descritas acima

Sobrepondo o tipo de uma coluna (column_types)

O servidor infere o tipo SQL de cada coluna olhando uma amostra do arquivo a cada upload — isso pode divergir entre execuções (ex: uma coluna maiormente vazia vira texto numa carga e data em outra), quebrando append/upsert com Schema incompatível mesmo sem mudança real de dado. Use column_types para fixar o tipo de uma coluna manualmente:

client.upload(
    "vendas.csv",
    dataset_id="<dataset-id>",
    table_id="<table-id>",
    mode="append",
    column_types={"data_venda": "DATE", "valor": "DECIMAL(18,2)"},
)

Tipos aceitos: BIGINT, DECIMAL(p,s) (ex: DECIMAL(18,2)), DATE, DATETIME2, TIME, NVARCHAR(MAX). A chave é o nome da coluna (cabeçalho original ou já normalizado); um override com nome ou tipo desconhecido é ignorado silenciosamente pelo servidor (não derruba o upload).

Erros comuns e como evitá-los:

Erro Causa Como evitar
Schema incompatível. Esperado: ...; atual: ... append/upsert com colunas diferentes (nome, ordem ou presença) da tabela já existente A partir de 0.6.0 isso é pego automaticamente pela pré-checagem, antes do upload; garanta cabeçalhos estáveis entre execuções
Schema incompatível: tipos da tabela atual diferem do arquivo Uma coluna que antes era só números agora tem texto/decimal (ou vice-versa) — o tipo é reinferido a cada arquivo Use column_types para fixar o tipo da coluna, ou garanta que a exportação de origem produza o mesmo tipo sempre
Coluna-chave '...' já não é única na tabela de destino mode="upsert" com uma key_column que já tem valores duplicados na tabela (ex: populada antes por um append) Pego automaticamente pela pré-checagem; limpe as duplicatas existentes na tabela antes de retomar o upsert
Arquivo contém chaves duplicadas para upsert na coluna "..." O arquivo enviado tem a própria chave duplicada — a mensagem já traz uma amostra dos valores e quantas vezes cada um se repete Deduplique o arquivo antes de subir, usando os valores listados no erro
XLSX_TOO_LARGE Arquivo .xlsx/.xls acima do limite (XLSX é lido inteiro em memória) Exporte como .csv para arquivos grandes
Upload nunca sai de RETRYING/FAILED sempre com o mesmo erro Erro estrutural (schema), não transitório — retry não resolve Corrija o schema (veja acima); um mode="replace" único recria a tabela do zero com as colunas novas, mas descarta dados que não estejam no arquivo atual

get_upload(upload_id)

Consulta o estado atual de um upload — útil com wait=False, ou pra checar um upload antigo.

upload = client.get_upload(upload_id)
print(upload["status"], upload.get("errorMessage"))

check_append_compat(dataset_id, table_id, headers)

Confere, antes de enviar o arquivo, se os cabeçalhos batem com o schema físico da tabela — pega o erro mais comum de append/upsert sem gastar tempo/banda subindo o arquivo primeiro. Aceita cabeçalhos crus (com acento, espaço, etc.) e normaliza internamente do mesmo jeito que o servidor.

headers = ["Período Início", "Período Fim", "Relatório", "Posto", "Produto", "Total Abastecido"]
client.check_append_compat(dataset_id, table_id, headers)  # levanta ValidationError se não bater
client.upload("relatorio.xlsx", dataset_id=dataset_id, table_id=table_id, mode="append")

Só compara nomes e ordem de coluna — não valida tipo de dado (isso só o servidor descobre lendo o arquivo de verdade). Se a tabela ainda não existe (upload novo), não levanta erro.

Desde 0.6.0, upload() já chama isso automaticamente para arquivos .csv (veja "Pré-checagens automáticas" acima) — use este método diretamente só para .xlsx/.xls, ou pra checar antes de decidir o table_id.


check_upsert_ready(dataset_id, table_id, key_column)

Confere, antes de enviar o arquivo, se key_column está pronta para mode="upsert": existe na tabela e ainda não tem valores duplicados nela. O servidor só valida chave duplicada no arquivo novo — nunca na tabela já existente — então uma chave que já não é única na tabela de destino faria o upsert mesclar essas duplicatas silenciosamente pra sempre.

client.check_upsert_ready(dataset_id, table_id, "documento")  # levanta ValidationError se a chave não existir ou já tiver duplicata
client.upload("clientes.csv", dataset_id=dataset_id, table_id=table_id, mode="upsert", key_column="documento")

Desde 0.6.0, upload() já chama isso automaticamente quando mode="upsert" (veja "Pré-checagens automáticas" acima) — use este método diretamente só se quiser rodar a checagem separadamente do upload, ou com skip_preflight=True.


projects()

Lista todos os projetos acessíveis pelo token.

projects = client.projects()
for p in projects:
    print(p["id"], p["name"])

datasets()

Lista todos os datasets acessíveis pelo token.

datasets = client.datasets()
for d in datasets:
    print(d["id"], d["name"], d["schemaName"])

tables(dataset_id)

Lista as tabelas de um dataset com colunas e tipos.

tables = client.tables("<dataset-id>")
for t in tables:
    origem = t.get("source")
    print(t["name"], origem["mode"] if origem else "catworld")

sources(dataset_id)

Lista as fontes conectadas de um dataset.

sources = client.sources("<dataset-id>")
for s in sources:
    print(s["id"], s["name"], s["mode"], s["connection"]["name"])

rows(table_id, limit=100)

Retorna as primeiras linhas de uma tabela pelo ID. Funciona tanto para tabelas materializadas no Catworld quanto para tabelas live; se a tabela for live, o servidor consulta o Postgres da fonte.

rows = client.rows("<table-id>", limit=50)
print(rows)

Tabelas live em query

Quando voce passa dataset_id, o SDK trata tabelas live como tabelas do dataset. Se a SQL referencia uma tabela live conhecida, query chama a fonte Postgres correta por baixo.

result = client.query("SELECT * FROM clientes", dataset_id="<dataset-id>")
df = result.dataframe
print(df.head())

Queries que misturam uma tabela live com tabela interna/extract nao sao roteadas automaticamente, porque elas precisariam cruzar bancos diferentes. Nesses casos, materialize a fonte como extract ou consulte a fonte live separadamente.


live_query(source_id, sql=None, timeout=30, limit=10000)

Executa uma consulta diretamente na fonte live Postgres. Normalmente prefira query(..., dataset_id=...); este metodo fica disponivel para casos em que voce ja tem o ID da fonte. Se sql for omitido, o servidor usa a consulta/tabela configurada na fonte.

result = client.live_query("<source-id>", "SELECT * FROM clientes LIMIT 100")
print(result.dataframe.head())

refresh_source(source_id)

Enfileira uma atualização de uma fonte do tipo cópia no Catworld.

job = client.refresh_source("<source-id>")
print(job["id"])

Exceções

Exceção Quando ocorre
catworld.ConnectionError Falha de rede ou erro inesperado do servidor
catworld.AuthenticationError Token inválido, expirado ou revogado (HTTP 401)
catworld.PermissionDeniedError Token sem permissão para a operação (HTTP 403)
catworld.ValidationError Dados inválidos, SQL inseguro ou upload malformado (HTTP 400/422)
catworld.QueryTimeoutError Query ou importação excedeu o tempo limite
from catworld import CatworldClient
from catworld.exceptions import PermissionDeniedError, QueryTimeoutError

try:
    result = client.query("SELECT * FROM banco_horas", dataset_id="<id>")
except PermissionDeniedError:
    print("Token sem acesso a este dataset")
except QueryTimeoutError:
    print("Query demorou demais, tente limitar com TOP ou WHERE")

Metadata

Release files for catworld-sdk 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for catworld-sdk 0.6.0
File Size Uploaded Provenance
catworld_sdk-0.6.0.tar.gz 19.9 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for catworld-sdk 0.6.0
File Interpreter ABI Platform
catworld_sdk-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 37.8 kB

Release files / catworld_sdk-0.6.0.tar.gz

Download URL catworld_sdk-0.6.0.tar.gz
Size 19.9 kB
Tags Source
SHA-256 checksum
How to use checksums
8bff7ce81e4e17a55901f1596a33d1c71877777bccda4e2042679f9a4f38697a
BLAKE2b-256 checksum
How to use checksums
b7c285ee3fb842b32bb706e9b9e263d04b44140a4afe3b517d5321ef47a51da6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.7

Release files / catworld_sdk-0.6.0-py3-none-any.whl

Download URL catworld_sdk-0.6.0-py3-none-any.whl
Size 17.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
669eac08aaf66526648cc9a47d89326e6e11eaf32c5967da1ec2fce108844b62
BLAKE2b-256 checksum
How to use checksums
1c1eebf1058ba5055759376804bd21aa688cc01c82357d69df2870fb314f31b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.7

Security for catworld-sdk 0.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page