Catalog PyPI
PACKAGE EXPLORERView in registry

aesops

PyPI

Python client for the Aesops dataset API — browse the catalog and load datasets into pandas, polars, or DuckDB.

Versions collected1
Direct dependencies5From release 0.3.0
Last observed20 Oct, 20:51 UTC

README

Version 0.3.0 documentation

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.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.3.05 Oct 20265

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 20:51 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:c12a95359b5937aad132112b7aa4931a2ee1d74744a1cbff6affabf26cce62fd