Metadata-Version: 2.5
Name: mindsight-external-api-requests
Version: 0.2.0
Summary: Funções para facilitar o uso da API externa da Mindsight.
Author-email: Vinicius Kenzo <vinicius.kenzo@mindsight.com.br>
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: httpx<1.0.0,>=0.28.0
Requires-Dist: pandas<4,>=2.2.3
Requires-Dist: pydantic>=2.12.0
Requires-Dist: requests<3.0.0,>=2.33.0
Requires-Dist: tqdm<5.0.0,>=4.67.0
Provides-Extra: dev
Requires-Dist: black<27.0,>=26.3; extra == 'dev'
Requires-Dist: build<2.0.0,>=1.3.0; extra == 'dev'
Requires-Dist: flake8<7.0,>=6.0; extra == 'dev'
Requires-Dist: pytest-asyncio<2.0,>=0.21; extra == 'dev'
Requires-Dist: pytest<8.0,>=7.0; extra == 'dev'
Description-Content-Type: text/markdown

# mindsight-external-api-requests

Biblioteca para consultar endpoints da API da Mindsight, gerenciar
tokens OAuth e retornar os resultados como `pandas.DataFrame`.

Este projeto oferece uma API pública totalmente síncrona (bloqueante); a
concorrência (paginação paralela e lotes de CRUD) permanece assíncrona como
detalhe interno, executada em um event loop de background dedicado.

## Visão geral

- Busca de páginas via paginação por página ou por cursor, com concorrência interna.
- `ApiClient` dinâmico que expõe métodos síncronos `get_<endpoint>` automaticamente.
- Gerenciamento de OAuth com `TokenManager` síncrono e sessão (`OAuthSession`) com retries.

## Principais funcionalidades

- Busca paralela de páginas e normalização automática para `pandas.DataFrame`.
- Helpers para operações CRUD (GET/POST/PUT/PATCH/DELETE) com concorrência configurável.
- Política de retry/backoff configurável em `mindsight_external_api_requests.oauth.retry_policy`.
- Registro central de endpoints em `mindsight_external_api_requests/endpoints`.

## Instalação

Instale a versão mais recente publicada no PyPI:

```bash
pip install mindsight-external-api-requests
```

Para fixar uma versão, use `pip install mindsight-external-api-requests==0.2.0`.
Python 3.10 ou mais recente é necessário.

## Uso rápido (Quickstart)

A API é síncrona: os métodos bloqueiam até o resultado, sem necessidade de
`asyncio`/`await`.

```py
from mindsight_external_api_requests.oauth.config import load_from_dict
from mindsight_external_api_requests.oauth.token_manager import TokenManager
from mindsight_external_api_requests.oauth.retry_policy import RetryPolicy
from mindsight_external_api_requests.api_client import ApiClient

cfg = load_from_dict({
	"client_id": "seu_client_id_aqui",
	"client_secret": "seu_client_secret_aqui",
	"tenant": "tenantcliente",
})
retry_policy = RetryPolicy()
token_manager = TokenManager(cfg)
# O `ApiClient` repassa a política ao `TokenManager` (a obtenção do token também
# re-tenta 5xx/429 e erros de rede), a menos que o `TokenManager` tenha recebido
# uma própria via `TokenManager(cfg, retry_policy=...)`.
client = ApiClient(token_manager, retry_policy)

# Busca paginada (concorrência paralela por baixo dos panos), retorna DataFrame
df = client.get_hub_employees()
print(df.head())

# Busca várias coleções em paralelo pela mesma sessão e retorna um dicionário
# com um DataFrame para cada nome solicitado. Se alguma falhar, a chamada levanta
# a exceção e não retorna resultados parciais.
collections = client.fetch_collections(["hub_employees", "controle_employees"])
employees = collections["hub_employees"]
controle_employees = collections["controle_employees"]

# Exportações de funcionários: `date` é obrigatório e a resposta é uma lista JSON.
export = client.get_controle_employees_export_info_json(date="2026-10-13")
extra_export = client.get_controle_employees_export_info_extra_json(date="2026-10-13")

# Níveis de senioridade usam cursor; a consulta individual usa `key`.
levels = client.get_controle_seniority_levels(search="senior")
level = client.get_controle_seniority_levels_by_key("senior")

# Os seis métodos `post_controle_upload_*` enviam uma lista inteira em uma
# única requisição JSON. A resposta é o corpo de uma requisição, não uma lista
# de respostas individuais.
result = client.post_controle_upload_employee_areas([
    {"employee_code": "EMP001", "area_code": "AREA001", "start": "2026-10-13"}
])

# O token também é obtido de forma síncrona
token = token_manager.get_token()
```

As opções passadas como argumentos nomeados para `fetch_collections` (por
exemplo, `page_size=2000`) são aplicadas a todas as coleções da chamada. Todos os
nomes precisam corresponder a endpoints registrados que suportam GET; nomes
inválidos levantam `KeyError` antes de qualquer requisição.
As exportações retornam listas JSON sem paginação e não são coleções para `fetch_collections`.
`get_controle_seniority_levels` não aceita `page_size`, pois a API usa cursor.
`get_controle_termination_types` também não usa paginação na requisição.

Solicite ao administrador da sua conta Mindsight as credenciais OAuth e o identificador do tenant. Configure `MINDSIGHT_OAUTH_CLIENT_ID`, `MINDSIGHT_OAUTH_CLIENT_SECRET` e `MINDSIGHT_OAUTH_TENANT` no ambiente; não salve credenciais no código ou no controle de versão. O exemplo abaixo usa valores fictícios.

## Configuração e parâmetros úteis

- OAuthConfig:
    - `client_id`, `client_secret`, `tenant` são obrigatórios.
    - `scope` e `token_url` têm valores padrão, mas podem ser personalizados.
    - `token_expiry_buffer` define quantos segundos antes do vencimento o token deve ser renovado.
    - `is_sandbox` ativa URLs de sandbox para testes.
- RetryPolicy:
    - `max_retries`: número total de tentativas, contando a primeira, antes de desistir. O mínimo é `1` (uma única tentativa, sem retries); valores menores que 1 levantam `ValueError`, e valores que não são inteiros (inclusive `bool`) levantam `TypeError`, tanto ao criar a `RetryPolicy` quanto ao alterar o atributo depois.
    - `backoff_factor`: fator multiplicador para calcular o tempo de espera entre as tentativas (exponencial).
    - `status_forcelist`: códigos de status HTTP que disparam uma tentativa de retry.
    - `allowed_methods`: métodos HTTP que devem ser re-tentados em caso de falha (não se aplica à obtenção do token, que sempre é re-tentada).
    - `per_try_timeout` e `max_retry_time`: timeout por tentativa e tempo total máximo de retries (valem também para a obtenção do token; sem `per_try_timeout`, o POST do token usa 10 s). `max_retry_time` deve ser maior que 0 (ou `None`, sem limite); ao esgotar o tempo, a requisição de dados devolve a última resposta recebida ou, se nada foi enviado ainda, levanta `httpx.TimeoutException`.
    - `jitter`: quantidade máxima de tempo aleatório a ser adicionado ao backoff para evitar colisões de retry.

## Tratamento de erros

Toda falha ao ler da API levanta uma subclasse de `MindsightApiError`
(`mindsight_external_api_requests.exceptions`), então basta um único
`except MindsightApiError` para cobrir "a leitura falhou":

```text
MindsightApiError
├── ApiHttpError                     falha de rede/HTTP (conexão, timeout, status não 2xx)
├── OAuthError                       falha ao obter/renovar o token
│   └── TokenRefreshError
└── ApiResponseError                 resposta recebida, mas malformada ou incompleta
    └── PaginationReconciliationError  registros obtidos não conferem com a contagem declarada
```

- `ApiHttpError` expõe `status_code` (ou `None` em falhas de rede), encadeia a
  exceção original do `httpx` em `__cause__` e também é um `httpx.HTTPError`,
  então `except httpx.HTTPError` existente continua funcionando. É o caso indicado para retry.
- `ApiResponseError` cobre resposta vazia, corpo que não é JSON, `count` que muda
  durante a paginação e resposta de cursor sem `next`. Em geral deve ser logada/alertada.
- `PaginationReconciliationError` expõe `collection`, `declared_count`,
  `distinct_count` e `total_rows`.
- Erros de configuração (por exemplo, nome de endpoint desconhecido, que levanta
  `KeyError`) ficam **fora** da hierarquia: indicam um bug do chamador, não uma
  falha da API.

```py
from mindsight_external_api_requests import ApiHttpError, MindsightApiError

try:
    df = client.get_hub_employees()
except ApiHttpError:
    ...  # tentar novamente
except MindsightApiError:
    ...  # logar e alertar
```

> **Atenção (mudança de comportamento):** falhas de leitura que antes levantavam
> `ValueError` ou `RuntimeError` (incluindo `PaginationReconciliationError`,
> antes um `RuntimeError`) agora levantam subclasses de `MindsightApiError`.
> Quem capturava esses tipos deve migrar para a nova hierarquia. Falhas de
> rede/HTTP continuam capturáveis como `httpx.HTTPError`.

## Testes

Execute a suíte de testes com:

```bash
pytest -q
```

## Onde procurar o código

- Implementação do cliente: [mindsight_external_api_requests/api_client.py](mindsight_external_api_requests/api_client.py)
- Helpers de paginação: [mindsight_external_api_requests/get.py](mindsight_external_api_requests/get.py)
- Auth/OAuth: [mindsight_external_api_requests/oauth](mindsight_external_api_requests/oauth)
