Skip to main content

aesops

Python client for the Aesops dataset API.

Browse the full catalog without any credentials. Load datasets directly into pandas, polars, or a DuckDB connection (with range-request pushdown, so only the row-groups you query are fetched).

Install

pip install aesops                  # core: httpx + duckdb
pip install 'aesops[pandas]'        # + pandas
pip install 'aesops[polars]'        # + polars
pip install 'aesops[pandas,polars]' # both

Requires Python 3.9+.

Quickstart

from aesops import Client

# No key needed to browse — the full catalog is public
client = Client()

datasets = client.list(query="housing", license="MIT")
for ds in datasets:
    print(ds.slug, ds.row_count, ds.keyless)

# Fetch metadata + schema (works for any dataset, keyless or not)
ds = client.load_dataset("kenya-housing-prices")
print(ds.name, ds.row_count, ds.column_count)
for col in ds.detail.columns:
    print(col.name, col.dtype)

# Summary stats per column — no network call, no API key needed; built from
# the metadata already fetched by load_dataset()
print(ds.describe())
# ┌────────┬────────┬───────┬────────────┬────────┬────────┬─────────┬──────┬────────┬────────┬────────┐
# │ column │  dtype │ count │ null_count │ null_% │ unique │    mean │  std │    min │ median │    max │
# ├────────┼────────┼───────┼────────────┼────────┼────────┼─────────┼──────┼────────┼────────┼────────┤
# │   year │ number │   178 │          0 │    0.0 │     16 │ 2018.50 │ 4.30 │ 2011.0 │ 2018.50 │ 2026.0 │
# │  month │ string │   178 │          0 │    0.0 │     12 │     NaN │  NaN │    NaN │    NaN │    NaN │
# └────────┴────────┴───────┴────────────┴────────┴────────┴─────────┴──────┴────────┴────────┴────────┘

# In a Jupyter/IPython notebook (or Zed's REPL), the same call renders as a
# bordered HTML table instead when it's the last expression in a cell.

# Want a real pandas.DataFrame for further chaining (.loc, filtering, etc.)?
ds.describe().to_frame()

# A compact overview — name, slug, description, AI insights, link to the
# dataset's Aesops page, and any linked community discussions. Also no
# network call, no API key needed.
print(ds.summary())

Full catalog

list() always returns the full catalog and never sends the API key even if the Client has one configured:

client = Client()
datasets = client.list(category="finance")
for ds in datasets:
    print(ds.slug, ds.keyless)  # keyless tells you which need a key to load

This is deliberate: the catalog is public for discovery/marketing purposes. What's gated is loading actual data (see below).

Loading data

Loading a dataset's actual rows (.to_pandas(), .to_polars(), .to_duckdb(), .sql(), .to_csv()) requires a read-scoped API key — unless the dataset is currently keyless (ds.keyless), in which case no key is needed. Create a key at aesops.co.ke/workspace/api-keys.

client = Client(api_key="Aes_...")

ds = client.load_dataset("kenya-housing-prices")

# pandas
df = ds.to_pandas()

# polars
lf = ds.to_polars()

# DuckDB (predicate + projection pushdown — only fetches the row-groups you touch)
con = ds.to_duckdb()
con.sql("SELECT county, avg(price) FROM data GROUP BY county").df()

# or use ds.sql() directly
ds.sql("SELECT county, avg(price) FROM data GROUP BY 1").df()

# CSV export (client-side, no extra server compute)
ds.to_csv("/tmp/housing.csv")

# Just want a peek? `limit` is pushed down through read_parquet, so it also
# cuts what crosses the network, not just what lands in the DataFrame.
sample = ds.to_pandas(limit=100)
ds.to_polars(limit=100)
ds.to_csv("/tmp/sample.csv", limit=100)

# for anything more specific (offset, filters, ordering) use ds.sql() directly
ds.sql("SELECT * FROM data ORDER BY price DESC LIMIT 20").df()

Several datasets in one connection

By default each to_duckdb() / sql() call opens its own isolated connection with the dataset exposed as data. To query several datasets together (joins, etc.), create one connection and pass it in with a view_name — the view is created directly in your connection, still lazily read from Parquet:

import duckdb

con = duckdb.connect()
housing = client.load_dataset("kenya-housing-prices")
census = client.load_dataset("knbs-census-2019")

housing.to_duckdb(con=con, view_name="housing")
census.to_duckdb(con=con, view_name="census")

con.sql("SELECT * FROM housing JOIN census USING (county)").df()

view_name defaults to the dataset's slug (quote it in SQL if it contains hyphens: "kenya-housing-prices"). Calling it again for the same name refreshes the view. ds.sql(query, con=con, view_name=...) works the same way. Keep the Client open for as long as you use the connection — views re-fetch signed URLs through it.

Long sessions

Each view reads from a signed URL that expires after about an hour. ds.sql() and to_pandas() refresh it for you, but queries you run yourself on a connection (con.sql(...)) can't. If one starts failing with an HTTP 403 error, call ds.to_duckdb(con=con, view_name=...) again — it replaces the view with a fresh URL.

Your own unpublished datasets

load_dataset() also works for a dataset you've uploaded but not published, as long as the client has an API key from the account that owns it. Anyone else gets a not-found error.

New to DuckDB?

ds.sql() and ds.to_duckdb().sql() return a duckdb.DuckDBPyRelation — a lazy query result, not a DataFrame. Call .df() for pandas, .pl() for polars, .arrow() for an Arrow table, or .fetchall() for plain Python tuples. If SQL or DuckDB itself is new to you: the DuckDB docs are a good starting point, and the Python client guide covers the API this SDK builds on.

Brand colors

The Aesops chart palette (aeschart) is available as plain hex strings, so a chart built from Aesops data can match the platform's own look — and printing a palette renders actual color swatches, not just hex text:

from aesops import colors

colors.aeschart         # 6-slot categorical series for chart colors, teal-led
colors.dark.aeschart    # dark mode's chart series

# See the actual chart colors, not just hex codes: in a terminal, prints
# each of the 6 aeschart slots as a colored block (ANSI truecolor) next to
# its name and hex value — separately for light and dark.
print(colors.light)
print(colors.dark)

# In a Jupyter/IPython notebook, colors.light / colors.dark render as HTML
# swatches automatically when they're the last expression in a cell.

# matplotlib
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.set_prop_cycle(color=list(colors.aeschart))

No network call, no API key required — colors mirrors the same aeschart-* chart tokens the Aesops web app itself uses (see DESIGN.md), available both on the top-level (light) module and colors.light/ colors.dark explicitly.

Context manager

with Client(api_key="Aes_...") as client:
    df = client.load_dataset("kenya-housing-prices").to_pandas()

Error handling

from aesops import AuthError, NotFoundError, ApiError

try:
    ds = client.load_dataset("does-not-exist")
except NotFoundError:
    print("dataset not found")
except AuthError:
    print("invalid or missing API key")
except ApiError as e:
    print(f"API error {e.status_code}: {e}")

Notes

  • Signed URLs are fetched lazily at query time (not at load_dataset) and automatically re-fetched on expiry or a 403 from storage, so long-running sessions never stall.
  • All format conversion (CSV, Arrow, etc.) happens locally — zero extra server compute.
  • list() and load_dataset() always work without an api_key — the full catalog and every dataset's metadata are public. Loading actual data (.to_pandas(), .to_polars(), .to_duckdb(), .sql(), .to_csv()) requires a key unless the dataset is currently keyless (ds.keyless).
  • .describe() and .summary() never touch the network or require a key — they summarize the metadata load_dataset() already fetched. .describe() returns a DescribeTable (bordered text/HTML display, no pandas required); call .to_frame() on it for a real pandas.DataFrame. .summary() returns a Summary (same bordered text/HTML display) with name, slug, description, AI insights, a link to the dataset's Aesops page, and any linked community discussions.
  • Datasets are always served at their latest active version — there's no version pinning.

Metadata

Release files for aesops 0.3.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 aesops 0.3.0
File Size Uploaded Provenance
aesops-0.3.0.tar.gz 205.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for aesops 0.3.0
File Interpreter ABI Platform
aesops-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 221.0 kB

Release files / aesops-0.3.0.tar.gz

Download URL aesops-0.3.0.tar.gz
Size 205.2 kB
Tags Source
SHA-256 checksum
How to use checksums
cf978d004f382651de3727b28ba545f5c990bb68d7d936e2de40b9108bfeabef
BLAKE2b-256 checksum
How to use checksums
e4732ca6fbe392d9e31bd48b2884118b16924f95a474884adf58d99479f09ea1
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 Oct 5, 2026.

Transparency log

Release files / aesops-0.3.0-py3-none-any.whl

Download URL aesops-0.3.0-py3-none-any.whl
Size 15.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
376a149ed71aa3116f162013722f5e008dab5478232ab5a3d4897800f6c9d88e
BLAKE2b-256 checksum
How to use checksums
bb089a9c38f6204d857149d0ac200612ab7a522123c6df1134107873cc15e8a5
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 Oct 5, 2026.

Transparency log

Security for aesops 0.3.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for aesops-0.3.0.tar.gz

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 Oct 5, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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