python-entsoe 0.6.3__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- entsoe/_mappings.py +24 -0
- entsoe/cli/_output.py +8 -2
- entsoe/cli/app.py +4 -3
- entsoe/cli/balancing.py +4 -2
- entsoe/cli/config_cmd.py +5 -1
- entsoe/cli/exec_cmd.py +10 -5
- entsoe/cli/generation.py +8 -4
- entsoe/cli/load.py +4 -2
- entsoe/cli/prices.py +2 -1
- entsoe/cli/transmission.py +6 -3
- entsoe/client.py +12 -5
- entsoe/namespaces/_base.py +53 -1
- entsoe/namespaces/balancing.py +12 -4
- entsoe/namespaces/generation.py +23 -6
- entsoe/namespaces/load.py +12 -4
- entsoe/namespaces/prices.py +6 -2
- entsoe/namespaces/transmission.py +21 -8
- {python_entsoe-0.6.3.dist-info → python_entsoe-0.7.0.dist-info}/METADATA +7 -3
- python_entsoe-0.7.0.dist-info/RECORD +35 -0
- {python_entsoe-0.6.3.dist-info → python_entsoe-0.7.0.dist-info}/WHEEL +1 -1
- python_entsoe-0.6.3.dist-info/RECORD +0 -35
- {python_entsoe-0.6.3.dist-info → python_entsoe-0.7.0.dist-info}/entry_points.txt +0 -0
entsoe/_mappings.py
CHANGED
|
@@ -64,6 +64,30 @@ AREA_CODES: dict[str, str] = {
|
|
|
64
64
|
"XK": "10Y1001C--00100H",
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
# IANA timezone of each ENTSO-E bidding area. Keys intentionally mirror AREA_CODES.
|
|
68
|
+
AREA_TZ: dict[str, str] = {
|
|
69
|
+
"AL": "Europe/Tirane", "AT": "Europe/Vienna", "BA": "Europe/Sarajevo",
|
|
70
|
+
"BE": "Europe/Brussels", "BG": "Europe/Sofia", "CH": "Europe/Zurich",
|
|
71
|
+
"CZ": "Europe/Prague", "DE": "Europe/Berlin", "DE_LU": "Europe/Berlin",
|
|
72
|
+
"DE_AT_LU": "Europe/Berlin", "DK": "Europe/Copenhagen",
|
|
73
|
+
"DK_1": "Europe/Copenhagen", "DK_2": "Europe/Copenhagen",
|
|
74
|
+
"EE": "Europe/Tallinn", "ES": "Europe/Madrid", "FI": "Europe/Helsinki",
|
|
75
|
+
"FR": "Europe/Paris", "GB": "Europe/London", "GR": "Europe/Athens",
|
|
76
|
+
"HR": "Europe/Zagreb", "HU": "Europe/Budapest", "IE": "Europe/Dublin",
|
|
77
|
+
"IE_SEM": "Europe/Dublin", "IT": "Europe/Rome", "IT_NORTH": "Europe/Rome",
|
|
78
|
+
"IT_CNOR": "Europe/Rome", "IT_CSUD": "Europe/Rome", "IT_SUD": "Europe/Rome",
|
|
79
|
+
"IT_SICI": "Europe/Rome", "IT_SARD": "Europe/Rome", "LT": "Europe/Vilnius",
|
|
80
|
+
"LU": "Europe/Luxembourg", "LV": "Europe/Riga", "ME": "Europe/Podgorica",
|
|
81
|
+
"MK": "Europe/Skopje", "MT": "Europe/Malta", "NL": "Europe/Amsterdam",
|
|
82
|
+
"NO": "Europe/Oslo", "NO_1": "Europe/Oslo", "NO_2": "Europe/Oslo",
|
|
83
|
+
"NO_3": "Europe/Oslo", "NO_4": "Europe/Oslo", "NO_5": "Europe/Oslo",
|
|
84
|
+
"PL": "Europe/Warsaw", "PT": "Europe/Lisbon", "RO": "Europe/Bucharest",
|
|
85
|
+
"RS": "Europe/Belgrade", "SE": "Europe/Stockholm", "SE_1": "Europe/Stockholm",
|
|
86
|
+
"SE_2": "Europe/Stockholm", "SE_3": "Europe/Stockholm", "SE_4": "Europe/Stockholm",
|
|
87
|
+
"SI": "Europe/Ljubljana", "SK": "Europe/Bratislava", "TR": "Europe/Istanbul",
|
|
88
|
+
"UA": "Europe/Kyiv", "UK": "Europe/London", "XK": "Europe/Belgrade",
|
|
89
|
+
}
|
|
90
|
+
|
|
67
91
|
# ISO code → human-readable country/area name
|
|
68
92
|
COUNTRY_NAMES: dict[str, str] = {
|
|
69
93
|
"AL": "Albania",
|
entsoe/cli/_output.py
CHANGED
|
@@ -34,7 +34,10 @@ def output(df, format: str, title: str = "", output_path: str | None = None):
|
|
|
34
34
|
typer.echo(text)
|
|
35
35
|
|
|
36
36
|
elif format == "json":
|
|
37
|
-
|
|
37
|
+
display = df.copy()
|
|
38
|
+
if "timestamp" in display:
|
|
39
|
+
display["timestamp"] = display["timestamp"].map(lambda value: value.isoformat())
|
|
40
|
+
text = display.to_json(orient="records", indent=2)
|
|
38
41
|
if output_path:
|
|
39
42
|
with open(output_path, "w") as f:
|
|
40
43
|
f.write(text)
|
|
@@ -72,7 +75,10 @@ def render_result(result, format: str, output_path: str | None) -> None:
|
|
|
72
75
|
if format == "csv":
|
|
73
76
|
text = result.to_csv()
|
|
74
77
|
elif format == "json":
|
|
75
|
-
|
|
78
|
+
display = result.copy()
|
|
79
|
+
if "timestamp" in display:
|
|
80
|
+
display["timestamp"] = display["timestamp"].map(lambda value: value.isoformat())
|
|
81
|
+
text = display.to_json(orient="records", indent=2)
|
|
76
82
|
else:
|
|
77
83
|
table = Table()
|
|
78
84
|
idx_name = result.index.name or ""
|
entsoe/cli/app.py
CHANGED
|
@@ -6,7 +6,7 @@ from typing import Optional
|
|
|
6
6
|
|
|
7
7
|
import typer
|
|
8
8
|
|
|
9
|
-
from entsoe.cli.config import get_api_key
|
|
9
|
+
from entsoe.cli.config import get_api_key, get_config
|
|
10
10
|
|
|
11
11
|
app = typer.Typer(
|
|
12
12
|
name="entsoe",
|
|
@@ -15,7 +15,7 @@ app = typer.Typer(
|
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def get_client(api_key: str | None = None):
|
|
18
|
+
def get_client(api_key: str | None = None, output_tz: str | None = None):
|
|
19
19
|
"""Lazy import + construct client."""
|
|
20
20
|
from entsoe.client import ENTSOEClient
|
|
21
21
|
|
|
@@ -26,7 +26,8 @@ def get_client(api_key: str | None = None):
|
|
|
26
26
|
err=True,
|
|
27
27
|
)
|
|
28
28
|
raise typer.Exit(1)
|
|
29
|
-
|
|
29
|
+
configured_tz = get_config("output-tz") if output_tz is None else output_tz
|
|
30
|
+
return ENTSOEClient(api_key=resolved, output_tz="UTC" if configured_tz is None else configured_tz)
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
# -- Register sub-commands ---------------------------------------------------
|
entsoe/cli/balancing.py
CHANGED
|
@@ -21,12 +21,13 @@ def prices(
|
|
|
21
21
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
22
22
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
23
23
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
24
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
24
25
|
):
|
|
25
26
|
"""Query imbalance prices."""
|
|
26
27
|
from entsoe.cli.app import get_client
|
|
27
28
|
|
|
28
29
|
s, e = resolve_dates(start, end, period)
|
|
29
|
-
client = get_client(api_key)
|
|
30
|
+
client = get_client(api_key, tz)
|
|
30
31
|
countries = resolve_many_required(country)
|
|
31
32
|
df = client.balancing.imbalance_prices(s, e, country=countries)
|
|
32
33
|
output(df, format, title="Imbalance Prices", output_path=output_path)
|
|
@@ -41,12 +42,13 @@ def volumes(
|
|
|
41
42
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
42
43
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
43
44
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
45
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
44
46
|
):
|
|
45
47
|
"""Query imbalance volumes."""
|
|
46
48
|
from entsoe.cli.app import get_client
|
|
47
49
|
|
|
48
50
|
s, e = resolve_dates(start, end, period)
|
|
49
|
-
client = get_client(api_key)
|
|
51
|
+
client = get_client(api_key, tz)
|
|
50
52
|
countries = resolve_many_required(country)
|
|
51
53
|
df = client.balancing.imbalance_volumes(s, e, country=countries)
|
|
52
54
|
output(df, format, title="Imbalance Volumes", output_path=output_path)
|
entsoe/cli/config_cmd.py
CHANGED
|
@@ -9,12 +9,16 @@ config_app = typer.Typer(no_args_is_help=True)
|
|
|
9
9
|
|
|
10
10
|
@config_app.command("set")
|
|
11
11
|
def config_set(
|
|
12
|
-
key: str = typer.Argument(..., help="Configuration key (
|
|
12
|
+
key: str = typer.Argument(..., help="Configuration key (api-key or output-tz)"),
|
|
13
13
|
value: str = typer.Argument(..., help="Configuration value"),
|
|
14
14
|
):
|
|
15
15
|
"""Set a configuration value."""
|
|
16
16
|
from entsoe.cli.config import set_config
|
|
17
17
|
|
|
18
|
+
if key == "output-tz":
|
|
19
|
+
from entsoe.namespaces._base import validate_output_tz
|
|
20
|
+
validate_output_tz(value)
|
|
21
|
+
|
|
18
22
|
set_config(key, value)
|
|
19
23
|
typer.echo(f"Set {key} = {'***' if 'key' in key.lower() else value}")
|
|
20
24
|
|
entsoe/cli/exec_cmd.py
CHANGED
|
@@ -62,6 +62,7 @@ def exec_prices(
|
|
|
62
62
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
63
63
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
64
64
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
65
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
65
66
|
):
|
|
66
67
|
"""Fetch price data and evaluate a Python expression.
|
|
67
68
|
|
|
@@ -73,7 +74,7 @@ def exec_prices(
|
|
|
73
74
|
|
|
74
75
|
method_name = _resolve_method(method, _PRICES_METHODS)
|
|
75
76
|
s, e = resolve_dates(start, end, period)
|
|
76
|
-
client = get_client(api_key)
|
|
77
|
+
client = get_client(api_key, tz)
|
|
77
78
|
fn = getattr(client.prices, method_name)
|
|
78
79
|
df = fn(s, e, country=resolve_many_required(country))
|
|
79
80
|
_eval_and_render(df, expr, format, output_path)
|
|
@@ -96,6 +97,7 @@ def exec_load(
|
|
|
96
97
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
97
98
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
98
99
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
100
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
99
101
|
):
|
|
100
102
|
"""Fetch load data and evaluate a Python expression.
|
|
101
103
|
|
|
@@ -107,7 +109,7 @@ def exec_load(
|
|
|
107
109
|
|
|
108
110
|
method_name = _resolve_method(method, _LOAD_METHODS)
|
|
109
111
|
s, e = resolve_dates(start, end, period)
|
|
110
|
-
client = get_client(api_key)
|
|
112
|
+
client = get_client(api_key, tz)
|
|
111
113
|
fn = getattr(client.load, method_name)
|
|
112
114
|
df = fn(s, e, country=resolve_many_required(country))
|
|
113
115
|
_eval_and_render(df, expr, format, output_path)
|
|
@@ -136,6 +138,7 @@ def exec_generation(
|
|
|
136
138
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
137
139
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
138
140
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
141
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
139
142
|
):
|
|
140
143
|
"""Fetch generation data and evaluate a Python expression.
|
|
141
144
|
|
|
@@ -147,7 +150,7 @@ def exec_generation(
|
|
|
147
150
|
|
|
148
151
|
method_name = _resolve_method(method, _GENERATION_METHODS)
|
|
149
152
|
s, e = resolve_dates(start, end, period)
|
|
150
|
-
client = get_client(api_key)
|
|
153
|
+
client = get_client(api_key, tz)
|
|
151
154
|
fn = getattr(client.generation, method_name)
|
|
152
155
|
df = fn(s, e, country=resolve_many_required(country), psr_type=resolve_many(psr))
|
|
153
156
|
_eval_and_render(df, expr, format, output_path)
|
|
@@ -176,6 +179,7 @@ def exec_transmission(
|
|
|
176
179
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
177
180
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
178
181
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
182
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
179
183
|
):
|
|
180
184
|
"""Fetch transmission data and evaluate a Python expression.
|
|
181
185
|
|
|
@@ -200,7 +204,7 @@ def exec_transmission(
|
|
|
200
204
|
typer.echo("Error: Provide --from/--to or --border.", err=True)
|
|
201
205
|
raise typer.Exit(1)
|
|
202
206
|
|
|
203
|
-
client = get_client(api_key)
|
|
207
|
+
client = get_client(api_key, tz)
|
|
204
208
|
fn = getattr(client.transmission, method_name)
|
|
205
209
|
df = fn(s, e, **kwargs)
|
|
206
210
|
_eval_and_render(df, expr, format, output_path)
|
|
@@ -223,6 +227,7 @@ def exec_balancing(
|
|
|
223
227
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
224
228
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
225
229
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
230
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
226
231
|
):
|
|
227
232
|
"""Fetch balancing data and evaluate a Python expression.
|
|
228
233
|
|
|
@@ -234,7 +239,7 @@ def exec_balancing(
|
|
|
234
239
|
|
|
235
240
|
method_name = _resolve_method(method, _BALANCING_METHODS)
|
|
236
241
|
s, e = resolve_dates(start, end, period)
|
|
237
|
-
client = get_client(api_key)
|
|
242
|
+
client = get_client(api_key, tz)
|
|
238
243
|
fn = getattr(client.balancing, method_name)
|
|
239
244
|
df = fn(s, e, country=resolve_many_required(country))
|
|
240
245
|
_eval_and_render(df, expr, format, output_path)
|
entsoe/cli/generation.py
CHANGED
|
@@ -22,12 +22,13 @@ def actual(
|
|
|
22
22
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
23
23
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
24
24
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
25
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
25
26
|
):
|
|
26
27
|
"""Query actual generation output per type."""
|
|
27
28
|
from entsoe.cli.app import get_client
|
|
28
29
|
|
|
29
30
|
s, e = resolve_dates(start, end, period)
|
|
30
|
-
client = get_client(api_key)
|
|
31
|
+
client = get_client(api_key, tz)
|
|
31
32
|
df = client.generation.actual(s, e, country=resolve_many_required(country), psr_type=resolve_many(psr))
|
|
32
33
|
output(df, format, title="Actual Generation", output_path=output_path)
|
|
33
34
|
|
|
@@ -42,12 +43,13 @@ def forecast(
|
|
|
42
43
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
43
44
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
44
45
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
46
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
45
47
|
):
|
|
46
48
|
"""Query day-ahead generation forecast (wind/solar)."""
|
|
47
49
|
from entsoe.cli.app import get_client
|
|
48
50
|
|
|
49
51
|
s, e = resolve_dates(start, end, period)
|
|
50
|
-
client = get_client(api_key)
|
|
52
|
+
client = get_client(api_key, tz)
|
|
51
53
|
df = client.generation.forecast(s, e, country=resolve_many_required(country), psr_type=resolve_many(psr))
|
|
52
54
|
output(df, format, title="Generation Forecast", output_path=output_path)
|
|
53
55
|
|
|
@@ -62,12 +64,13 @@ def capacity(
|
|
|
62
64
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
63
65
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
64
66
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
67
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
65
68
|
):
|
|
66
69
|
"""Query installed generation capacity per type."""
|
|
67
70
|
from entsoe.cli.app import get_client
|
|
68
71
|
|
|
69
72
|
s, e = resolve_dates(start, end, period)
|
|
70
|
-
client = get_client(api_key)
|
|
73
|
+
client = get_client(api_key, tz)
|
|
71
74
|
df = client.generation.installed_capacity(s, e, country=resolve_many_required(country), psr_type=resolve_many(psr))
|
|
72
75
|
output(df, format, title="Installed Capacity", output_path=output_path)
|
|
73
76
|
|
|
@@ -82,12 +85,13 @@ def per_plant(
|
|
|
82
85
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
83
86
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
84
87
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
88
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
85
89
|
):
|
|
86
90
|
"""Query actual generation per production unit."""
|
|
87
91
|
from entsoe.cli.app import get_client
|
|
88
92
|
|
|
89
93
|
s, e = resolve_dates(start, end, period)
|
|
90
|
-
client = get_client(api_key)
|
|
94
|
+
client = get_client(api_key, tz)
|
|
91
95
|
df = client.generation.per_plant(s, e, country=resolve_many_required(country), psr_type=resolve_many(psr))
|
|
92
96
|
output(df, format, title="Generation Per Plant", output_path=output_path)
|
|
93
97
|
|
entsoe/cli/load.py
CHANGED
|
@@ -21,12 +21,13 @@ def actual(
|
|
|
21
21
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
22
22
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
23
23
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
24
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
24
25
|
):
|
|
25
26
|
"""Query actual total system load."""
|
|
26
27
|
from entsoe.cli.app import get_client
|
|
27
28
|
|
|
28
29
|
s, e = resolve_dates(start, end, period)
|
|
29
|
-
client = get_client(api_key)
|
|
30
|
+
client = get_client(api_key, tz)
|
|
30
31
|
countries = resolve_many_required(country)
|
|
31
32
|
df = client.load.actual(s, e, country=countries)
|
|
32
33
|
output(df, format, title="Actual Load", output_path=output_path)
|
|
@@ -41,12 +42,13 @@ def forecast(
|
|
|
41
42
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
42
43
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
43
44
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
45
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
44
46
|
):
|
|
45
47
|
"""Query day-ahead load forecast."""
|
|
46
48
|
from entsoe.cli.app import get_client
|
|
47
49
|
|
|
48
50
|
s, e = resolve_dates(start, end, period)
|
|
49
|
-
client = get_client(api_key)
|
|
51
|
+
client = get_client(api_key, tz)
|
|
50
52
|
countries = resolve_many_required(country)
|
|
51
53
|
df = client.load.forecast(s, e, country=countries)
|
|
52
54
|
output(df, format, title="Load Forecast", output_path=output_path)
|
entsoe/cli/prices.py
CHANGED
|
@@ -22,12 +22,13 @@ def day_ahead(
|
|
|
22
22
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
23
23
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
24
24
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
25
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
25
26
|
):
|
|
26
27
|
"""Query day-ahead electricity prices."""
|
|
27
28
|
from entsoe.cli.app import get_client
|
|
28
29
|
|
|
29
30
|
s, e = resolve_dates(start, end, period)
|
|
30
|
-
client = get_client(api_key)
|
|
31
|
+
client = get_client(api_key, tz)
|
|
31
32
|
countries = resolve_many_required(country)
|
|
32
33
|
df = client.prices.day_ahead(s, e, country=countries, sequence=sequence)
|
|
33
34
|
output(df, format, title="Day-Ahead Prices", output_path=output_path)
|
entsoe/cli/transmission.py
CHANGED
|
@@ -43,13 +43,14 @@ def flows(
|
|
|
43
43
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
44
44
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
45
45
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
46
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
46
47
|
):
|
|
47
48
|
"""Query physical cross-border flows."""
|
|
48
49
|
from entsoe.cli.app import get_client
|
|
49
50
|
|
|
50
51
|
s, e = resolve_dates(start, end, period)
|
|
51
52
|
kwargs = _resolve_borders_or_pairs(country_from, country_to, border)
|
|
52
|
-
client = get_client(api_key)
|
|
53
|
+
client = get_client(api_key, tz)
|
|
53
54
|
df = client.transmission.crossborder_flows(s, e, **kwargs)
|
|
54
55
|
output(df, format, title="Cross-Border Flows", output_path=output_path)
|
|
55
56
|
|
|
@@ -65,13 +66,14 @@ def exchanges(
|
|
|
65
66
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
66
67
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
67
68
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
69
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
68
70
|
):
|
|
69
71
|
"""Query scheduled commercial exchanges (day-ahead)."""
|
|
70
72
|
from entsoe.cli.app import get_client
|
|
71
73
|
|
|
72
74
|
s, e = resolve_dates(start, end, period)
|
|
73
75
|
kwargs = _resolve_borders_or_pairs(country_from, country_to, border)
|
|
74
|
-
client = get_client(api_key)
|
|
76
|
+
client = get_client(api_key, tz)
|
|
75
77
|
df = client.transmission.scheduled_exchanges(s, e, **kwargs)
|
|
76
78
|
output(df, format, title="Scheduled Exchanges", output_path=output_path)
|
|
77
79
|
|
|
@@ -87,13 +89,14 @@ def capacity(
|
|
|
87
89
|
format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
|
|
88
90
|
output_path: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
|
|
89
91
|
api_key: Optional[str] = typer.Option(None, "--api-key", help="ENTSO-E API key"),
|
|
92
|
+
tz: Optional[str] = typer.Option(None, "--tz", help="Output timezone: UTC, local, or IANA name"),
|
|
90
93
|
):
|
|
91
94
|
"""Query day-ahead net transfer capacity."""
|
|
92
95
|
from entsoe.cli.app import get_client
|
|
93
96
|
|
|
94
97
|
s, e = resolve_dates(start, end, period)
|
|
95
98
|
kwargs = _resolve_borders_or_pairs(country_from, country_to, border)
|
|
96
|
-
client = get_client(api_key)
|
|
99
|
+
client = get_client(api_key, tz)
|
|
97
100
|
df = client.transmission.net_transfer_capacity(s, e, **kwargs)
|
|
98
101
|
output(df, format, title="Net Transfer Capacity", output_path=output_path)
|
|
99
102
|
|
entsoe/client.py
CHANGED
|
@@ -7,6 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
from ._http import HttpClient
|
|
9
9
|
from .cache import CacheConfig, CacheStore
|
|
10
|
+
from .namespaces._base import validate_output_tz
|
|
10
11
|
from .namespaces import (
|
|
11
12
|
BalancingNamespace,
|
|
12
13
|
GenerationNamespace,
|
|
@@ -38,6 +39,8 @@ class ENTSOEClient:
|
|
|
38
39
|
|
|
39
40
|
# Override default timezone
|
|
40
41
|
client = ENTSOEClient(tz="UTC")
|
|
42
|
+
client = ENTSOEClient(output_tz="local") # results use each zone's local time
|
|
43
|
+
client = ENTSOEClient(output_tz="Europe/Madrid") # fixed output zone
|
|
41
44
|
|
|
42
45
|
# Disable cache
|
|
43
46
|
client = ENTSOEClient(cache=False)
|
|
@@ -60,6 +63,7 @@ class ENTSOEClient:
|
|
|
60
63
|
cache: bool = True,
|
|
61
64
|
cache_dir: str | Path | None = None,
|
|
62
65
|
cache_recent_ttl: int = 48,
|
|
66
|
+
output_tz: str = "UTC",
|
|
63
67
|
) -> None:
|
|
64
68
|
"""Initialize the client.
|
|
65
69
|
|
|
@@ -71,10 +75,13 @@ class ENTSOEClient:
|
|
|
71
75
|
cache: Whether to enable local parquet caching. Defaults to True.
|
|
72
76
|
cache_dir: Custom cache directory. Defaults to ``~/.cache/entsoe/``.
|
|
73
77
|
cache_recent_ttl: Hours before data is considered final (default 48).
|
|
78
|
+
output_tz: Result timezone: ``UTC`` (default), ``local`` for the requested area, or an IANA name.
|
|
74
79
|
|
|
75
80
|
Raises:
|
|
76
81
|
ValueError: If no API key is found.
|
|
82
|
+
InvalidParameterError: If output_tz is not UTC, local, or a valid IANA timezone.
|
|
77
83
|
"""
|
|
84
|
+
output_tz = validate_output_tz(output_tz)
|
|
78
85
|
resolved_key = api_key or os.environ.get("ENTSOE_API_KEY")
|
|
79
86
|
if not resolved_key:
|
|
80
87
|
raise ValueError(
|
|
@@ -94,11 +101,11 @@ class ENTSOEClient:
|
|
|
94
101
|
|
|
95
102
|
cache_store = CacheStore(cache_config) if cache else None
|
|
96
103
|
|
|
97
|
-
self.load = LoadNamespace(http, tz=tz, cache=cache_store)
|
|
98
|
-
self.prices = PricesNamespace(http, tz=tz, cache=cache_store)
|
|
99
|
-
self.generation = GenerationNamespace(http, tz=tz, cache=cache_store)
|
|
100
|
-
self.transmission = TransmissionNamespace(http, tz=tz, cache=cache_store)
|
|
101
|
-
self.balancing = BalancingNamespace(http, tz=tz, cache=cache_store)
|
|
104
|
+
self.load = LoadNamespace(http, tz=tz, output_tz=output_tz, cache=cache_store)
|
|
105
|
+
self.prices = PricesNamespace(http, tz=tz, output_tz=output_tz, cache=cache_store)
|
|
106
|
+
self.generation = GenerationNamespace(http, tz=tz, output_tz=output_tz, cache=cache_store)
|
|
107
|
+
self.transmission = TransmissionNamespace(http, tz=tz, output_tz=output_tz, cache=cache_store)
|
|
108
|
+
self.balancing = BalancingNamespace(http, tz=tz, output_tz=output_tz, cache=cache_store)
|
|
102
109
|
|
|
103
110
|
# Expose for CLI cache commands
|
|
104
111
|
self._cache = cache_store
|
entsoe/namespaces/_base.py
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
+
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
6
7
|
from typing import TYPE_CHECKING, Callable, Optional, Union
|
|
7
8
|
|
|
8
9
|
import pandas as pd
|
|
9
10
|
|
|
10
11
|
from ..exceptions import InvalidParameterError
|
|
11
|
-
from .._mappings import country_name, lookup_area
|
|
12
|
+
from .._mappings import AREA_TZ, EIC_TO_ISO, country_name, lookup_area
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
import xml.etree.ElementTree as ET
|
|
@@ -28,6 +29,52 @@ OneOrMany = Union[str, list[str]]
|
|
|
28
29
|
TimeSeriesFilter = Optional[Callable[["ET.Element"], bool]]
|
|
29
30
|
|
|
30
31
|
|
|
32
|
+
def validate_output_tz(value: str) -> str:
|
|
33
|
+
"""Validate an output timezone without changing its spelling."""
|
|
34
|
+
if value == "local":
|
|
35
|
+
return value
|
|
36
|
+
if not isinstance(value, str):
|
|
37
|
+
raise InvalidParameterError("output_tz must be 'UTC', 'local', or an IANA timezone.")
|
|
38
|
+
try:
|
|
39
|
+
ZoneInfo(value)
|
|
40
|
+
except (ZoneInfoNotFoundError, ValueError):
|
|
41
|
+
raise InvalidParameterError(
|
|
42
|
+
f"Invalid output_tz {value!r}; use 'UTC', 'local', or an IANA timezone."
|
|
43
|
+
) from None
|
|
44
|
+
return value
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _to_output_tz(df: pd.DataFrame, zones: OneOrMany, output_tz: str) -> pd.DataFrame:
|
|
48
|
+
"""Convert a UTC result at the namespace boundary without mutating cached data.
|
|
49
|
+
|
|
50
|
+
For cross-border requests, callers pass the first in_domain zone; local time then follows that zone.
|
|
51
|
+
"""
|
|
52
|
+
target = validate_output_tz(output_tz)
|
|
53
|
+
if target == "UTC":
|
|
54
|
+
return df
|
|
55
|
+
if target == "local":
|
|
56
|
+
requested = [zones] if isinstance(zones, str) else zones
|
|
57
|
+
timezones = [AREA_TZ[EIC_TO_ISO[lookup_area(zone)]] for zone in requested]
|
|
58
|
+
instants = df["timestamp"].drop_duplicates() if "timestamp" in df else []
|
|
59
|
+
if not len(instants):
|
|
60
|
+
instants = [pd.Timestamp("2024-01-01", tz="UTC"), pd.Timestamp("2024-07-01", tz="UTC")]
|
|
61
|
+
signatures = {
|
|
62
|
+
zone: tuple(instant.to_pydatetime().astimezone(ZoneInfo(zone)).utcoffset() for instant in instants)
|
|
63
|
+
for zone in set(timezones)
|
|
64
|
+
}
|
|
65
|
+
if len(set(signatures.values())) != 1:
|
|
66
|
+
raise InvalidParameterError(
|
|
67
|
+
f"Local output spans different timezones: {', '.join(sorted(set(timezones)))}. "
|
|
68
|
+
"Use 'UTC' or a fixed IANA timezone."
|
|
69
|
+
)
|
|
70
|
+
target = timezones[0]
|
|
71
|
+
if df.empty or "timestamp" not in df:
|
|
72
|
+
return df.copy()
|
|
73
|
+
result = df.copy()
|
|
74
|
+
result["timestamp"] = result["timestamp"].dt.tz_convert(target)
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
|
|
31
78
|
class BaseNamespace:
|
|
32
79
|
"""Base class for domain namespaces.
|
|
33
80
|
|
|
@@ -40,12 +87,17 @@ class BaseNamespace:
|
|
|
40
87
|
http: HttpClient,
|
|
41
88
|
*,
|
|
42
89
|
tz: str = "Europe/Brussels",
|
|
90
|
+
output_tz: str = "UTC",
|
|
43
91
|
cache: CacheStore | None = None,
|
|
44
92
|
) -> None:
|
|
45
93
|
self._http = http
|
|
46
94
|
self._tz = tz
|
|
95
|
+
self._output_tz = validate_output_tz(output_tz)
|
|
47
96
|
self._cache = cache
|
|
48
97
|
|
|
98
|
+
def _output(self, df: pd.DataFrame, zones: OneOrMany, output_tz: str | None) -> pd.DataFrame:
|
|
99
|
+
return _to_output_tz(df, zones, self._output_tz if output_tz is None else output_tz)
|
|
100
|
+
|
|
49
101
|
# ------------------------------------------------------------------
|
|
50
102
|
# Timestamp coercion
|
|
51
103
|
# ------------------------------------------------------------------
|
entsoe/namespaces/balancing.py
CHANGED
|
@@ -20,6 +20,8 @@ class BalancingNamespace(BaseNamespace):
|
|
|
20
20
|
start: Timestamp,
|
|
21
21
|
end: Timestamp,
|
|
22
22
|
country: OneOrMany,
|
|
23
|
+
*,
|
|
24
|
+
output_tz: str | None = None,
|
|
23
25
|
) -> pd.DataFrame:
|
|
24
26
|
"""Query imbalance prices.
|
|
25
27
|
|
|
@@ -29,6 +31,8 @@ class BalancingNamespace(BaseNamespace):
|
|
|
29
31
|
country: Country code or list of codes (e.g., "FR" or ["FR", "NL"]).
|
|
30
32
|
When a list is passed, results include a ``country`` column.
|
|
31
33
|
|
|
34
|
+
output_tz: Override the client result timezone (UTC, local, or IANA name).
|
|
35
|
+
|
|
32
36
|
Returns:
|
|
33
37
|
DataFrame with columns: timestamp, value (EUR/MWh).
|
|
34
38
|
"""
|
|
@@ -39,15 +43,17 @@ class BalancingNamespace(BaseNamespace):
|
|
|
39
43
|
"controlArea_Domain": self._area(code),
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
return self._query_multi_cached(
|
|
46
|
+
return self._output(self._query_multi_cached(
|
|
43
47
|
"balancing", "imbalance_prices", _params, country, start, end,
|
|
44
|
-
)
|
|
48
|
+
), country, output_tz)
|
|
45
49
|
|
|
46
50
|
def imbalance_volumes(
|
|
47
51
|
self,
|
|
48
52
|
start: Timestamp,
|
|
49
53
|
end: Timestamp,
|
|
50
54
|
country: OneOrMany,
|
|
55
|
+
*,
|
|
56
|
+
output_tz: str | None = None,
|
|
51
57
|
) -> pd.DataFrame:
|
|
52
58
|
"""Query imbalance volumes.
|
|
53
59
|
|
|
@@ -57,6 +63,8 @@ class BalancingNamespace(BaseNamespace):
|
|
|
57
63
|
country: Country code or list of codes (e.g., "FR" or ["FR", "NL"]).
|
|
58
64
|
When a list is passed, results include a ``country`` column.
|
|
59
65
|
|
|
66
|
+
output_tz: Override the client result timezone (UTC, local, or IANA name).
|
|
67
|
+
|
|
60
68
|
Returns:
|
|
61
69
|
DataFrame with columns: timestamp, value (MW).
|
|
62
70
|
"""
|
|
@@ -67,6 +75,6 @@ class BalancingNamespace(BaseNamespace):
|
|
|
67
75
|
"controlArea_Domain": self._area(code),
|
|
68
76
|
}
|
|
69
77
|
|
|
70
|
-
return self._query_multi_cached(
|
|
78
|
+
return self._output(self._query_multi_cached(
|
|
71
79
|
"balancing", "imbalance_volumes", _params, country, start, end,
|
|
72
|
-
)
|
|
80
|
+
), country, output_tz)
|
entsoe/namespaces/generation.py
CHANGED
|
@@ -28,6 +28,7 @@ class GenerationNamespace(BaseNamespace):
|
|
|
28
28
|
psr_type: OneOrMany | None,
|
|
29
29
|
*,
|
|
30
30
|
cache_topic: str,
|
|
31
|
+
output_tz: str | None,
|
|
31
32
|
) -> pd.DataFrame:
|
|
32
33
|
"""Shared logic for generation queries with country + psr_type multi-support."""
|
|
33
34
|
|
|
@@ -67,7 +68,7 @@ class GenerationNamespace(BaseNamespace):
|
|
|
67
68
|
from .._mappings import country_name
|
|
68
69
|
sub_df["country"] = country_name(code)
|
|
69
70
|
frames.append(sub_df)
|
|
70
|
-
return pd.concat(frames, ignore_index=True)
|
|
71
|
+
return self._output(pd.concat(frames, ignore_index=True), country, output_tz)
|
|
71
72
|
|
|
72
73
|
# Standard case: single or no psr_type filter
|
|
73
74
|
df = self._query_multi_cached(
|
|
@@ -81,7 +82,7 @@ class GenerationNamespace(BaseNamespace):
|
|
|
81
82
|
resolved_name = psr_name(psr_type)
|
|
82
83
|
df = df[df[pivot_col] == resolved_name].reset_index(drop=True)
|
|
83
84
|
|
|
84
|
-
return df
|
|
85
|
+
return self._output(df, country, output_tz)
|
|
85
86
|
|
|
86
87
|
def actual(
|
|
87
88
|
self,
|
|
@@ -89,6 +90,8 @@ class GenerationNamespace(BaseNamespace):
|
|
|
89
90
|
end: Timestamp,
|
|
90
91
|
country: OneOrMany,
|
|
91
92
|
psr_type: OneOrMany | None = None,
|
|
93
|
+
*,
|
|
94
|
+
output_tz: str | None = None,
|
|
92
95
|
) -> pd.DataFrame:
|
|
93
96
|
"""Query actual generation output per type.
|
|
94
97
|
|
|
@@ -101,11 +104,13 @@ class GenerationNamespace(BaseNamespace):
|
|
|
101
104
|
(e.g., "solar", ["solar", "wind_onshore"], or "B16").
|
|
102
105
|
If None, returns all types.
|
|
103
106
|
|
|
107
|
+
output_tz: Override the client result timezone (UTC, local, or IANA name).
|
|
108
|
+
|
|
104
109
|
Returns:
|
|
105
110
|
DataFrame with columns: timestamp, value (MW), psr_type.
|
|
106
111
|
"""
|
|
107
112
|
return self._gen_query("A75", "A16", start, end, country, psr_type,
|
|
108
|
-
cache_topic="actual")
|
|
113
|
+
cache_topic="actual", output_tz=output_tz)
|
|
109
114
|
|
|
110
115
|
def forecast(
|
|
111
116
|
self,
|
|
@@ -113,6 +118,8 @@ class GenerationNamespace(BaseNamespace):
|
|
|
113
118
|
end: Timestamp,
|
|
114
119
|
country: OneOrMany,
|
|
115
120
|
psr_type: OneOrMany | None = None,
|
|
121
|
+
*,
|
|
122
|
+
output_tz: str | None = None,
|
|
116
123
|
) -> pd.DataFrame:
|
|
117
124
|
"""Query day-ahead generation forecast (wind and solar).
|
|
118
125
|
|
|
@@ -123,11 +130,13 @@ class GenerationNamespace(BaseNamespace):
|
|
|
123
130
|
When a list is passed, results include a ``country`` column.
|
|
124
131
|
psr_type: PSR type code/name, list of codes/names, or None.
|
|
125
132
|
|
|
133
|
+
output_tz: Override the client result timezone (UTC, local, or IANA name).
|
|
134
|
+
|
|
126
135
|
Returns:
|
|
127
136
|
DataFrame with columns: timestamp, value (MW), psr_type.
|
|
128
137
|
"""
|
|
129
138
|
return self._gen_query("A69", "A01", start, end, country, psr_type,
|
|
130
|
-
cache_topic="forecast")
|
|
139
|
+
cache_topic="forecast", output_tz=output_tz)
|
|
131
140
|
|
|
132
141
|
def installed_capacity(
|
|
133
142
|
self,
|
|
@@ -135,6 +144,8 @@ class GenerationNamespace(BaseNamespace):
|
|
|
135
144
|
end: Timestamp,
|
|
136
145
|
country: OneOrMany,
|
|
137
146
|
psr_type: OneOrMany | None = None,
|
|
147
|
+
*,
|
|
148
|
+
output_tz: str | None = None,
|
|
138
149
|
) -> pd.DataFrame:
|
|
139
150
|
"""Query installed generation capacity per type.
|
|
140
151
|
|
|
@@ -145,11 +156,13 @@ class GenerationNamespace(BaseNamespace):
|
|
|
145
156
|
When a list is passed, results include a ``country`` column.
|
|
146
157
|
psr_type: PSR type code/name, list of codes/names, or None.
|
|
147
158
|
|
|
159
|
+
output_tz: Override the client result timezone (UTC, local, or IANA name).
|
|
160
|
+
|
|
148
161
|
Returns:
|
|
149
162
|
DataFrame with columns: timestamp, value (MW), psr_type.
|
|
150
163
|
"""
|
|
151
164
|
return self._gen_query("A68", "A33", start, end, country, psr_type,
|
|
152
|
-
cache_topic="capacity")
|
|
165
|
+
cache_topic="capacity", output_tz=output_tz)
|
|
153
166
|
|
|
154
167
|
def per_plant(
|
|
155
168
|
self,
|
|
@@ -157,6 +170,8 @@ class GenerationNamespace(BaseNamespace):
|
|
|
157
170
|
end: Timestamp,
|
|
158
171
|
country: OneOrMany,
|
|
159
172
|
psr_type: OneOrMany | None = None,
|
|
173
|
+
*,
|
|
174
|
+
output_tz: str | None = None,
|
|
160
175
|
) -> pd.DataFrame:
|
|
161
176
|
"""Query actual generation per production unit.
|
|
162
177
|
|
|
@@ -167,9 +182,11 @@ class GenerationNamespace(BaseNamespace):
|
|
|
167
182
|
When a list is passed, results include a ``country`` column.
|
|
168
183
|
psr_type: PSR type code/name, list of codes/names, or None.
|
|
169
184
|
|
|
185
|
+
output_tz: Override the client result timezone (UTC, local, or IANA name).
|
|
186
|
+
|
|
170
187
|
Returns:
|
|
171
188
|
DataFrame with columns: timestamp, value (MW), psr_type,
|
|
172
189
|
and additional unit identifiers.
|
|
173
190
|
"""
|
|
174
191
|
return self._gen_query("A73", "A16", start, end, country, psr_type,
|
|
175
|
-
cache_topic="per_plant")
|
|
192
|
+
cache_topic="per_plant", output_tz=output_tz)
|
entsoe/namespaces/load.py
CHANGED
|
@@ -20,6 +20,8 @@ class LoadNamespace(BaseNamespace):
|
|
|
20
20
|
start: Timestamp,
|
|
21
21
|
end: Timestamp,
|
|
22
22
|
country: OneOrMany,
|
|
23
|
+
*,
|
|
24
|
+
output_tz: str | None = None,
|
|
23
25
|
) -> pd.DataFrame:
|
|
24
26
|
"""Query actual total system load.
|
|
25
27
|
|
|
@@ -29,6 +31,8 @@ class LoadNamespace(BaseNamespace):
|
|
|
29
31
|
country: Country code or list of codes (e.g., "FR" or ["FR", "ES"]).
|
|
30
32
|
When a list is passed, results include a ``country`` column.
|
|
31
33
|
|
|
34
|
+
output_tz: Override the client result timezone (UTC, local, or IANA name).
|
|
35
|
+
|
|
32
36
|
Returns:
|
|
33
37
|
DataFrame with columns: timestamp, value (MW).
|
|
34
38
|
"""
|
|
@@ -40,15 +44,17 @@ class LoadNamespace(BaseNamespace):
|
|
|
40
44
|
"outBiddingZone_Domain": self._area(code),
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
return self._query_multi_cached(
|
|
47
|
+
return self._output(self._query_multi_cached(
|
|
44
48
|
"load", "actual", _params, country, start, end,
|
|
45
|
-
)
|
|
49
|
+
), country, output_tz)
|
|
46
50
|
|
|
47
51
|
def forecast(
|
|
48
52
|
self,
|
|
49
53
|
start: Timestamp,
|
|
50
54
|
end: Timestamp,
|
|
51
55
|
country: OneOrMany,
|
|
56
|
+
*,
|
|
57
|
+
output_tz: str | None = None,
|
|
52
58
|
) -> pd.DataFrame:
|
|
53
59
|
"""Query day-ahead load forecast.
|
|
54
60
|
|
|
@@ -58,6 +64,8 @@ class LoadNamespace(BaseNamespace):
|
|
|
58
64
|
country: Country code or list of codes (e.g., "FR" or ["FR", "ES"]).
|
|
59
65
|
When a list is passed, results include a ``country`` column.
|
|
60
66
|
|
|
67
|
+
output_tz: Override the client result timezone (UTC, local, or IANA name).
|
|
68
|
+
|
|
61
69
|
Returns:
|
|
62
70
|
DataFrame with columns: timestamp, value (MW).
|
|
63
71
|
"""
|
|
@@ -69,6 +77,6 @@ class LoadNamespace(BaseNamespace):
|
|
|
69
77
|
"outBiddingZone_Domain": self._area(code),
|
|
70
78
|
}
|
|
71
79
|
|
|
72
|
-
return self._query_multi_cached(
|
|
80
|
+
return self._output(self._query_multi_cached(
|
|
73
81
|
"load", "forecast", _params, country, start, end,
|
|
74
|
-
)
|
|
82
|
+
), country, output_tz)
|
entsoe/namespaces/prices.py
CHANGED
|
@@ -22,6 +22,8 @@ class PricesNamespace(BaseNamespace):
|
|
|
22
22
|
end: Timestamp,
|
|
23
23
|
country: OneOrMany,
|
|
24
24
|
sequence: int = 1,
|
|
25
|
+
*,
|
|
26
|
+
output_tz: str | None = None,
|
|
25
27
|
) -> pd.DataFrame:
|
|
26
28
|
"""Query day-ahead electricity prices.
|
|
27
29
|
|
|
@@ -40,6 +42,8 @@ class PricesNamespace(BaseNamespace):
|
|
|
40
42
|
DE_LU and AT. Zones that publish a single auction without a
|
|
41
43
|
sequence count as ``1``.
|
|
42
44
|
|
|
45
|
+
output_tz: Override the client result timezone (UTC, local, or IANA name).
|
|
46
|
+
|
|
43
47
|
Returns:
|
|
44
48
|
DataFrame with columns: timestamp, value (EUR/MWh).
|
|
45
49
|
Multi-country queries add a ``country`` column.
|
|
@@ -58,8 +62,8 @@ class PricesNamespace(BaseNamespace):
|
|
|
58
62
|
return False
|
|
59
63
|
return (timeseries_sequence(ts) or 1) == sequence
|
|
60
64
|
|
|
61
|
-
return self._query_multi_cached(
|
|
65
|
+
return self._output(self._query_multi_cached(
|
|
62
66
|
"prices", "day_ahead", _params, country, start, end,
|
|
63
67
|
ts_filter=_keep,
|
|
64
68
|
partition_fn=lambda code: code if sequence == 1 else f"{code}.seq{sequence}",
|
|
65
|
-
)
|
|
69
|
+
), country, output_tz)
|
|
@@ -32,6 +32,7 @@ class TransmissionNamespace(BaseNamespace):
|
|
|
32
32
|
extra_params: dict | None = None,
|
|
33
33
|
*,
|
|
34
34
|
cache_topic: str,
|
|
35
|
+
output_tz: str | None,
|
|
35
36
|
) -> pd.DataFrame:
|
|
36
37
|
"""Shared logic for transmission queries with multi-value support.
|
|
37
38
|
|
|
@@ -75,9 +76,9 @@ class TransmissionNamespace(BaseNamespace):
|
|
|
75
76
|
"out_Domain": self._area(country_from),
|
|
76
77
|
**extra,
|
|
77
78
|
}
|
|
78
|
-
return self._query_single_border(
|
|
79
|
+
return self._output(self._query_single_border(
|
|
79
80
|
cache_topic, country_from, country_to, params, start, end,
|
|
80
|
-
)
|
|
81
|
+
), country_to, output_tz)
|
|
81
82
|
|
|
82
83
|
froms = country_from if is_multi_from else [country_from]
|
|
83
84
|
tos = country_to if is_multi_to else [country_to]
|
|
@@ -92,9 +93,9 @@ class TransmissionNamespace(BaseNamespace):
|
|
|
92
93
|
"out_Domain": self._area(cf),
|
|
93
94
|
**extra,
|
|
94
95
|
}
|
|
95
|
-
return self._query_single_border(
|
|
96
|
+
return self._output(self._query_single_border(
|
|
96
97
|
cache_topic, cf, ct, params, start, end,
|
|
97
|
-
)
|
|
98
|
+
), ct, output_tz)
|
|
98
99
|
|
|
99
100
|
frames = []
|
|
100
101
|
for cf, ct in pairs:
|
|
@@ -109,7 +110,7 @@ class TransmissionNamespace(BaseNamespace):
|
|
|
109
110
|
)
|
|
110
111
|
df["border"] = f"{country_name(cf)} → {country_name(ct)}"
|
|
111
112
|
frames.append(df)
|
|
112
|
-
return pd.concat(frames, ignore_index=True)
|
|
113
|
+
return self._output(pd.concat(frames, ignore_index=True), pairs[0][1], output_tz)
|
|
113
114
|
|
|
114
115
|
def _query_single_border(
|
|
115
116
|
self,
|
|
@@ -139,6 +140,8 @@ class TransmissionNamespace(BaseNamespace):
|
|
|
139
140
|
country_from: OneOrMany | None = None,
|
|
140
141
|
country_to: OneOrMany | None = None,
|
|
141
142
|
borders: OneOrMany | None = None,
|
|
143
|
+
*,
|
|
144
|
+
output_tz: str | None = None,
|
|
142
145
|
) -> pd.DataFrame:
|
|
143
146
|
"""Query physical cross-border flows.
|
|
144
147
|
|
|
@@ -153,12 +156,14 @@ class TransmissionNamespace(BaseNamespace):
|
|
|
153
156
|
and comma-separated combinations like ``"ES-*,FR-*"``.
|
|
154
157
|
Cannot be combined with country_from/country_to.
|
|
155
158
|
|
|
159
|
+
output_tz: Override the client result timezone. With local, cross-border output follows the first in_domain (importing) zone.
|
|
160
|
+
|
|
156
161
|
Returns:
|
|
157
162
|
DataFrame with columns: timestamp, value (MW).
|
|
158
163
|
"""
|
|
159
164
|
return self._transmission_query(
|
|
160
165
|
"A11", start, end, country_from, country_to, borders,
|
|
161
|
-
cache_topic="crossborder_flows",
|
|
166
|
+
cache_topic="crossborder_flows", output_tz=output_tz,
|
|
162
167
|
)
|
|
163
168
|
|
|
164
169
|
def scheduled_exchanges(
|
|
@@ -168,6 +173,8 @@ class TransmissionNamespace(BaseNamespace):
|
|
|
168
173
|
country_from: OneOrMany | None = None,
|
|
169
174
|
country_to: OneOrMany | None = None,
|
|
170
175
|
borders: OneOrMany | None = None,
|
|
176
|
+
*,
|
|
177
|
+
output_tz: str | None = None,
|
|
171
178
|
) -> pd.DataFrame:
|
|
172
179
|
"""Query scheduled commercial exchanges (day-ahead).
|
|
173
180
|
|
|
@@ -179,12 +186,14 @@ class TransmissionNamespace(BaseNamespace):
|
|
|
179
186
|
When either is a list, results include a ``border`` column.
|
|
180
187
|
borders: Border spec or list of specs. See :meth:`crossborder_flows`.
|
|
181
188
|
|
|
189
|
+
output_tz: Override the client result timezone. With local, cross-border output follows the first in_domain (importing) zone.
|
|
190
|
+
|
|
182
191
|
Returns:
|
|
183
192
|
DataFrame with columns: timestamp, value (MW).
|
|
184
193
|
"""
|
|
185
194
|
return self._transmission_query(
|
|
186
195
|
"A09", start, end, country_from, country_to, borders,
|
|
187
|
-
cache_topic="scheduled_exchanges",
|
|
196
|
+
cache_topic="scheduled_exchanges", output_tz=output_tz,
|
|
188
197
|
)
|
|
189
198
|
|
|
190
199
|
def net_transfer_capacity(
|
|
@@ -194,6 +203,8 @@ class TransmissionNamespace(BaseNamespace):
|
|
|
194
203
|
country_from: OneOrMany | None = None,
|
|
195
204
|
country_to: OneOrMany | None = None,
|
|
196
205
|
borders: OneOrMany | None = None,
|
|
206
|
+
*,
|
|
207
|
+
output_tz: str | None = None,
|
|
197
208
|
) -> pd.DataFrame:
|
|
198
209
|
"""Query day-ahead net transfer capacity.
|
|
199
210
|
|
|
@@ -205,11 +216,13 @@ class TransmissionNamespace(BaseNamespace):
|
|
|
205
216
|
When either is a list, results include a ``border`` column.
|
|
206
217
|
borders: Border spec or list of specs. See :meth:`crossborder_flows`.
|
|
207
218
|
|
|
219
|
+
output_tz: Override the client result timezone. With local, cross-border output follows the first in_domain (importing) zone.
|
|
220
|
+
|
|
208
221
|
Returns:
|
|
209
222
|
DataFrame with columns: timestamp, value (MW).
|
|
210
223
|
"""
|
|
211
224
|
return self._transmission_query(
|
|
212
225
|
"A61", start, end, country_from, country_to, borders,
|
|
213
226
|
extra_params={"contract_MarketAgreement.Type": "A01"},
|
|
214
|
-
cache_topic="net_transfer_capacity",
|
|
227
|
+
cache_topic="net_transfer_capacity", output_tz=output_tz,
|
|
215
228
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-entsoe
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Python client for the ENTSO-E Transparency Platform API
|
|
5
5
|
Project-URL: Homepage, https://gitlab.com/datons.com/libraries/python-entsoe
|
|
6
6
|
Project-URL: Repository, https://gitlab.com/datons.com/libraries/python-entsoe
|
|
@@ -57,7 +57,7 @@ start = pd.Timestamp("2024-06-01", tz="Europe/Paris")
|
|
|
57
57
|
df = client.load.actual(start, "2024-06-08", country="FR")
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Every method returns a `pandas.DataFrame` with a `timestamp` column
|
|
60
|
+
Every method returns a `pandas.DataFrame` with a timezone-aware `timestamp` column and a `value` column.
|
|
61
61
|
|
|
62
62
|
## Authentication
|
|
63
63
|
|
|
@@ -216,7 +216,11 @@ df = client.load.actual("2024-01-01", pd.Timestamp("2024-01-07", tz="UTC"), coun
|
|
|
216
216
|
start = pd.Timestamp("2024-01-01") # ← no tz, will error
|
|
217
217
|
```
|
|
218
218
|
|
|
219
|
-
|
|
219
|
+
## Timezones
|
|
220
|
+
|
|
221
|
+
The client `tz` setting interprets input date strings (default `Europe/Brussels`); it does not control returned timestamps. The separate `output_tz` setting controls results: `"UTC"` (default, unchanged behavior), `"local"` (the requested area's timezone), or a fixed IANA timezone such as `"Europe/Madrid"`. Every namespace method also accepts `output_tz=` for one call. The CLI accepts `--tz` on data commands, or `entsoe config set output-tz local` as a persistent default; `--tz` takes precedence.
|
|
222
|
+
|
|
223
|
+
For a multi-area request, `"local"` requires matching clock offsets throughout the returned period (for example, FR and ES); otherwise choose `"UTC"` or a fixed IANA zone. Cross-border results use the first importing (`in_domain`) zone. During the October DST fall-back, local wall-clock times repeat; each row retains its offset, but timestamps cease to be unique if the timezone is stripped.
|
|
220
224
|
|
|
221
225
|
## Features
|
|
222
226
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
entsoe/__init__.py,sha256=9qzvMUzoD-CRfX1vTzWIBfpXBMdJ2MgRwJEgQQsw1fA,529
|
|
2
|
+
entsoe/_http.py,sha256=XhZZy059XHh8uAP2Vsm1e7BfRwQcmGLEYdREcgaF3Tk,6608
|
|
3
|
+
entsoe/_mappings.py,sha256=d70uUyGzB1NaJYoUTCdtkRe0N-gt0wuaagNxzb7w4jc,17158
|
|
4
|
+
entsoe/_xml.py,sha256=cJa4_nVIk27O9bCXkp2w-QR2fV3UrH-A80mX54KReVU,8439
|
|
5
|
+
entsoe/cache.py,sha256=3UIuhaSH9LVGk9Ony5eMsk_qOmRzzMEyskQS_cmY020,14547
|
|
6
|
+
entsoe/catalog.py,sha256=FUPg_WqLBkfl5sUgT9m8Bua7IP10NvBsakcndpUOBa0,9733
|
|
7
|
+
entsoe/client.py,sha256=mFqPhpEZxt4rGL98EM9LCOtTAo_xZRgffkAKiPRP4cw,4146
|
|
8
|
+
entsoe/exceptions.py,sha256=vqeu6_LxAtDvJziPnjbH52KV0fm63F8n2Y1lzSNp2Bs,1017
|
|
9
|
+
entsoe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
entsoe/.agents/skills/entsoe/SKILL.md,sha256=JsFECSkfnxyR6iFx55WBBcMJjAOaMu84vhcsPsTVWC0,6452
|
|
11
|
+
entsoe/cli/__init__.py,sha256=qUGXx4zdi8dCQkq3PPPa1sgD3Q6KIhJwWeBfTub4suM,139
|
|
12
|
+
entsoe/cli/_output.py,sha256=bP1KqGtvDBy3Mtm_mZMY2Qp4dzmGWz7hcklKZuS0fno,3239
|
|
13
|
+
entsoe/cli/_resolve.py,sha256=oIUgZ2BF-p3AD2wQQBhkVCBHg8fbhIVFdrvll2sesD8,4799
|
|
14
|
+
entsoe/cli/app.py,sha256=vw5_Pabd7feTeP-Vpt50KVwbg70hFex6z7JNWTem9v0,2252
|
|
15
|
+
entsoe/cli/balancing.py,sha256=sYoyZ8KcU6WXNu_9HZNgehhEjJkockW86D3pHg6XGw8,2964
|
|
16
|
+
entsoe/cli/cache_cmd.py,sha256=ozbQ5apxeLBIjbPsH-Cs5rDOVBVfrzJfDPKv1anLFgY,2255
|
|
17
|
+
entsoe/cli/catalog_cmd.py,sha256=TzkkecSlnlz-f2_BlRqidKN6uTJtSInR0np4BA1rQ5c,5361
|
|
18
|
+
entsoe/cli/config.py,sha256=YB6IeOBGMKahcVJBGUaCN5r1vs6YILv_XbXlcKLxLiU,1663
|
|
19
|
+
entsoe/cli/config_cmd.py,sha256=gduIqUaW13APCUtf5eUc2g0l4i4_xqI5vFDs4-iR-pQ,1053
|
|
20
|
+
entsoe/cli/exec_cmd.py,sha256=9cCtDSCnzY5Z1iDWidY_iJ2fvALFraMFCdbbWqliYb8,11838
|
|
21
|
+
entsoe/cli/generation.py,sha256=TDLXWcA2kH9LGnueegBkKVkOyHTg_iWWNdoa2OV7h2Q,6105
|
|
22
|
+
entsoe/cli/load.py,sha256=ErvHaeZG8TUKdusoG4vWvFU2J8etiHE0G7c5jDqzvGc,2897
|
|
23
|
+
entsoe/cli/prices.py,sha256=3lyCGICm_CIzaVLkfYaGVju76BHbRkxNDTfYbTh5FHw,1835
|
|
24
|
+
entsoe/cli/transmission.py,sha256=oC4qoIQCnFUpi9A-2K8UI1tGDFMFAbG05T9jMEOfNig,5910
|
|
25
|
+
entsoe/namespaces/__init__.py,sha256=ZVd80s5OVxegLo3A6nI64tHlBA1Iwe2ejqQilGNod5E,399
|
|
26
|
+
entsoe/namespaces/_base.py,sha256=ExhByzVDnrj4CLmg0EXd7Zhm1Cd6gL-gQ7lp-J-60IM,17202
|
|
27
|
+
entsoe/namespaces/balancing.py,sha256=N4D_5n2a__HOodmG1p_3OP8Bw4NTSPyL16UWhGW6Vl0,2485
|
|
28
|
+
entsoe/namespaces/generation.py,sha256=UapqnvCyWmVdMasdKHHXeRJO6ybZN0NIIySIascXSlo,7460
|
|
29
|
+
entsoe/namespaces/load.py,sha256=QLXZY8Cg4I5ph5IBeJHY1op-9_Ku7cqeHqH91oR32bs,2502
|
|
30
|
+
entsoe/namespaces/prices.py,sha256=gUHxb-UeG7MLn9PWSzifsIM43QJGhDjW6rrzJatxfwM,2482
|
|
31
|
+
entsoe/namespaces/transmission.py,sha256=v7TDHBE2MhkUEG2ixXzZviD1GzAiY1fXMV2OXqtwoLk,8829
|
|
32
|
+
python_entsoe-0.7.0.dist-info/METADATA,sha256=lV2M8F4mqdkzvKu4Yo150iOGnmwp4Cqn7-XjWSqt0Vs,9740
|
|
33
|
+
python_entsoe-0.7.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
34
|
+
python_entsoe-0.7.0.dist-info/entry_points.txt,sha256=tYyCheggCsGRKOq2vIjpAxBksEM7K_iGI6vHkkQLmYg,42
|
|
35
|
+
python_entsoe-0.7.0.dist-info/RECORD,,
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
entsoe/__init__.py,sha256=9qzvMUzoD-CRfX1vTzWIBfpXBMdJ2MgRwJEgQQsw1fA,529
|
|
2
|
-
entsoe/_http.py,sha256=XhZZy059XHh8uAP2Vsm1e7BfRwQcmGLEYdREcgaF3Tk,6608
|
|
3
|
-
entsoe/_mappings.py,sha256=6-IKLtwChvM7_YG90oSRDxo3IfQUuz3Gnv17TmQgPjM,15534
|
|
4
|
-
entsoe/_xml.py,sha256=cJa4_nVIk27O9bCXkp2w-QR2fV3UrH-A80mX54KReVU,8439
|
|
5
|
-
entsoe/cache.py,sha256=3UIuhaSH9LVGk9Ony5eMsk_qOmRzzMEyskQS_cmY020,14547
|
|
6
|
-
entsoe/catalog.py,sha256=FUPg_WqLBkfl5sUgT9m8Bua7IP10NvBsakcndpUOBa0,9733
|
|
7
|
-
entsoe/client.py,sha256=zHB9y2TbCRjSNe9d5c9XVt_BNZ7b8KsvJDORc5V-ypE,3542
|
|
8
|
-
entsoe/exceptions.py,sha256=vqeu6_LxAtDvJziPnjbH52KV0fm63F8n2Y1lzSNp2Bs,1017
|
|
9
|
-
entsoe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
entsoe/.agents/skills/entsoe/SKILL.md,sha256=JsFECSkfnxyR6iFx55WBBcMJjAOaMu84vhcsPsTVWC0,6452
|
|
11
|
-
entsoe/cli/__init__.py,sha256=qUGXx4zdi8dCQkq3PPPa1sgD3Q6KIhJwWeBfTub4suM,139
|
|
12
|
-
entsoe/cli/_output.py,sha256=aV_3CLmOHSxTAVuYdccTWYahqTNPunCbSeoq3YZL-4A,2943
|
|
13
|
-
entsoe/cli/_resolve.py,sha256=oIUgZ2BF-p3AD2wQQBhkVCBHg8fbhIVFdrvll2sesD8,4799
|
|
14
|
-
entsoe/cli/app.py,sha256=_dQsejLncSFJTgOy-26Bvl7pIMXve_YD3gxP-gRVsG4,2069
|
|
15
|
-
entsoe/cli/balancing.py,sha256=ZXcjTEdbx00MUL4L6116G7MwBfpffB02DgazYexq-dc,2752
|
|
16
|
-
entsoe/cli/cache_cmd.py,sha256=ozbQ5apxeLBIjbPsH-Cs5rDOVBVfrzJfDPKv1anLFgY,2255
|
|
17
|
-
entsoe/cli/catalog_cmd.py,sha256=TzkkecSlnlz-f2_BlRqidKN6uTJtSInR0np4BA1rQ5c,5361
|
|
18
|
-
entsoe/cli/config.py,sha256=YB6IeOBGMKahcVJBGUaCN5r1vs6YILv_XbXlcKLxLiU,1663
|
|
19
|
-
entsoe/cli/config_cmd.py,sha256=HfpMMv8S1kDUIid6kxX-cNirCvhDby2lSPNSD8o3qX8,923
|
|
20
|
-
entsoe/cli/exec_cmd.py,sha256=KiGwOTIjYQQPEg7v51oRbpk3Cj9-7hDDCbEYXfZ_lfQ,11308
|
|
21
|
-
entsoe/cli/generation.py,sha256=wSQUyZJ3vuhcHss4R8g__2BMkTSik6rYkr4FvwWvvrs,5681
|
|
22
|
-
entsoe/cli/load.py,sha256=wC8ZYGi8ce_b5OBBC7fdUPLCgUN_ssC79A0eZJuXGSQ,2685
|
|
23
|
-
entsoe/cli/prices.py,sha256=Gj_Irw4i5KJ7lUUUChkm9FqBDCHorP9tN4fFMnc5v0c,1729
|
|
24
|
-
entsoe/cli/transmission.py,sha256=nQMQwxNcvxHEdlldHnVY02oL2aUKONKRpz2M3bFKM1U,5592
|
|
25
|
-
entsoe/namespaces/__init__.py,sha256=ZVd80s5OVxegLo3A6nI64tHlBA1Iwe2ejqQilGNod5E,399
|
|
26
|
-
entsoe/namespaces/_base.py,sha256=Mxsdix1FiYXilVoI1tapoIkgMmXY9XPCQ6NKFUy5xkI,14918
|
|
27
|
-
entsoe/namespaces/balancing.py,sha256=210Ft6dE7WXEuxtTJqyEvCIL960n-sDHT7tsqpSbXIw,2143
|
|
28
|
-
entsoe/namespaces/generation.py,sha256=V4DkvV5ai7aWYYSdZKwWyxTEv1ed4nCCq7DYyVwvc6M,6729
|
|
29
|
-
entsoe/namespaces/load.py,sha256=fbkwKWZifb8DOf5Rc_7c4_eFAXiqT_562r9WTMQwsuo,2160
|
|
30
|
-
entsoe/namespaces/prices.py,sha256=Dr1y5Y9wVdTOzTTWXlx11eiQLQc75chua3UoRpBYfxw,2311
|
|
31
|
-
entsoe/namespaces/transmission.py,sha256=V8zrs2CQXf1WPjJRqz7wc-ztnkfriIKXPLfYHBDJD9M,8067
|
|
32
|
-
python_entsoe-0.6.3.dist-info/METADATA,sha256=ydDvnqNB_ohS3tLgq9_Gbk3wzfVe42uIPcO695UVTWc,8863
|
|
33
|
-
python_entsoe-0.6.3.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
34
|
-
python_entsoe-0.6.3.dist-info/entry_points.txt,sha256=tYyCheggCsGRKOq2vIjpAxBksEM7K_iGI6vHkkQLmYg,42
|
|
35
|
-
python_entsoe-0.6.3.dist-info/RECORD,,
|
|
File without changes
|