Skip to content

Commit da64471

Browse files
committed
feat: add parquet caching, multi-value support, and CLI improvements
- Add local parquet cache (cache.py, cli/cache_cmd.py) to avoid redundant API calls - Add multi-value support for transmission queries (--from/--to accept lists) - Add _resolve.py for shared CLI argument resolution - Expand _mappings.py with additional bidding zone codes - Improve exec_cmd.py with richer pandas expression support - Update all namespaces to accept optional cache parameter - Bump version to 0.5.0
1 parent 86ec008 commit da64471

18 files changed

Lines changed: 1649 additions & 223 deletions

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "python-entsoe"
3-
version = "0.4.2"
3+
version = "0.5.0"
44
description = "Python client for the ENTSO-E Transparency Platform API"
55
readme = "README.md"
66
license = "MIT"

src/entsoe/_mappings.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,149 @@ def psr_name(identifier: str) -> str:
354354
"""
355355
code = lookup_psr(identifier)
356356
return PSR_TYPES[code]
357+
358+
359+
# ── Border / neighbour data (for CLI --border flag) ───────────────────────
360+
361+
# Direct interconnections between bidding zones used by ENTSO-E.
362+
# Each key maps to the set of zones it has a physical border with.
363+
NEIGHBOURS: dict[str, set[str]] = {
364+
"ES": {"FR", "PT"},
365+
"FR": {"ES", "BE", "CH", "DE_LU", "GB", "IT_NORTH"},
366+
"PT": {"ES"},
367+
"BE": {"FR", "NL", "DE_LU", "GB"},
368+
"NL": {"BE", "DE_LU", "GB", "NO_2", "DK_1"},
369+
"DE_LU": {"FR", "BE", "NL", "AT", "CH", "CZ", "DK_1", "DK_2", "PL", "SE_4", "NO_2"},
370+
"AT": {"DE_LU", "CH", "CZ", "HU", "SI", "IT_NORTH"},
371+
"CH": {"FR", "DE_LU", "AT", "IT_NORTH"},
372+
"IT_NORTH": {"FR", "CH", "AT", "SI"},
373+
"GB": {"FR", "BE", "NL", "IE_SEM", "NO_2"},
374+
"IE_SEM": {"GB"},
375+
"DK_1": {"DE_LU", "NL", "NO_2", "SE_3", "DK_2"},
376+
"DK_2": {"DE_LU", "DK_1", "SE_4"},
377+
"NO_1": {"NO_2", "NO_3", "NO_5", "SE_3"},
378+
"NO_2": {"NO_1", "NO_5", "NL", "DK_1", "DE_LU", "GB"},
379+
"NO_3": {"NO_1", "NO_4", "NO_5", "SE_2"},
380+
"NO_4": {"NO_3", "SE_1", "SE_2", "FI"},
381+
"NO_5": {"NO_1", "NO_2", "NO_3"},
382+
"SE_1": {"NO_4", "SE_2", "FI"},
383+
"SE_2": {"NO_3", "NO_4", "SE_1", "SE_3"},
384+
"SE_3": {"NO_1", "DK_1", "SE_2", "SE_4", "FI"},
385+
"SE_4": {"DE_LU", "DK_2", "SE_3", "PL", "LT"},
386+
"FI": {"NO_4", "SE_1", "SE_3", "EE"},
387+
"EE": {"FI", "LV"},
388+
"LV": {"EE", "LT"},
389+
"LT": {"LV", "SE_4", "PL"},
390+
"PL": {"DE_LU", "CZ", "SK", "LT", "SE_4"},
391+
"CZ": {"DE_LU", "AT", "PL", "SK"},
392+
"SK": {"CZ", "PL", "HU"},
393+
"HU": {"AT", "SK", "RO", "HR", "SI", "RS"},
394+
"SI": {"AT", "IT_NORTH", "HU", "HR"},
395+
"HR": {"HU", "SI", "RS", "BA"},
396+
"RS": {"HU", "HR", "BA", "RO", "BG", "MK", "ME", "AL"},
397+
"BA": {"HR", "RS", "ME"},
398+
"RO": {"HU", "RS", "BG"},
399+
"BG": {"RO", "RS", "MK", "GR", "TR"},
400+
"MK": {"RS", "BG", "GR", "AL"},
401+
"GR": {"BG", "MK", "AL", "IT_SUD", "TR"},
402+
"AL": {"RS", "MK", "GR", "ME"},
403+
"ME": {"RS", "BA", "AL"},
404+
"TR": {"BG", "GR"},
405+
}
406+
407+
# Named border groups for convenient CLI usage.
408+
# ── Border parsing (library-level) ────────────────────────────────────────
409+
410+
411+
def parse_borders(specs: str | list[str]) -> list[tuple[str, str]]:
412+
"""Parse border specifications into deduplicated (from, to) pairs.
413+
414+
Supported formats:
415+
416+
- ``"ES-FR"`` — single border
417+
- ``"ES-FR,ES-PT"`` — comma-separated within a string
418+
- ``["ES-*", "FR-*"]`` — list with wildcards (all neighbours)
419+
- ``"iberian"`` — named group
420+
421+
Deduplicates pairs while preserving insertion order, so
422+
``"ES-*,FR-*"`` won't produce ``(ES, FR)`` and ``(FR, ES)`` twice
423+
if both wildcards expand to include each other.
424+
425+
Args:
426+
specs: A single spec string or list of spec strings.
427+
428+
Returns:
429+
Deduplicated list of ``(country_from, country_to)`` tuples.
430+
431+
Raises:
432+
InvalidParameterError: On unknown country or invalid spec format.
433+
"""
434+
from .exceptions import InvalidParameterError
435+
436+
if isinstance(specs, str):
437+
specs = [specs]
438+
439+
flat = [v.strip() for raw in specs for v in raw.split(",") if v.strip()]
440+
seen: set[tuple[str, str]] = set()
441+
pairs: list[tuple[str, str]] = []
442+
443+
def _add(pair: tuple[str, str]) -> None:
444+
if pair not in seen:
445+
seen.add(pair)
446+
pairs.append(pair)
447+
448+
for spec in flat:
449+
low = spec.lower()
450+
451+
# Named group
452+
if low in BORDER_GROUPS:
453+
for p in BORDER_GROUPS[low]:
454+
_add(p)
455+
continue
456+
457+
# Pair: A-B or A-*
458+
if "-" in spec:
459+
parts = spec.split("-", 1)
460+
a, b = parts[0].strip().upper(), parts[1].strip().upper()
461+
if b == "*":
462+
if a not in NEIGHBOURS:
463+
raise InvalidParameterError(
464+
f"No known neighbours for '{a}'. "
465+
f"Available: {', '.join(sorted(NEIGHBOURS.keys()))}"
466+
)
467+
for n in sorted(NEIGHBOURS[a]):
468+
_add((a, n))
469+
else:
470+
_add((a, b))
471+
continue
472+
473+
raise InvalidParameterError(
474+
f"Invalid border spec '{spec}'. Use A-B, A-*, or a group name "
475+
f"({', '.join(sorted(BORDER_GROUPS.keys()))})."
476+
)
477+
478+
return pairs
479+
480+
481+
BORDER_GROUPS: dict[str, list[tuple[str, str]]] = {
482+
"iberian": [("ES", "FR"), ("ES", "PT"), ("PT", "ES"), ("FR", "ES")],
483+
"nordic": [
484+
("NO_1", "NO_2"), ("NO_1", "NO_3"), ("NO_1", "NO_5"), ("NO_1", "SE_3"),
485+
("NO_2", "NO_1"), ("NO_2", "NO_5"), ("NO_2", "NL"), ("NO_2", "DK_1"), ("NO_2", "DE_LU"), ("NO_2", "GB"),
486+
("NO_3", "NO_1"), ("NO_3", "NO_4"), ("NO_3", "NO_5"), ("NO_3", "SE_2"),
487+
("NO_4", "NO_3"), ("NO_4", "SE_1"), ("NO_4", "SE_2"), ("NO_4", "FI"),
488+
("NO_5", "NO_1"), ("NO_5", "NO_2"), ("NO_5", "NO_3"),
489+
("SE_1", "NO_4"), ("SE_1", "SE_2"), ("SE_1", "FI"),
490+
("SE_2", "NO_3"), ("SE_2", "NO_4"), ("SE_2", "SE_1"), ("SE_2", "SE_3"),
491+
("SE_3", "NO_1"), ("SE_3", "DK_1"), ("SE_3", "SE_2"), ("SE_3", "SE_4"), ("SE_3", "FI"),
492+
("SE_4", "DE_LU"), ("SE_4", "DK_2"), ("SE_4", "SE_3"), ("SE_4", "PL"), ("SE_4", "LT"),
493+
("DK_1", "DK_2"), ("DK_1", "DE_LU"), ("DK_1", "NL"), ("DK_1", "NO_2"), ("DK_1", "SE_3"),
494+
("DK_2", "DK_1"), ("DK_2", "DE_LU"), ("DK_2", "SE_4"),
495+
("FI", "NO_4"), ("FI", "SE_1"), ("FI", "SE_3"), ("FI", "EE"),
496+
],
497+
"baltic": [
498+
("EE", "FI"), ("EE", "LV"),
499+
("LV", "EE"), ("LV", "LT"),
500+
("LT", "LV"), ("LT", "SE_4"), ("LT", "PL"),
501+
],
502+
}

0 commit comments

Comments
 (0)