Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,27 @@ for sale in client.sales.history_autopaginate(buyer_name="Paula"):

The iterator stops when there are no more pages — no token management, no loop conditions.

### Processing pages individually

If you need to act at the end of each page — flush a batch to a database, save a checkpoint, update a progress bar — use the single-page method and control the loop yourself:

```python
page_token = None
while True:
page = client.sales.history(start_date=1700000000000, page_token=page_token)

for sale in page.items:
process(sale)

save_checkpoint(page_token) # per-page side effect

if not page.page_info or not page.page_info.next_page_token:
break
page_token = page.page_info.next_page_token
```

Use `*_autopaginate` when you only need to iterate all records. Use the manual loop when you need to act between pages.

---

## Sandbox Mode
Expand Down Expand Up @@ -418,7 +439,19 @@ Exception hierarchy:
| `APIStatusError` | other | Unexpected HTTP status |
| `HotmartError` | — | Base class for all SDK errors |

The SDK retries automatically on transient errors (5xx, 429) with exponential backoff (`0.5 × 2^attempt + jitter`, cap 30s). Configure via `max_retries`:
### Retry behavior

Not all errors are equal — the SDK handles them differently before raising:

| Behavior | Exceptions |
|----------|-----------|
| **Retried with exponential backoff** — raised only after all retries are exhausted | `RateLimitError` (429), `InternalServerError` (500, 502, 503) |
| **Triggers token refresh + one automatic retry** — never raised for an expired token | `AuthenticationError` from 401 |
| **Raised immediately, no retry** | `BadRequestError` (400), `AuthenticationError` from 403, `NotFoundError` (404), `APIStatusError` |

In practice: a `RateLimitError` in your `except` block means Hotmart was still returning 429 after all retries. An `AuthenticationError` means your credentials are genuinely invalid, not just that a token expired mid-run.

Backoff formula: `0.5s × 2^attempt + jitter` (jitter 0–0.5s, cap 30s). For 429, the `RateLimit-Reset` header is used directly when present. Configure the number of retries via `max_retries`:

```python
client = Hotmart(..., max_retries=5)
Expand Down
11 changes: 11 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ O formato segue [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) e o pro

---

## [1.0.4] - 2026-03-28

Versão de manutenção com melhorias de documentação. Nenhuma mudança no código da biblioteca.

### Changed

- Seção "Processing pages individually" adicionada ao README (EN e PT-BR): padrão de loop manual com `page_token` para processamento página a página com efeitos colaterais (checkpoint, batch flush, etc.)
- Seção "Retry behavior" expandida no README (EN e PT-BR): tabela clara de quais erros são reprocessados automaticamente, quais disparam renovação de token e quais são lançados imediatamente

---

## [1.0.3] - 2026-03-26

### Fixed
Expand Down
35 changes: 34 additions & 1 deletion docs/README-ptBR.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,27 @@ for venda in client.sales.history_autopaginate(buyer_name="Paula"):

O iterador para automaticamente quando não há mais páginas — sem gerenciamento de token, sem condições de loop.

### Processamento página a página

Se você precisar agir ao final de cada página — salvar um checkpoint, enviar um batch para o banco, atualizar uma barra de progresso — use o método de página única e controle o loop manualmente:

```python
page_token = None
while True:
pagina = client.sales.history(start_date=1700000000000, page_token=page_token)

for venda in pagina.items:
processar(venda)

salvar_checkpoint(page_token) # efeito colateral por página

if not pagina.page_info or not pagina.page_info.next_page_token:
break
page_token = pagina.page_info.next_page_token
```

Use `*_autopaginate` quando precisar apenas iterar todos os registros. Use o loop manual quando precisar agir entre páginas.

---

## Modo Sandbox
Expand Down Expand Up @@ -417,7 +438,19 @@ Hierarquia de exceções:
| `APIStatusError` | outros | Status HTTP inesperado |
| `HotmartError` | — | Classe base para todos os erros do SDK |

O SDK realiza retentativas automáticas em erros transitórios (5xx, 429) com backoff exponencial (`0.5 × 2^attempt + jitter`, cap 30s). Configure via `max_retries`:
### Comportamento de retentativas

Nem todos os erros são tratados da mesma forma — o SDK age diferente antes de lançar cada um:

| Comportamento | Exceções |
|---------------|---------|
| **Reprocessado com backoff exponencial** — lançado apenas após esgotar todas as tentativas | `RateLimitError` (429), `InternalServerError` (500, 502, 503) |
| **Dispara renovação de token + uma nova tentativa automática** — nunca lançado por token expirado | `AuthenticationError` originado de 401 |
| **Lançado imediatamente, sem retentativa** | `BadRequestError` (400), `AuthenticationError` de 403, `NotFoundError` (404), `APIStatusError` |

Na prática: um `RateLimitError` no seu `except` significa que a Hotmart continuou retornando 429 mesmo após todas as tentativas. Um `AuthenticationError` indica credenciais genuinamente inválidas — não simplesmente um token expirado no meio da execução.

Fórmula do backoff: `0.5s × 2^attempt + jitter` (jitter 0–0.5s, cap 30s). Para 429, o header `RateLimit-Reset` é usado diretamente quando presente. Configure o número de tentativas via `max_retries`:

```python
client = Hotmart(..., max_retries=5)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "hotmart-python"
version = "1.0.3"
version = "1.0.4"
description = "Python SDK for the Hotmart API"
authors = [{ name = "Matheus Tenório", email = "matheusct16@gmail.com" }]
license = { text = "Apache-2.0" }
Expand Down
Loading
Loading