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"semkey_columnfalha na hora, sem gastar banda.- Para arquivos
.csv, os cabeçalhos são comparados com o schema físico da tabela (mesma checagem decheck_append_compat) — pega o erro mais comum de append/upsert de graça. Para.xlsx/.xlsessa parte é pulada (chamecheck_append_compatmanualmente se quiser essa cobertura). - Em
mode="upsert", também confere quekey_columnexiste 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 umappend) 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)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| catworld_sdk-0.6.0.tar.gz | 19.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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.