rebase.energy

Bulk record reads

Totals independent of paging, server-side filtering and trimming, and a streamed export of a whole type.

Pagination is the right tool for a screen of records and the wrong one for a data consumer that wants everything. Three things cover that case.

Totals

GET /objects/{object_name}/stats answers whole-type questions in one call, independent of paging:

r = requests.get(f"{BASE}/objects/assets/stats", headers=HEADERS)
r.raise_for_status()
print(r.json())
# {"total": 3, "sums": {"capacity_kw": 212800.0}, "type_counts": {"wind_turbine": 3}}

total is the record count. sums totals the type's numeric fields: capacity_kw for the built-in assets, every number attribute for a custom type. type_counts breaks the count down by subtype where the type has one (assets by values.type); it is empty otherwise.

A filtered, trimmed page

Filtering and trimming happen server-side, so the client does not download what it will discard:

r = requests.get(f"{BASE}/objects/plants", headers=HEADERS, params={
    "filter[fuel]": "wind",
    "fields": "name,capacity_mw,location",
    "limit": 200,
})
r.raise_for_status()
  • filter[<attribute>]=value keeps records whose attribute has that value. Repeat the parameter for several values of one attribute (OR-ed) or for several attributes (AND-ed). Filters work on custom types; on the built-ins they are a 400. Link attributes filter by id, see Linking records.
  • fields=a,b trims values to the named attributes. The envelope always rides along.

A whole type, streamed

GET /objects/{object_name}/export walks a server-side cursor and streams the result, so memory stays flat however many records there are. format=ndjson (the default) gives one JSON record per line:

import json

with requests.get(f"{BASE}/objects/plants/export", headers=HEADERS,
                  params={"fields": "name,capacity_mw,location"}, stream=True) as r:
    r.raise_for_status()
    print(r.headers["X-Total-Count"], "records; ETag", r.headers["ETag"])
    etag = r.headers["ETag"]
    for line in r.iter_lines():
        if line:
            record = json.loads(line)

?filter[...] and ?fields= apply to the export exactly as to the list.

Cheap re-exports

The ETag is derived from the type's size and newest updated_at, so a repeat export of unchanged data costs one round trip and no body:

r = requests.get(f"{BASE}/objects/plants/export",
                 headers={**HEADERS, "If-None-Match": etag})
print(r.status_code)   # 304 when nothing changed

GeoJSON

format=geojson returns a FeatureCollection built from the type's geometry attribute; the remaining fields become each feature's properties. When a type has several geometry attributes, ?geometry_field= picks the one to use.

r = requests.get(f"{BASE}/objects/zones/export", headers=HEADERS,
                 params={"format": "geojson", "fields": "name,country"})
r.raise_for_status()
zones = r.json()
print(zones["type"], len(zones["features"]))   # FeatureCollection 12

This is the file a map draws areas from. For the series behind each area, see Many series at once.

On this page