Skip to main content

Periplus Python SDK

A read-only client for the public Periplus query API. Python 3.11 or later. Configure the public web application URL, not the internal query or control service. No API token, DuckDB installation or lake credentials are needed.

from periplus_sdk import Client

with Client("http://localhost:8080") as client:
    result = client.execute(
        "SELECT capture_id FROM public_v1.capture LIMIT ?", [10]
    )
    print(result.columns, result.types)
    print(result.rows)
    print(result.source_snapshot, result.truncated)

For a hosted deployment, replace the URL with its public HTTPS origin. Alternatively set PERIPLUS_PUBLIC_URL and use Client(). An optional URL path prefix is preserved. The client reuses HTTP connections; close it with a context manager or close().

Marimo SQL cells and schema browser

Install the notebook integration from PyPI:

uv add "periplus-python-sdk[notebook]>=0.6.1"

In a Python setup cell, create a SQLAlchemy engine:

from periplus_sdk import sql_api

pp = sql_api.create_engine("https://periplus.dev", mode="stable")

Add a SQL cell, select pp in its connection dropdown, and enter:

SELECT capture_id, requested_url
FROM public_v1.capture
LIMIT 10

Marimo displays the result as a table. Expand pp → periplus → public_v1 in Data Sources to discover views and expand a view to load its columns for SQL completion. Discovery uses bounded SHOW TABLES and DESCRIBE through the same public API; no internal catalogue or storage credentials are used. Truncated discovery fails explicitly rather than displaying a silently incomplete schema. To eagerly load schemas and views, enable their discovery in marimo's Packages & Data settings. Column discovery is on demand by default, to avoid many public API requests.

The Python equivalent of a SQL cell is:

import marimo as mo

captures = mo.sql(
    "SELECT capture_id FROM public_v1.capture LIMIT 10",
    engine=pp,
)

Set mode="experimental" for the experimental service. Omit the URL to use PERIPLUS_PUBLIC_URL. Optional timeout=140 and schema_version="public_v1" arguments configure the client deadline and public schema. Run pp.dispose() when finished. This is a read-only SQLAlchemy dialect for textual SQL and reflection, not a writable ORM backend. Each statement has its own server snapshot; SQLAlchemy transaction blocks do not provide a shared snapshot or rollback. The adapter makes no transaction requests.

A complete notebook is in examples/notebook.py. The integration is tested with marimo 0.24.1 and SQLAlchemy 2.x. SQLAlchemy is included in the standard SDK install; the notebook extra adds marimo. Existing marimo environments only need uv add "periplus-python-sdk>=0.6.1". The returned object is a standard SQLAlchemy Engine, also usable with pandas and ordinary Python scripts. Engine creation is lazy; the first query opens a connection.

DB-API connection

For SQL cells without schema browsing, or standard cursor-based Python code:

from periplus_sdk import connect

with connect("https://periplus.dev", mode="stable") as connection:
    with connection.cursor() as cursor:
        cursor.execute("SELECT capture_id FROM public_v1.capture LIMIT ?", [10])
        print(cursor.description)
        print(cursor.fetchall())
        print(cursor.result.source_snapshot)

Connections expose cursor, execute, close, and context managers. Cursors support execute, fetchone, fetchmany, fetchall, iteration, and close. Use positional ? parameters. Decimal and temporal parameters are sent as strings; use explicit SQL casts. Binary and nested parameters are not supported by this adapter. Fetching only consumes the bounded result already received; it never issues pagination or retries. Connections/cursors are not thread-shared. commit() is a no-op; rollback() and executemany() are unsupported.

cursor.result preserves the original query response. connection.last_result also retains it after marimo closes a cursor; a new execution clears it first. Truncation emits periplus_sdk.dbapi.TruncationWarning and sets rowcount to -1. DB-API failures use the standard exception hierarchy in periplus_sdk.dbapi; HTTP errors retain status_code, code, and retry_after_seconds.

Scalar integer, floating-point, decimal, date, time, timestamp and BLOB results are decoded to Python values. UUIDs remain strings. Nested/other SQL types keep their JSON wire representation; out-of-range dates/timestamps remain strings. Temporal precision is limited to what the server JSON transport preserves. The cursor preserves duplicate column names, but dataframe libraries/marimo may not: use unique SQL aliases. Dataframe inference can lose types for empty or all-null results; cursor.description retains the SQL type names.

Stable and experimental APIs

Both clients accept mode="stable" (the default) or mode="experimental" at initialization:

with Client("https://periplus.dev", mode="experimental") as client:
    result = client.execute("SELECT capture_id FROM public_v1.capture LIMIT 1")
    print(result.query_mode, result.compiler_version, result.optimizations)

The selected mode applies to preparation, execution, and helper discovery. Experimental requests use the public application's /api/query/experimental/ routes. There is no automatic fallback to stable if the experimental service is unavailable. AsyncClient accepts the same option. Invalid modes raise ConfigurationError.

Preparation and helpers

with Client("http://localhost:8080") as client:
    prepared = client.prepare("SELECT capture_id FROM public_v1.capture LIMIT ?", [10])
    print(prepared.diagnostics, prepared.plan)
    result = client.execute(prepared.sql, prepared.parameters)
    helpers = client.helpers()
    print(helpers.catalogue_version, helpers.helpers)

Preparation validates and explains without executing the analytical query. Execution independently validates and prepares; a prior preparation never authorizes SQL. Linting, diagnostics and future SQL optimizations belong to the server. The SDK sends SQL unchanged.

Async use

from periplus_sdk import AsyncClient

async def observations():
    async with AsyncClient("http://localhost:8080") as client:
        return await client.execute("SELECT capture_id FROM public_v1.capture LIMIT 10")

Use aclose() when managing an async client's lifetime explicitly.

Permissions, results and errors

  • The same public SQL feature switch, shared rate budget, namespace validation and read-only execution apply as in the public web workspace. The SDK provides no writes, crawling, administrative controls or direct lake attachment.
  • Results retain query_id, SQL, parameters, diagnostics, plan, columns, SQL types, JSON rows, elapsed milliseconds, source_snapshot and truncated. Decimals and large integers remain strings exactly as returned by the server. Duplicate column names are preserved.
  • Operator-configured execution limits default to 1,000 rows, an 8 MiB result budget and a 20-second server deadline. Always inspect truncated. The SDK does not silently fetch more rows or retry.
  • ApiError exposes status_code, safe code, and retry_after_seconds when supplied. TransportError means HTTP failed; ResponseError means a malformed successful response. The client timeout defaults to 140 seconds and can be set with timeout=. A timeout or local cancellation does not guarantee server cancellation. Redirects are not followed automatically.
  • Preparation and execution are attributed to sdk in the existing private query history. Original SQL and parameters are retained for 30 days; result rows are not stored. Recording is best-effort and can be lost during outages or backpressure. This label is not a user identity.

Installation and verification

Install the public-v1 client from PyPI:

python -m pip install "periplus-python-sdk>=0.6.1"

Version 0.6.1 supports the current public-v1 contract. For production, configure PERIPLUS_PUBLIC_URL=https://periplus.dev; no API token is required. Run the installed package against an available public app:

PERIPLUS_PUBLIC_URL=http://localhost:8080 python packages/periplus-python-sdk/examples/smoke.py

Releasing

Repository CI publishes immutable releases from tags named periplus-python-sdk-v<version>. The tag must exactly match the static version in pyproject.toml; for example, version 0.6.1 is released with:

git tag periplus-python-sdk-v0.6.1
git push origin periplus-python-sdk-v0.6.1

PyPI publishing uses Trusted Publishing rather than a stored API token. The PyPI publisher must be configured for GitHub owner elei-io, repository periplus, workflow python-sdk-release.yml, and environment pypi. Protect that GitHub environment with required reviewers before the first release.

Public v1

Install the updated SDK from PyPI with python -m pip install "periplus-python-sdk>=0.6.1". The previously published 0.2.0 release predates this contract. prepare and execute accept keyword-only schema_version="public_v1" (the default); responses preserve schema_version separately from source_snapshot. Unavailable versions are rejected by the server.

License

Copyright (c) 2026 Ekku Leivonen (elei.io). Licensed under Apache-2.0; see NOTICE. The server and other repository packages have separate licensing described in the root LICENSING.md.

Release files for periplus-python-sdk 0.6.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for periplus-python-sdk 0.6.1
File Size Uploaded
periplus_python_sdk-0.6.1.tar.gz 20.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for periplus-python-sdk 0.6.1
File Interpreter ABI Platform
periplus_python_sdk-0.6.1-py3-none-any.whl Python 3 none any Details

Total release size: 40.2 kB

Release files / periplus_python_sdk-0.6.1.tar.gz

Download URL periplus_python_sdk-0.6.1.tar.gz
Size 20.8 kB
Tags Source
SHA-256 checksum
How to use checksums
3425a8a0dabccde9b2ce991d1146e6834db8cc3ad7392db02f7f41bed1926a7e
BLAKE2b-256 checksum
How to use checksums
3d5a7293858956de9852ca10a149324d0f23da5403fef31837098d8f7c44c919
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log

Release files / periplus_python_sdk-0.6.1-py3-none-any.whl

Download URL periplus_python_sdk-0.6.1-py3-none-any.whl
Size 19.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
34f665ff1407c75dfd0138c8fa17bf3af6c4b255bd3592949d60e8fc12867eed
BLAKE2b-256 checksum
How to use checksums
8917536b37a84cb2c9e619c532397ec0dc923b0fb76bc2e501bd200f2af89145
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log

Release history Release notifications | RSS feed

0.11.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

This release

0.6.1 This release

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

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