Skip to content

HTTP Handlers

Note

For the full method reference see the API Reference → HTTP section.

All HTTP handlers are built on httpx and expose both a synchronous and an asynchronous API. A single handler instance can be used for both.

Choosing the right handler

Handler When to use
HttpHandler Any REST API that needs no special authentication
BazeHttpHandler Bazefield — API key or user/password auth
CceeHttpHandler CCEE Dados Abertos — handles WAF warm-up automatically
OnsHttpHandler ONS SAGER / SINAPSE — full OAuth2 multi-step login

Connection properties

All HTTP handlers take an HttpConnProperties (or a subclass) as their first argument:

Python
from echo_connhandler import HttpConnProperties

props = HttpConnProperties(
    host="https://api.example.com",
    api_key="my-secret-key",        # preferred auth
    # user="...", password="...",   # alternative auth (cannot use both)
    conn_timeout=30,                # seconds to establish a connection
    request_timeout=300,            # seconds to wait for a response
    verify_ssl=False,               # disable SSL verification
    headers={"accept": "application/json"},
    payload_kwargs={"api_token": "xyz"},  # merged into every request body
    payload_kwargs_key="json",            # where to merge payload_kwargs
)

ONS — extended properties

OnsHttpHandler requires OnsHttpConnProperties, which adds a login_target field:

Python
from echo_connhandler import OnsHttpConnProperties, OnsHttpHandler

props = OnsHttpConnProperties(
    host="https://integra.ons.org.br/api/sager/renovaveis/api",
    user="my.user",
    password="my-password",
    login_target="SAGER",   # or "SINAPSE"
)
handler = OnsHttpHandler(props)

Synchronous usage

Basic requests

Python
from echo_connhandler import HttpConnProperties, BazeHttpHandler

props = HttpConnProperties(
    host="https://bazefield.echoenergia.com.br",
    api_key="my-api-key",
)

handler = BazeHttpHandler(props)
response = handler.get("BazeField.Services/api/allocationTypes")
data = response.json()

Endpoints can be relative (appended to host) or full URLs — the handler normalises them automatically.

All HTTP verbs

Python
handler.get("api/resource")
handler.post("api/resource", json={"key": "value"})
handler.put("api/resource/1", json={"key": "updated"})
handler.patch("api/resource/1", json={"key": "patched"})
handler.delete("api/resource/1")

Every method accepts response_ok (default 200) — a RuntimeError is raised if the status code doesn't match. Pass response_ok=None to skip the check entirely.

Context manager

Python
with BazeHttpHandler(props) as handler:
    data = handler.get("api/endpoint").json()
# connection is closed automatically

Accessing the underlying client

The connection property returns the live httpx.Client, useful for inspecting cookies or headers after requests:

Python
handler.get("api/session/start")
print(handler.connection.cookies)

Asynchronous usage

All HTTP handlers expose async counterparts for every sync method. Async connections are lazy — the async client is only created when you first call an async_* method (or await handler.async_reconnect() explicitly). No changes to the constructor are needed.

Basic async requests

Python
import asyncio
from echo_connhandler import HttpConnProperties, BazeHttpHandler

props = HttpConnProperties(
    host="https://bazefield.echoenergia.com.br",
    api_key="my-api-key",
)

async def main():
    async with BazeHttpHandler(props, skip_connect=True) as handler:
        response = await handler.async_get("BazeField.Services/api/allocationTypes")
        data = response.json()

asyncio.run(main())

Tip

Use skip_connect=True for async-only usage to avoid creating an unnecessary synchronous connection on construction.

All async HTTP verbs

Python
await handler.async_get("api/resource")
await handler.async_post("api/resource", json={"key": "value"})
await handler.async_put("api/resource/1", json={"key": "updated"})
await handler.async_patch("api/resource/1", json={"key": "patched"})
await handler.async_delete("api/resource/1")

Async context manager

Python
async with BazeHttpHandler(props, skip_connect=True) as handler:
    data = (await handler.async_get("api/endpoint")).json()
# async_close() called automatically

Explicit async lifecycle

Python
handler = BazeHttpHandler(props, skip_connect=True)
await handler.async_reconnect()     # connect explicitly
data = (await handler.async_get("api/endpoint")).json()
await handler.async_close()        # close explicitly

Accessing the async client

The async_connection property returns the live httpx.AsyncClient after the first async request:

Python
await handler.async_get("api/session/start")
print(handler.async_connection.cookies)

Mixed sync + async on the same instance

Sync and async connections are independent. A single instance can serve both:

Python
handler = BazeHttpHandler(props)                        # sync connects here
sync_data = handler.get("api/resource").json()          # uses sync client
async_data = (await handler.async_get("api/resource")).json()  # lazy async connect

Warning

Do not call async_* methods on the same handler instance concurrently from multiple coroutines. The async client is stored as a plain instance attribute (not task-local). For concurrent usage, create one handler per coroutine.


Authentication patterns

API key

Python
props = HttpConnProperties(host="https://api.example.com", api_key="my-key")
handler = BazeHttpHandler(props)
# Authorization: Bearer my-key is set on every request automatically

User / password

Python
props = HttpConnProperties(
    host="https://bazefield.echoenergia.com.br",
    user="my.user",
    password="my-password",
)
handler = BazeHttpHandler(props)
# Login POST is called during _connect()

Per-request token (payload_kwargs)

Some APIs require a token injected into every request body:

Python
props = HttpConnProperties(
    host="https://api.example.com",
    payload_kwargs={"api_token": "xyz"},
    payload_kwargs_key="json",   # merges into the json= body
)
handler = HttpHandler(props)
# Every handler.post("endpoint", json={...}) will automatically include api_token

OAuth2 (ONS)

OnsHttpHandler handles the full OAuth2 flow transparently during _connect():

  1. GET federation endpoint (sets cookies)
  2. POST OAuth2 token endpoint → JWT retrieved
  3. Bearer token injected into session headers
  4. (SINAPSE only) GET profile endpoint → Perfil-Selecionado header set

No extra code needed — just pass the right OnsHttpConnProperties.


Retry behaviour

All requests (sync and async) automatically retry using exponential backoff. On each failure the handler reconnects before the next attempt.

Default parameters (configurable in the constructor):

Parameter Default Meaning
max_retries 8 Attempts per request
max_conn_retries 3 Attempts to re-establish the connection
retry_wait_time 30 s Maximum wait between retries
exponential_min_retry_wait_time 0.1 s Minimum wait between retries
exponential_multiplier 1 Backoff multiplier
Python
# Low-latency endpoint: fail fast
handler = BazeHttpHandler(props, max_retries=0)

# Resilient background job: keep retrying for longer
handler = BazeHttpHandler(props, max_retries=12, retry_wait_time=60)