⚡ EnergyDB
Persistent storage for energy portfolios — assets, grid topology, and 3-dimensional time series, in one connected database.
🏗️ What is EnergyDB?
EnergyDB is a database for energy portfolios. It stores three things together in one connected system:
| Layer | Description | Real-World Example |
|---|---|---|
| 🌳 Asset hierarchy | Arbitrary-depth tree of portfolios, sites, and assets | "Offshore-1 → WindTurbine T01 → power" |
| 🔗 Grid topology | Typed edges (lines, transformers, pipes, interconnections) connecting any two assets | "Cable-1: BusA → BusB" |
| ⏱️ 3-dimensional time series | Actuals and versioned forecasts attached to any node or edge, queryable as-of any point in time | "power_flow on Cable-1, valid Wed 12:00, known Mon 18:00" |
Structure lives in PostgreSQL, values live in ClickHouse, and stable UUID identity lets Python objects round-trip to the database without losing any structural state.
EnergyDB extends TimeDB with persistent storage for EnergyDataModel hierarchies.
✨ Why EnergyDB?
Most time-series systems are agnostic about what their series represent — they treat data as opaque (series_id, timestamp, value) triples. EnergyDB knows it is a portfolio, and links every series back to the asset or grid edge it describes.
-
🔁 Round-trip persistence: Every
Elementkeeps its UUID7 from in-memory object to row primary key — renames, moves, and property edits become silentUPDATEs, never delete-then-insert. -
📋 Diffable structural changes:
dry_run=Truepreviews every insert, rename, move, and delete as aTreeDiffbefore you apply — no surprise mutations, and the same preview is available across a wholetransaction(). -
⏱️ Time-of-knowledge queries: Forecast revisions, corrections, and as-of backtests, powered by TimeDB.
-
🧭 Lazy fluent navigation:
client.get_node("Portfolio", "Site", "T01").read(...)resolves to one indexed SQL query, regardless of subtree size. -
⚖️ Unit conversion at the boundary: Declare canonical units once; pint rescales every read and write automatically.
-
🧹 Idempotent writes:
write(..., skip_unchanged=True)drops rows that only duplicate the latest stored value before insert, so writing the same window repeatedly doesn't bloat storage. The comparison key is chosen per series — actuals dedupe pervalid_time, versioned forecasts per(valid_time, knowledge_time)— so a republished forecast is never mistaken for a duplicate. Opt-in; the write still returns itsrun_id(aWriteResultcarrying.written/.skippedcounts). -
🎯 Partial reads that don't fail:
read(manifest, on_missing="skip")returns every series that resolved plus the triples that didn't, so one unregistered series in a 1,500-series manifest costs you that series — not the whole batch. -
🧯 Typed errors:
NodeNotFoundError,SeriesNotFoundError(carrying every unresolved triple),ManifestError, … all under oneEnergyDBErrorbase — so you branch on types and structured fields instead of matching message text. Every raisable class also subclassesValueError, so broad handlers keep working. -
🏢 Opt-in multi-tenancy:
client.namespace("acme")returns a view of the client bound to one tenant — it shares the pool, stamps every row it writes with that namespace, and (once the host app enables PostgreSQL RLS) sees only that tenant's rows. A client that never calls it behaves exactly as a single-tenant client always has.
🚀 Quick Start
1. Installation
pip install energydb
Requires Python 3.12+, PostgreSQL 15+ (asset hierarchy + series catalog), and ClickHouse (time-series values).
Need a local Postgres + ClickHouse? One command brings both up:
cd local-db && docker compose up -d(seelocal-db/, or DEVELOPMENT.md for the full setup).
2. Usage Example
from datetime import UTC, datetime
import energydb as edb
import pandas as pd
with edb.Client() as client: # reads TIMEDB_PG_DSN / TIMEDB_CH_URL
client.create()
# 1. Declare your portfolio: a tree of typed assets with their series.
t01 = edb.wind.WindTurbine(
name="T01", capacity=3.5, hub_height=80,
timeseries=[edb.TimeSeries(name="power", unit="MW",
data_type=edb.DataType.ACTUAL)],
)
portfolio = edb.Portfolio(
name="my-portfolio",
members=[edb.Site(name="Offshore-1", members=[t01])],
)
client.register_tree(portfolio) # create-only; edit existing nodes via scope mutators
# 2. Write hourly power for that turbine.
start = datetime(2026, 1, 1, tzinfo=UTC)
df = pd.DataFrame({
"valid_time": pd.date_range(start, periods=24, freq="1h", tz="UTC"),
"value": [2.5 + 0.05 * i for i in range(24)],
})
client.get_node("my-portfolio", "Offshore-1", "T01").write(
df, name="power", data_type="actual",
)
# 3. Read across the whole portfolio in one fluent call.
client.get_node("my-portfolio").read(name="power", data_type="actual")
# the pool and background loop are released once the `with` block exits;
# without it, call client.close() yourself when you're done.
Async?
edb.Clientis a synchronous facade overedb.AsyncClient. Forasync/awaitcode, useAsyncClientdirectly, either as an async context manager (async with edb.AsyncClient(...) as client:) or withawait client.open()/await client.close()around every method shown above.
🧪 Try it in Google Colab
Want to try EnergyDB without a local setup? Open our Quickstart in Colab — the first cell automatically installs PostgreSQL + ClickHouse inside the VM.
Note: Data persists only within the active Colab session. Additional notebooks are available in the
examples/directory.
📚 Documentation & Resources
- 📖 Official Documentation
- ⚙️ Installation Guide
- 🐍 Python SDK Documentation
- 🌐 Reference
- 💡 Examples & Notebooks
📦 Related Projects
| Project | Description |
|---|---|
| TimeDB | 3-dimensional time-series storage on ClickHouse with auditability and overlapping-forecast support |
| TimeDataModel | Pythonic data model for time series |
| EnergyDataModel | Data model for energy assets (solar, wind, battery, grid, ...) |
🤝 Contributing
Contributions are welcome! If you're interested in improving EnergyDB, please see our Development Guide for local setup instructions.
Licensed under the Apache-2.0 License.
Find a bug or have a feature request? Open an Issue.
Metadata
Release files for energydb 0.12.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| energydb-0.12.0.tar.gz | 183.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| energydb-0.12.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 289.6 kB
Release files / energydb-0.12.0.tar.gz
| Download URL | energydb-0.12.0.tar.gz |
|---|---|
| Size | 183.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9fdcbca82bec750bd33d38db6957177a3a3a636eca64b833d8eb792711efbc4f
|
|
BLAKE2b-256 checksum How to use checksums |
6513230434449449c11b40974c7578c1b6efc20242cc2471174f134eccdb5ec2
|
| 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 Aug 28, 2026.
Transparency logRelease files / energydb-0.12.0-py3-none-any.whl
| Download URL | energydb-0.12.0-py3-none-any.whl |
|---|---|
| Size | 106.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b52195c4b99bbbf42de765e18c14d4cff748ff41a69783f314e5d93c4426536b
|
|
BLAKE2b-256 checksum How to use checksums |
c4c3335e8642d612c6b8ca3816e1971060380693ae8410467fee1d6309fd6473
|
| 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 Aug 28, 2026.
Transparency log