Skip to content

Commit dcc35f7

Browse files
committed
feat: migrate catalog from hardcoded Python to YAML-backed manager
Replace inline catalog definitions with YAML-backed ENTSOECatalogManager, add IT_SUD neighbour mapping for GR, expose catalog on client, add pyyaml dependency, and mark integration tests.
1 parent bd6dca7 commit dcc35f7

5 files changed

Lines changed: 25 additions & 192 deletions

File tree

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ classifiers = [
2222
]
2323
dependencies = [
2424
"pandas>=2.0",
25+
"pyyaml>=6.0",
2526
"requests>=2.28",
2627
"typer>=0.9",
2728
"rich>=13.0",
@@ -46,6 +47,9 @@ packages = ["src/entsoe"]
4647

4748
[tool.pytest.ini_options]
4849
testpaths = ["tests"]
50+
markers = [
51+
"integration: tests that hit real APIs (deselect with -m 'not integration')",
52+
]
4953

5054
[dependency-groups]
5155
dev = [

src/entsoe/_mappings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ def psr_name(identifier: str) -> str:
399399
"BG": {"RO", "RS", "MK", "GR", "TR"},
400400
"MK": {"RS", "BG", "GR", "AL"},
401401
"GR": {"BG", "MK", "AL", "IT_SUD", "TR"},
402+
"IT_SUD": {"GR"},
402403
"AL": {"RS", "MK", "GR", "ME"},
403404
"ME": {"RS", "BA", "AL"},
404405
"TR": {"BG", "GR"},

src/entsoe/catalog.py

Lines changed: 14 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -7,202 +7,25 @@
77

88
from __future__ import annotations
99

10-
from dataclasses import dataclass, field
10+
# Re-export dataclasses for backward compatibility
11+
from entsoe._models import Category, Endpoint
12+
from entsoe.catalog_manager import ENTSOECatalogManager as _Manager
1113

14+
__all__ = ["Category", "Endpoint", "CATEGORIES", "get_category", "summary"]
1215

13-
@dataclass(frozen=True)
14-
class Endpoint:
15-
"""A queryable data endpoint."""
16+
# ── Load categories from YAML ──────────────────────────────────────────
1617

17-
name: str
18-
description: str
19-
method: str # Python client method name
20-
cli: str # CLI command
21-
resolution: str # e.g. "15min", "hourly", "daily"
22-
columns: tuple[str, ...] # key columns returned
23-
units: str
24-
notes: str = ""
18+
_mgr = _Manager()
19+
_cats = {c.key: c for c in _mgr._load_categories()}
2520

21+
CATEGORIES: dict[str, Category] = _cats
2622

27-
@dataclass(frozen=True)
28-
class Category:
29-
"""A top-level data category grouping related endpoints."""
30-
31-
name: str
32-
description: str
33-
namespace: str # Python client namespace (e.g. "prices")
34-
endpoints: tuple[Endpoint, ...]
35-
36-
37-
# ── Prices ──────────────────────────────────────────────────────────────
38-
39-
PRICES = Category(
40-
name="Prices",
41-
description="Day-ahead electricity market prices",
42-
namespace="prices",
43-
endpoints=(
44-
Endpoint(
45-
name="Day-Ahead Prices",
46-
description="Hourly day-ahead market clearing prices per bidding zone",
47-
method="client.prices.day_ahead(start, end, country='FR')",
48-
cli="entsoe prices day-ahead -s 2024-06-01 -e 2024-06-08 -c FR",
49-
resolution="hourly",
50-
columns=("timestamp", "country", "value", "currency", "quantity_unit"),
51-
units="EUR/MWh",
52-
),
53-
),
54-
)
55-
56-
# ── Load ────────────────────────────────────────────────────────────────
57-
58-
LOAD = Category(
59-
name="Load",
60-
description="Actual system load and day-ahead load forecasts",
61-
namespace="load",
62-
endpoints=(
63-
Endpoint(
64-
name="Actual Total Load",
65-
description="Actual total electricity consumption per control area",
66-
method="client.load.actual(start, end, country='FR')",
67-
cli="entsoe load actual -s 2024-06-01 -e 2024-06-08 -c FR",
68-
resolution="15min or hourly (varies by country)",
69-
columns=("timestamp", "country", "value", "quantity_unit"),
70-
units="MW",
71-
),
72-
Endpoint(
73-
name="Day-Ahead Load Forecast",
74-
description="Day-ahead forecast of total electricity consumption",
75-
method="client.load.forecast(start, end, country='FR')",
76-
cli="entsoe load forecast -s 2024-06-01 -e 2024-06-08 -c FR",
77-
resolution="15min or hourly (varies by country)",
78-
columns=("timestamp", "country", "value", "quantity_unit"),
79-
units="MW",
80-
),
81-
),
82-
)
83-
84-
# ── Generation ──────────────────────────────────────────────────────────
85-
86-
GENERATION = Category(
87-
name="Generation",
88-
description="Actual generation, forecasts, installed capacity, and per-plant output",
89-
namespace="generation",
90-
endpoints=(
91-
Endpoint(
92-
name="Actual Generation per Type",
93-
description="Actual electricity generation broken down by fuel/technology type (PSR)",
94-
method="client.generation.actual(start, end, country='FR')",
95-
cli="entsoe generation actual -s 2024-06-01 -e 2024-06-08 -c FR",
96-
resolution="15min or hourly",
97-
columns=("timestamp", "country", "psr_type", "value", "quantity_unit"),
98-
units="MW",
99-
notes="Filter by PSR type: --psr solar --psr wind_onshore",
100-
),
101-
Endpoint(
102-
name="Generation Forecast (Wind/Solar)",
103-
description="Day-ahead forecast for wind and solar generation",
104-
method="client.generation.forecast(start, end, country='FR')",
105-
cli="entsoe generation forecast -s 2024-06-01 -e 2024-06-08 -c FR",
106-
resolution="15min or hourly",
107-
columns=("timestamp", "country", "psr_type", "value", "quantity_unit"),
108-
units="MW",
109-
),
110-
Endpoint(
111-
name="Installed Generation Capacity",
112-
description="Installed generation capacity per type (updated annually or on change)",
113-
method="client.generation.installed_capacity(start, end, country='FR')",
114-
cli="entsoe generation capacity -s 2024-06-01 -e 2024-06-08 -c FR",
115-
resolution="yearly (point-in-time)",
116-
columns=("timestamp", "country", "psr_type", "value", "quantity_unit"),
117-
units="MW",
118-
),
119-
Endpoint(
120-
name="Actual Generation per Plant",
121-
description="Generation output per individual production unit (>= 100 MW)",
122-
method="client.generation.per_plant(start, end, country='FR')",
123-
cli="entsoe generation per-plant -s 2024-06-01 -e 2024-06-08 -c FR",
124-
resolution="15min or hourly",
125-
columns=("timestamp", "country", "unit_name", "psr_type", "value", "quantity_unit"),
126-
units="MW",
127-
),
128-
),
129-
)
130-
131-
# ── Transmission ────────────────────────────────────────────────────────
132-
133-
TRANSMISSION = Category(
134-
name="Transmission",
135-
description="Cross-border physical flows, scheduled exchanges, and transfer capacity",
136-
namespace="transmission",
137-
endpoints=(
138-
Endpoint(
139-
name="Physical Cross-Border Flows",
140-
description="Actual measured electricity flows between bidding zones",
141-
method="client.transmission.crossborder_flows(start, end, country_from='FR', country_to='DE_LU')",
142-
cli="entsoe transmission flows -s 2024-06-01 -e 2024-06-08 --from FR --to DE_LU",
143-
resolution="15min or hourly",
144-
columns=("timestamp", "in_domain", "value", "quantity_unit"),
145-
units="MW",
146-
notes="Use --border for multi-border queries: --border FR-*, --border iberian",
147-
),
148-
Endpoint(
149-
name="Scheduled Commercial Exchanges",
150-
description="Day-ahead scheduled commercial power exchanges between zones",
151-
method="client.transmission.scheduled_exchanges(start, end, country_from='FR', country_to='ES')",
152-
cli="entsoe transmission exchanges -s 2024-06-01 -e 2024-06-08 --from FR --to ES",
153-
resolution="hourly",
154-
columns=("timestamp", "in_domain", "value", "quantity_unit"),
155-
units="MW",
156-
),
157-
Endpoint(
158-
name="Net Transfer Capacity",
159-
description="Available transfer capacity (NTC) between bidding zones",
160-
method="client.transmission.net_transfer_capacity(start, end, country_from='FR', country_to='ES')",
161-
cli="entsoe transmission capacity -s 2024-06-01 -e 2024-06-08 --from FR --to ES",
162-
resolution="hourly",
163-
columns=("timestamp", "in_domain", "value", "quantity_unit"),
164-
units="MW",
165-
),
166-
),
167-
)
168-
169-
# ── Balancing ───────────────────────────────────────────────────────────
170-
171-
BALANCING = Category(
172-
name="Balancing",
173-
description="System imbalance prices and volumes",
174-
namespace="balancing",
175-
endpoints=(
176-
Endpoint(
177-
name="Imbalance Prices",
178-
description="System imbalance settlement prices",
179-
method="client.balancing.imbalance_prices(start, end, country='FR')",
180-
cli="entsoe balancing prices -s 2024-06-01 -e 2024-06-08 -c FR",
181-
resolution="15min or hourly (varies by country)",
182-
columns=("timestamp", "country", "value", "quantity_unit"),
183-
units="EUR/MWh",
184-
),
185-
Endpoint(
186-
name="Imbalance Volumes",
187-
description="Net system imbalance volumes",
188-
method="client.balancing.imbalance_volumes(start, end, country='FR')",
189-
cli="entsoe balancing volumes -s 2024-06-01 -e 2024-06-08 -c FR",
190-
resolution="15min or hourly (varies by country)",
191-
columns=("timestamp", "country", "value", "quantity_unit"),
192-
units="MW",
193-
),
194-
),
195-
)
196-
197-
# ── Full catalog ────────────────────────────────────────────────────────
198-
199-
CATEGORIES: dict[str, Category] = {
200-
"prices": PRICES,
201-
"load": LOAD,
202-
"generation": GENERATION,
203-
"transmission": TRANSMISSION,
204-
"balancing": BALANCING,
205-
}
23+
# Individual constants for backward compatibility
24+
PRICES = CATEGORIES["prices"]
25+
LOAD = CATEGORIES["load"]
26+
GENERATION = CATEGORIES["generation"]
27+
TRANSMISSION = CATEGORIES["transmission"]
28+
BALANCING = CATEGORIES["balancing"]
20629

20730

20831
def get_category(name: str) -> Category:

src/entsoe/client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from ._http import HttpClient
99
from .cache import CacheConfig, CacheStore
10+
from .catalog_manager import ENTSOECatalogManager
1011
from .namespaces import (
1112
BalancingNamespace,
1213
GenerationNamespace,
@@ -100,6 +101,8 @@ def __init__(
100101
self.transmission = TransmissionNamespace(http, tz=tz, cache=cache_store)
101102
self.balancing = BalancingNamespace(http, tz=tz, cache=cache_store)
102103

104+
self.catalog = ENTSOECatalogManager(self)
105+
103106
# Expose for CLI cache commands
104107
self._cache = cache_store
105108

uv.lock

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)