Skip to main content

Elmo Python SDK

PyPI License: MIT

Read and manage the brands, prompts, competitors, and AI-visibility analytics of this deployment.

This package calls the Elmo API from Python, with a synchronous and an asynchronous client built on httpx2. It includes Pydantic models for every request and reply.

Issues

Installation

The SDK is published on PyPI. Install it with the package manager you use.

uv add elmo-sdk
Other package managers
pip install elmo-sdk
poetry add elmo-sdk
pdm add elmo-sdk

Usage

Set ELMO_API_KEY in your environment, then call the API:

from elmo_sdk import Elmo

elmo = Elmo()
data = elmo.brands.list()

print(data)
The same call with AsyncElmo
import asyncio

from elmo_sdk import AsyncElmo

async_elmo = AsyncElmo()


async def main() -> None:
    data = await async_elmo.brands.list()

    print(data)


asyncio.run(main())

Requirements

  • Python 3.10 or later

Pagination

Iterate a paginated call to read every item. Each page is fetched when the loop reaches it.

from elmo_sdk import Elmo

elmo = Elmo()
for item in elmo.brands.list():
    print(item)

Errors

When the API answers with an error status, the call raises APIError, or a subclass named for the status. data holds the body as the model its status declares, where there is one. request_id holds the id to quote when you report a failure. Every exception a call raises subclasses ElmoError.

from elmo_sdk import APIError, Elmo

elmo = Elmo()
try:
    elmo.brands.list()
except APIError as error:
    print(error.status, error.request_id, error.data)

An argument the call refuses raises ElmoError itself, before anything is sent, with pydantic's error as its __cause__. A model you build yourself raises pydantic.ValidationError, as any pydantic model does.

Catch one status by its class:

Status Error
400 BadRequestError
401 AuthenticationError
402 PaymentRequiredError
403 PermissionDeniedError
404 NotFoundError
409 ConflictError
422 UnprocessableEntityError
429 RateLimitError
500 and above InternalServerError
Any other error status APIError

A call that got no reply has no status. It raises TransportError, or TransportTimeoutError where an attempt ran out of time. Neither is an APIError.

A call made with no credential raises MissingCredentialError before anything is sent. A reply the SDK cannot read raises DecodeError.

By the time a call raises RateLimitError, the client has sent it again up to 2 times. retry_after_seconds on RateLimitError holds how long the API asked to wait, in seconds, where it said.

To report a failure, open an issue and quote request_id.

Configuration

Authentication

The client reads ELMO_API_KEY from the environment. To pass the value yourself, set api_key:

from elmo_sdk import Elmo

elmo = Elmo(api_key="…")

api_key also takes a function that returns the value, such as one that reads it from a secret store. The client calls it for each request.

Base URL

Calls go to /api/v1. To send them elsewhere, such as a proxy or a gateway, set base_url, or the ELMO_BASE_URL environment variable:

from elmo_sdk import Elmo

elmo = Elmo(base_url="https://gateway.example.com")

Closing the client

The client opens a connection pool on its first call, and a with block closes it on the way out. A client that lives as long as the process closes with close() when you shut down. AsyncElmo closes with async with and aclose(). A client you passed as http_client is yours to close.

from elmo_sdk import Elmo

with Elmo() as elmo:
    elmo.brands.list()
The same with AsyncElmo
import asyncio

from elmo_sdk import AsyncElmo


async def main() -> None:
    async with AsyncElmo() as async_elmo:
        await async_elmo.brands.list()


asyncio.run(main())

Per-call options

One call can run under settings of its own. with_options returns a client that differs only in what you pass and sends over the same connection pool, and takes every option the client does. Headers merge per name, and every other option replaces the client's:

from elmo_sdk import Elmo

elmo = Elmo()
elmo.with_options(timeout=5, max_retries=0).brands.list()

A call also takes the same settings as keyword arguments:

from elmo_sdk import Elmo

elmo = Elmo()
elmo.brands.list(timeout=5, max_retries=0)

Retries

A failed call is sent again up to 2 times, after a lost connection, a 408, 425 or 429 status, or most 5xx statuses. A POST or PATCH call may already have changed something, so it is sent again only after a request that never reached the API or a 408, 425 or 429 status.

Each wait is longer than the last on average, or as long as Retry-After asks.

To change that count, set max_retries on the client or on one call. For every other rule, such as which failures are retried and how long each wait is, set retry, which takes a RetryOptions:

from elmo_sdk import Elmo

elmo = Elmo(max_retries=5)
elmo.with_options(max_retries=0).brands.list()

To wait longer between attempts, set the first wait and the longest one:

from elmo_sdk import Elmo

elmo = Elmo(retry={"delay": 1, "max_delay": 10})

Timeouts

Each attempt may run for NaN seconds. One that runs longer raises TransportTimeoutError. To change the limit, set timeout in seconds on the client or on one call. False removes it:

from elmo_sdk import Elmo

elmo = Elmo(timeout=20)
elmo.with_options(timeout=5).brands.list()

Logging

Logging is off by default. To see what the SDK sends and receives, set log_level or the ELMO_LOG environment variable. "info" logs each call and its reply, and "debug" adds headers, with credentials redacted. Bodies are never logged. Records go to a logger under elmo_sdk, which writes to standard error until the application configures logging. Pass logger to use your own.

from elmo_sdk import Elmo

elmo = Elmo(log_level="info")

Custom HTTP client

To send requests over HTTP/2, through a proxy, or with other TLS settings or connection limits, pass an httpx2.Client you built as http_client. HTTP/2 needs the httpx2[http2] extra. A plain httpx.Client (or httpx.AsyncClient) works here too, since the two share their shape. AsyncElmo takes an httpx2.AsyncClient.

import httpx2
from elmo_sdk import Elmo

elmo = Elmo(http_client=httpx2.Client(http2=True))

Default headers

To send a header with every call, set default_headers on the client. A call's own extra_headers are merged with them by name, and None drops one:

from elmo_sdk import Elmo

elmo = Elmo(default_headers={"x-team": "billing"})

Interceptors

To run code of your own around every call, set interceptors on the client. A request hook runs on each request last before it is sent, with the credential already on it. A response hook returns the result the caller reads, and an error hook returns the error the caller gets, so a hook that only observes returns what it was given. Hooks run in the order you list them. AsyncElmo also waits for a hook that returns an awaitable.

from elmo_sdk import Elmo, PreparedRequest


def tag(request: PreparedRequest) -> None:
    request.meta["x-request-source"] = "worker"


elmo = Elmo(interceptors={"request": [tag]})

Raw response

To read the HTTP response as well, make the call through with_response. What comes back holds:

  • data: what the call returns on its own
  • status: the status it arrived with
  • request_id: the id to quote when you report a problem
  • response: the httpx2.Response itself, for a header or a field the SDK does not model
from elmo_sdk import Elmo

elmo = Elmo()
reply = elmo.with_response.me.get()
print(reply.status, reply.response.headers)

Environment variables

The client reads each variable when its option is not set:

Variable Option Default
ELMO_API_KEY api_key
ELMO_BASE_URL base_url "/api/v1"
ELMO_LOG log_level "off"

Forward compatibility

The API can add a field or a value after this version of the SDK is released. This version keeps working when that happens, and you can use the new part of the API before a release declares it.

  • A field of a reply that this version does not declare is kept. Read it from model_extra on the model that holds it.
  • An enum value this version does not list is a member of its own, whose value is what the API sent.
  • To send a field this version does not declare, pass it in extra_body to a call. A model or a dictionary you pass keeps any other key you set on it, and sends it.

License

MIT. See LICENSE.md.

Metadata

Release files for elmo-sdk 0.1.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 elmo-sdk 0.1.0
File Size Uploaded
elmo_sdk-0.1.0.tar.gz 85.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for elmo-sdk 0.1.0
File Interpreter ABI Platform
elmo_sdk-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 196.8 kB

Release files / elmo_sdk-0.1.0.tar.gz

Download URL elmo_sdk-0.1.0.tar.gz
Size 85.2 kB
Tags Source
SHA-256 checksum
How to use checksums
5c17edfa4e3da7dcb2771b1dfa2b8eb917a641baa11608045703661cc337384d
BLAKE2b-256 checksum
How to use checksums
0aacaa5ab3782a2f2743d43cf7cbbf4d450fdce68934a5c5864ee24707ef281b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.24 {"installer":{"name":"uv","version":"0.12.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / elmo_sdk-0.1.0-py3-none-any.whl

Download URL elmo_sdk-0.1.0-py3-none-any.whl
Size 111.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cef96c4531d17d611cec2554fff9fcfab995aed85d23507af4b50350b7946c61
BLAKE2b-256 checksum
How to use checksums
a1aeb1a70e4be7b6b43e76c136ed793932e6777751a48965198acbc60096242f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.24 {"installer":{"name":"uv","version":"0.12.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

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