Getting started
This walkthrough mirrors the public API exposed by the SDK so you can go from zero to a working query in minutes.
1. Install and import
Beacon API supports Python 3.10+. Install it from PyPI and import the pieces you need:
Editor support
The project ships typed stubs (py.typed) so VS Code, PyCharm, or notebooks provide signature help and inline documentation out of the box.
2. Create a client
Instantiate Client with the Beacon base URL and (optionally) headers for authentication. The constructor normalizes headers, sets JSON defaults, and validates connectivity by calling /api/health.
The examples on this page use the public World Ocean Database (WOD) node at https://beacon-wod.maris.nl, so they run without authentication. Always pass a user_agent identifying your application (and ideally a contact address): on shared or public nodes it lets operators attribute traffic and reach you if needed.
client = Client(
"https://beacon-wod.maris.nl",
user_agent="my-app/1.0 (you@example.com)",
# jwt_token="<optional bearer token>", # for protected nodes
# proxy_headers={"X-Forwarded-For": "<ip>"},
# basic_auth=("user", "pass") is also supported
)
Use client.check_status() to verify connectivity and print the Beacon version, or client.get_server_info() to inspect the metadata returned by /api/info.
3. Discover tables and datasets
list_tables() returns a mapping of table names to DataTable helpers that already know their description and type.
tables = client.list_tables()
stations = tables["stations-collection"]
print(stations.get_table_description())
schema = stations.get_table_schema() # dict[str, type] -> {column: python type}
schema_arrow = stations.get_table_schema_arrow() # pyarrow.Schema with pyarrow fields
If your Beacon Node is running v1.4.0 or later, use list_datasets() to enumerate file-backed resources and derive a query directly from a Dataset:
datasets = client.list_datasets(pattern="*.parquet", limit=5)
file = datasets["wod/2024-01.parquet"]
print(file.get_file_format(), file.get_file_name())
dataset_schema = file.get_schema()
dataset_query = file.query()
Deprecation notice
Client.query() and Client.subset() are still available for backwards compatibility but emit deprecation warnings. Prefer starting from a table (tables["default"].query()) or dataset (file.query()).
4. Build a JSON query
All table and dataset helpers return a JSONQuery, a fluent builder with chainable selects and filters:
tables = client.list_tables()
df = (
tables['default'] # Select the 'default' table as our data source
.query() # Create a new query on the selected table
.add_select_column("lon", alias="longitude") # Select the longitude column
.add_select_column("lat", alias="latitude") # Select the latitude column
.add_select_column("z", alias="depth") # Select the depth column
.add_select_column("time")
.add_select_column("Temperature")
.add_select_column("Salinity")
.add_range_filter("time", "2020-01-01T00:00:00", "2021-01-01T00:00:00") # Filter for time between 2020 and 2021
.add_range_filter("z", 0, 10) # Filter for depth between 0 and 10 m
.to_pandas_dataframe() # Execute the query and return the results as a Pandas DataFrame
)
df
from datetime import datetime
query = (
stations
.query()
.add_select_columns([
("LONGITUDE", None),
("LATITUDE", None),
("JULD", None),
("TEMP", "temperature_c"),
("PSAL", "salinity"),
])
.add_select_coalesced(["SEA_NAME", "BASIN"], alias="water_body")
.add_range_filter("JULD", datetime(2024, 1, 1), datetime(2024, 6, 1))
.add_range_filter("PRES", 0, 10)
.add_polygon_filter(
longitude_column="LONGITUDE",
latitude_column="LATITUDE",
polygon=[(-5.2, 52.0), (-5.2, 52.5), (-4.2, 52.5), (-4.2, 52.0), (-5.2, 52.0)],
)
)
Need a quick spatial/temporal subset without writing filters manually? DataTable.subset() wraps the same builder and automatically selects longitude/latitude/depth/time columns.
subset_query = stations.subset(
longitude_column="LONGITUDE",
latitude_column="LATITUDE",
time_column="JULD",
depth_column="PRES",
columns=["TEMP", "PSAL"],
bbox=(-20, 40, -10, 50),
depth_range=(0, 50),
)
5. Execute the query
Every query inherits rich output helpers from BaseQuery:
query = tables['argo'].query()
... # build up the query as shown above
# Serialize results into various formats
df = query.to_pandas_dataframe()
gdf = query.to_geo_pandas_dataframe("LONGITUDE", "LATITUDE")
query.to_parquet("subset.parquet")
query.to_geoparquet("subset.geoparquet", "LONGITUDE", "LATITUDE")
query.to_netcdf("subset.nc")
query.to_nd_netcdf("subset_nd.nc", dimension_columns=["LONGITUDE", "LATITUDE", "JULD"])
query.to_zarr("subset.zarr")
Beacon compatibility
to_nd_netcdf requires Beacon Node v1.5.0 or newer.
Need lazy/out-of-core execution? Use execute_streaming() to pull results batch by batch as a PyArrow RecordBatchStreamReader (Beacon ≥ 1.5.0), or to_xarray_dataset(dimension_columns, chunks=...) for a dask-backed, chunked xarray dataset.
Profiling and explain
Call query.explain() to retrieve the Beacon execution plan, or query.execute() to inspect the raw HTTP response.
6. Running SQL directly
When you already have SQL, skip the builder and call:
sql = client.sql_query("""
SELECT lon, lat, z, time, Temperature, Salinity
FROM "default"
WHERE time BETWEEN '2020-01-01T00:00:00' AND '2020-06-30T23:59:59'
""")
df = sql.to_pandas_dataframe()
print(df)
For result sets too large to buffer in memory, use client.sql_query_streaming(...) instead. It returns a PyArrow RecordBatchStreamReader you can iterate batch by batch (requires Beacon ≥ 1.5.0):
reader = client.sql_query_streaming('SELECT lon, lat, z, time, Temperature FROM "default"')
for batch in reader:
# batch is a pyarrow.RecordBatch
print(batch.num_rows)
Next steps
- Deep dive into exploring datasets and tables.
- Learn the fluent APIs in Querying the Beacon Data Lake.
- Browse the auto-generated API reference.