Observed since 20 Oct, 20:51 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Python client for the Aesops dataset API — browse the catalog and load datasets into pandas, polars, or DuckDB.
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).
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+.
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())
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 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()
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.
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.
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.
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.
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.
with Client(api_key="Aes_...") as client:
df = client.load_dataset("kenya-housing-prices").to_pandas()
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}")
load_dataset) and
automatically re-fetched on expiry or a 403 from storage, so long-running
sessions never stall.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.Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.3.0 | 5 Oct 2026 | 5 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 20:51 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:c12a95359b5937aad132112b7aa4931a2ee1d74744a1cbff6affabf26cce62fd