Skip to main content

mode-sdk

Unofficial Python SDK for the Mode Analytics API — the documented REST surface plus the Discovery batch API. Not affiliated with Mode or ThoughtSpot.

  • Python 3.11+, one runtime dependency (httpx), fully typed
  • Lazy pagination, mapped errors, frozen dataclass models, retries with backoff
  • Requires a Mode Business workspace — only resources inside one are reachable over the API

Install

uv add mode-sdk          # or: pip install mode-sdk

Quickstart

from mode_sdk import Mode

# Explicit, or from MODE_WORKSPACE / MODE_API_TOKEN / MODE_API_SECRET
with Mode("acme", token=token, secret=secret) as mode:
    mode.verify()                                    # cheapest credential check
    for space in mode.spaces.list(filter="all"):     # default lists only *your* collections
        for report in mode.reports.list(space=space):
            print(report.token, report.name, report.created_at)

Credentials are keyword-only; each argument falls back to its environment variable. Every identifier argument takes the model a previous call returned, or its 12-character token string — passing the model is the safer spelling.

Examples

Run a report and read the results

run = mode.reports.run_and_wait(report, parameters={"country": "US"})
if run.succeeded:
    for filename, csv in mode.report_runs.results_tables(report, run).items():
        print(filename, len(csv))
else:
    print(mode.report_runs.failure_detail(report, run))

The shape of an export is Mode's choice, not the report's: the same path answers text/csv for one report and application/zip for another. results_tables() returns {filename: csv} either way; results() returns a RunResults when you want the raw bytes and their media_type.

Save results to disk, or render a PDF

mode.report_runs.results(report, run).save("./exports")   # a directory keeps Mode's filename
mode.exports.pdf(report, run).save("report.pdf")          # starts and polls the async render

Create a report

from mode_sdk import query_spec

report = mode.reports.create(space, "revenue", [query_spec(sql, data_source_id)])

Mode documents no create-report endpoint, so this is three requests under the hood; a failure part-way deletes what was minted rather than leaving an unnamed report behind.

Handle errors

from mode_sdk import ModeError, NotFoundError, RateLimitError

try:
    mode.reports.get(token)
except NotFoundError:
    ...
except RateLimitError as exc:
    print(exc.retry_after)        # the server's hint, in seconds
except ModeError:                 # the root: everything this package raises
    raise

ModeAPIError subclasses map Mode's error bodies — BadRequestError, AuthenticationError, PermissionDeniedError, ConflictError, UnprocessableEntityError, InternalServerError. ModeConnectionError and ModeTimeoutError mean Mode never answered. httpx exceptions never escape.

Pagination

page = mode.reports.list(space=space)
for report in page:               # lazily walks every page
    ...
page.first_page()                 # one request, one list
list(page)                        # everything, eagerly

One client, many threads

patient = mode.with_options(timeout=600.0, max_retries=0)
patient.report_runs.results(report, run)      # same connection pool, longer deadline

Build one Mode and share it across threads: httpx.Client is thread-safe and models are frozen. Page objects are the exception — each belongs to the thread walking it.

Call an endpoint this package does not model

mode.request("GET", "/some/new/endpoint")     # workspace-relative, returns the raw dict

Discovery API

from mode_sdk import Discovery, create_signature_token

signature = create_signature_token(
    "acme", token=token, secret=secret, name="etl-reader", expires_at="2027-01-01T00:00Z"
)
# store signature.token / access_key / access_secret — the secret is returned exactly once

with Discovery("acme", signature=signature) as discovery:
    for report in discovery.reports(include_spaces="all"):
        print(report.token, report.name)

Read-only batch listings (reports, report_stats, queries, charts, collections, members) with a separate credential. Requires a Mode Enterprise plan; refusals raise DiscoveryUnavailableError.

What it covers

Namespace Endpoints
mode.workspace verify, workspace, account
mode.spaces Collections: get, list, create, update, delete, reports, datasets
mode.space_memberships list, get, add, remove (deprecated by Mode)
mode.reports get, list, create, update, delete, archive, unarchive, purge, run, run_and_wait
mode.report_runs list, get, create, clone, duplicate, duplication_status, form_fields, results, results_tables, wait, create_and_wait, failure_detail
mode.report_filters list, get, create, update, delete
mode.queries list, get, create, update, delete
mode.query_runs list, get, results
mode.charts list, get
mode.definitions list, get, create, update, delete
mode.data_sources list, get, update, refresh_schema, purge
mode.datasets get, list, update, delete, reports, fields, refresh_in_report
mode.dataset_runs list, get, create
mode.dataset_fields list, create, update, delete
mode.memberships list, get, remove
mode.invites create
mode.groups list, get, create, update, delete, memberships, add_member, remove_member
mode.audit_logs list
mode.exports report_run, report_run_tables, query_run, pdf, from_href
mode.report_schedules list, get, create, update, delete
mode.report_subscriptions list, get, create, update, delete
mode.dataset_schedules list

Good to know

  • Numeric-looking ids are strings (report.id == "5747815"), and they do not route: Mode's paths take the 12-character token, and the numeric id answers 404. Nothing is coerced.
  • Every model field is optional. Mode's key set varies by object type and endpoint; unmodelled keys stay in model.raw, and an unparseable timestamp leaves the attribute None with the original string in raw.
  • POST is not retried by default — a replayed run-create starts a second run and bills the warehouse twice. Connection failures and 408/429/5xx on idempotent methods retry with full-jitter backoff; opt in per request with mode.transport.request(..., retry=True).
  • A Retry-After above 60 seconds stops the retry loop instead of sleeping minutes inside a library call; the hint comes back on RateLimitError.retry_after.
  • per_page is capped at 30 by Mode and ignored entirely by several collections; the page walk absorbs both and still returns the whole collection.
  • Logging uses the standard library: logging.getLogger("mode_sdk").setLevel(logging.DEBUG). Header values, bodies, query strings, tokens and secrets are never logged at any level.
  • Bring your own HTTP client with Mode(..., http_client=httpx.Client(...)). That hands you HTTP policy, so combining it with timeout, proxy, verify and friends raises rather than being silently ignored.

Development

uv sync                  # create the venv from uv.lock
uv run pytest            # offline suite — no network, no credentials
uv run ruff format && uv run ruff check --fix
uv run pyright           # strict

Fixtures are hand-written payloads and every request is mocked; tests must stay offline, with synthetic tokens and names only.

Licence

Apache-2.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mode_sdk-0.1.0.tar.gz (49.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mode_sdk-0.1.0-py3-none-any.whl (62.4 kB view details)

Uploaded Python 3

File details

Details for the file mode_sdk-0.1.0.tar.gz.

File metadata

  • Download URL: mode_sdk-0.1.0.tar.gz
  • Upload date:
  • Size: 49.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for mode_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1fcf41c78d3e84d27fffabafe85b067fa2991eae75f537e6deba5b9aa12df159
MD5 5c386b7bf27cb3b21afcde5d8b12fa10
BLAKE2b-256 860806d10da2a0e7feec88e652ff363458801cd2fb49244d0ed8f051ae1c9020

See more details on using hashes here.

Provenance

The following attestation bundles were made for mode_sdk-0.1.0.tar.gz:

Publisher: release.yml on moonD4rk/mode-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mode_sdk-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: mode_sdk-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 62.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for mode_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d2986cc2c643a59d7087589e8a1f5a1f356bc3c6ae14cf7b0907b7f6979733d7
MD5 12dd77d45ed20f122d4847a43f203129
BLAKE2b-256 976c31c86b64995c0e691af82928074aa633a7d85d69b7c6f6898836368f3b96

See more details on using hashes here.

Provenance

The following attestation bundles were made for mode_sdk-0.1.0-py3-none-any.whl:

Publisher: release.yml on moonD4rk/mode-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 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