Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions examples/demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import logging
import os

from dotenv import load_dotenv

from tinygrid import ERCOT, ERCOTAuth, ERCOTAuthConfig
from tinygrid.ercot.archive import ERCOTArchiveBundle

load_dotenv()

logging.basicConfig(level=logging.INFO)

logger = logging.getLogger(__name__)

DEBUG = False


def main():
auth = ERCOTAuth(
ERCOTAuthConfig(
username=os.getenv("ERCOT_USERNAME"),
password=os.getenv("ERCOT_PASSWORD"),
subscription_key=os.getenv("ERCOT_SUBSCRIPTION_KEY"),
)
)

ercot = ERCOT(auth=auth)

logger.info("TEST get_60_day_dam_disclosure")
reports = ercot.get_60_day_dam_disclosure("2025-10-30")
for name, df in reports.items():
logger.debug(f"{name}")
logger.debug(df.head())
logger.debug("*" * 30)

logger.info("TEST get_60_day_dam_disclosure")
reports = ercot.get_60_day_sced_disclosure("2025-10-30")
logger.debug(reports)

start_ts = "2025-12-20"
end_ts = "2025-12-30"

logger.info("TEST get_system_wide_actuals")
results = ercot.get_system_wide_actuals(start=start_ts, end=end_ts)
logger.info(results.head())
logger.debug(results)

logger.info("TEST get_se_load")
results = ercot.get_se_load(start=start_ts, end=end_ts)
logger.debug(results)

logger.info("TEST get_se_dc_tie_flows")
results = ercot.get_se_dc_tie_flows(start_ts, end_ts)
logger.debug(results)

bundle_client = ERCOTArchiveBundle(ercot=ercot)
bundles = bundle_client.bundles("NP4-33-CD")
for bundle_link in bundles.links:
logger.debug(f"{bundle_link = }")
logger.debug("*" * 30)

dfs = bundle_client.one_bundle(bundles.links[0])
logger.debug(dfs)


if __name__ == "__main__":
main()
119 changes: 119 additions & 0 deletions examples/ercot_app.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"id": "ad6e4c18",
"metadata": {},
"outputs": [],
"source": [
"%load_ext autoreload\n",
"%autoreload 2"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "e0a56088",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"\n",
"import pandas as pd\n",
"from dotenv import load_dotenv\n",
"\n",
"from tinygrid import ERCOT, ERCOTAuth, ERCOTAuthConfig, LocationType, Market\n",
"\n",
"# Load environment variables from .env file\n",
"load_dotenv()\n",
"pd.set_option(\"display.max_columns\", 20)\n",
"pd.set_option(\"display.width\", 200)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b4e2d805",
"metadata": {},
"outputs": [],
"source": [
"auth = ERCOTAuth(ERCOTAuthConfig(\n",
" username=os.getenv(\"ERCOT_USERNAME\"),\n",
" password=os.getenv(\"ERCOT_PASSWORD\"),\n",
" subscription_key=os.getenv(\"ERCOT_SUBSCRIPTION_KEY\"),\n",
"))\n",
"\n",
"ercot = ERCOT(auth=auth)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "8f410d19",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'https://api.ercot.com/api/public-reports'"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ercot.base_url"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ea9b771d",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"No archives found for /np3-966-er/60_dam_gen_res_data from 2025-12-30 23:00:00-06:00 to 2025-12-31 23:00:00-06:00\n"
]
}
],
"source": [
"ercot.get_60_day_dam_disclosure(\"2025-11-1\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6d01935c",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.14.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
6 changes: 3 additions & 3 deletions examples/ercot_demo.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": 27,
"execution_count": 51,
"metadata": {},
"outputs": [
{
Expand Down Expand Up @@ -55,7 +55,7 @@
},
{
"cell_type": "code",
"execution_count": 28,
"execution_count": 52,
"metadata": {
"execution": {
"iopub.execute_input": "2025-12-28T16:14:56.927060Z",
Expand Down Expand Up @@ -89,7 +89,7 @@
},
{
"cell_type": "code",
"execution_count": 29,
"execution_count": 53,
"metadata": {
"execution": {
"iopub.execute_input": "2025-12-28T16:14:57.854493Z",
Expand Down
8 changes: 7 additions & 1 deletion pyercot/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# pyercot

A client library for accessing ERCOT Public API Client/Developer Documentation

## Usage

First, create a client:

```python
Expand Down Expand Up @@ -64,6 +66,7 @@ client = AuthenticatedClient(
```

Things to know:

1. Every path/method combo becomes a Python module with four functions:
1. `sync`: Blocking request that returns parsed data (if successful) or `None`
1. `sync_detailed`: Blocking request that always returns a `Request`, optionally with `parsed` set if the request was successful.
Expand Down Expand Up @@ -110,13 +113,16 @@ client.set_httpx_client(httpx.Client(base_url="https://api.example.com", proxies
```

## Building / publishing this package

This project uses [uv](https://github.com/astral-sh/uv) to manage dependencies and packaging. Here are the basics:

1. Update the metadata in `pyproject.toml` (e.g. authors, version).
2. If you're using a private repository: https://docs.astral.sh/uv/guides/integration/alternative-indexes/
2. If you're using a private repository: <https://docs.astral.sh/uv/guides/integration/alternative-indexes/>
3. Build a distribution with `uv build`, builds `sdist` and `wheel` by default.
1. Publish the client with `uv publish`, see documentation for publishing to private indexes.

If you want to install this client into another project without publishing it (e.g. for development) then:

1. If that project **is using uv**, you can simply do `uv add <path-to-this-client>` from that project
1. If that project is not using uv:
1. Build a wheel with `uv build --wheel`.
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies = [
"tenacity>=8.0.0",
"pandas>=2.0.0",
"openpyxl>=3.1.5",
"returns>=0.26.0",
]

[project.urls]
Expand Down Expand Up @@ -107,6 +108,7 @@ dev = [
"kaleido>=1.2.0",
"nbformat>=5.10.4",
"plotly>=6.5.0",
"ty>=0.0.8",
]

[tool.setuptools.packages.find]
Expand Down
46 changes: 6 additions & 40 deletions tests/test_ercot_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,57 +422,23 @@ def _needs_historical(self, start: pd.Timestamp, market: str) -> bool: # type:
archive.fetch_historical.assert_called_once()


def test_get_60_day_dam_disclosure_uses_archive(
monkeypatch: pytest.MonkeyPatch,
) -> None:
def test_get_60_day_dam_disclosure_uses_archive() -> None:
client = ERCOT()

archive = MagicMock()
archive.fetch_historical.return_value = pd.DataFrame(
{"DeliveryDate": ["2024-01-01"]}
)
monkeypatch.setattr(client, "_get_archive", lambda: archive)

for method_name in [
"get_dam_gen_res_as_offers",
"get_dam_load_res_data",
"get_dam_load_res_as_offers",
"get_dam_energy_only_offers",
"get_dam_energy_only_offer_awards",
"get_dam_energy_bids",
"get_dam_energy_bid_awards",
"get_dam_ptp_obl_bids",
"get_dam_ptp_obl_bid_awards",
"get_dam_ptp_obl_opt",
"get_dam_ptp_obl_opt_awards",
]:
monkeypatch.setattr(
client, method_name, lambda **kwargs: pd.DataFrame({"dummy": [1]})
)

reports = client.get_60_day_dam_disclosure("today")

assert "dam_gen_resource" in reports
client.get_60_day_dam_disclosure()
archive.fetch_historical.assert_called_once()


def test_get_60_day_sced_disclosure(monkeypatch: pytest.MonkeyPatch) -> None:
def test_get_60_day_sced_disclosure() -> None:
client = ERCOT()

archive = MagicMock()
archive.fetch_historical.return_value = pd.DataFrame(
documents = MagicMock()
documents.get_60d_sced_disclosure.return_value = pd.DataFrame(
{"DeliveryDate": ["2024-01-01"]}
)
monkeypatch.setattr(client, "_get_archive", lambda: archive)

monkeypatch.setattr(
client, "get_sced_gen_res_data", lambda **kwargs: pd.DataFrame({"a": [1]})
)
monkeypatch.setattr(
client, "get_load_res_data_in_sced", lambda **kwargs: pd.DataFrame({"b": [2]})
)

reports = client.get_60_day_sced_disclosure("today")

assert "sced_smne" in reports
archive.fetch_historical.assert_called_once()
documents._get_document.assert_called_once()
2 changes: 1 addition & 1 deletion tinygrid/ercot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ class ERCOT(
ERCOTDocumentsMixin,
ERCOTDashboardMixin,
):
"""ERCOT (Electric Reliability Council of Texas) SDK client.
"""ERCOT (Electricity Reliability Council of Texas) SDK client.

Provides a clean, intuitive interface for accessing ERCOT grid data without
needing to know about endpoint paths, API categories, or client lifecycle management.
Expand Down
Loading
Loading