python-esios 2.2.0__tar.gz → 2.3.0__tar.gz

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.
Files changed (83) hide show
  1. python_esios-2.3.0/.release-please-manifest.json +3 -0
  2. {python_esios-2.2.0 → python_esios-2.3.0}/CHANGELOG.md +12 -0
  3. {python_esios-2.2.0 → python_esios-2.3.0}/PKG-INFO +1 -1
  4. {python_esios-2.2.0 → python_esios-2.3.0}/pyproject.toml +1 -1
  5. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/managers/indicators.py +25 -4
  6. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/processing/i90.py +32 -11
  7. python_esios-2.2.0/.release-please-manifest.json +0 -3
  8. {python_esios-2.2.0 → python_esios-2.3.0}/.github/workflows/release-please.yml +0 -0
  9. {python_esios-2.2.0 → python_esios-2.3.0}/.gitignore +0 -0
  10. {python_esios-2.2.0 → python_esios-2.3.0}/CLAUDE.md +0 -0
  11. {python_esios-2.2.0 → python_esios-2.3.0}/LICENSE +0 -0
  12. {python_esios-2.2.0 → python_esios-2.3.0}/README.md +0 -0
  13. {python_esios-2.2.0 → python_esios-2.3.0}/examples/.gitignore +0 -0
  14. {python_esios-2.2.0 → python_esios-2.3.0}/examples/01_Quickstart/01_Setup and First Query.ipynb +0 -0
  15. {python_esios-2.2.0 → python_esios-2.3.0}/examples/02_Indicators/01_Search Indicators.ipynb +0 -0
  16. {python_esios-2.2.0 → python_esios-2.3.0}/examples/02_Indicators/02_Historical Data.ipynb +0 -0
  17. {python_esios-2.2.0 → python_esios-2.3.0}/examples/02_Indicators/03_Multi-Geography Indicators.ipynb +0 -0
  18. {python_esios-2.2.0 → python_esios-2.3.0}/examples/02_Indicators/04_Compare Indicators.ipynb +0 -0
  19. {python_esios-2.2.0 → python_esios-2.3.0}/examples/02_Indicators/05_Market Prices.ipynb +0 -0
  20. {python_esios-2.2.0 → python_esios-2.3.0}/examples/02_Indicators/06_Generation and Demand.ipynb +0 -0
  21. {python_esios-2.2.0 → python_esios-2.3.0}/examples/03_Archives/01_Download Archives.ipynb +0 -0
  22. {python_esios-2.2.0 → python_esios-2.3.0}/examples/03_Archives/02_I90 Settlement Files.ipynb +0 -0
  23. {python_esios-2.2.0 → python_esios-2.3.0}/examples/04_Caching/01_Cache Management.ipynb +0 -0
  24. {python_esios-2.2.0 → python_esios-2.3.0}/examples/05_Advanced/01_Ad-hoc Pandas Expressions.ipynb +0 -0
  25. {python_esios-2.2.0 → python_esios-2.3.0}/examples/05_Advanced/02_Async Client.ipynb +0 -0
  26. {python_esios-2.2.0 → python_esios-2.3.0}/examples/_specs/01_Quickstart/01_Setup and First Query.yaml +0 -0
  27. {python_esios-2.2.0 → python_esios-2.3.0}/examples/_specs/02_Indicators/01_Search Indicators.yaml +0 -0
  28. {python_esios-2.2.0 → python_esios-2.3.0}/examples/_specs/02_Indicators/02_Historical Data.yaml +0 -0
  29. {python_esios-2.2.0 → python_esios-2.3.0}/examples/_specs/02_Indicators/03_Multi-Geography Indicators.yaml +0 -0
  30. {python_esios-2.2.0 → python_esios-2.3.0}/examples/_specs/02_Indicators/04_Compare Indicators.yaml +0 -0
  31. {python_esios-2.2.0 → python_esios-2.3.0}/examples/_specs/02_Indicators/05_Market Prices.yaml +0 -0
  32. {python_esios-2.2.0 → python_esios-2.3.0}/examples/_specs/02_Indicators/06_Generation and Demand.yaml +0 -0
  33. {python_esios-2.2.0 → python_esios-2.3.0}/examples/_specs/03_Archives/01_Download Archives.yaml +0 -0
  34. {python_esios-2.2.0 → python_esios-2.3.0}/examples/_specs/03_Archives/02_I90 Settlement Files.yaml +0 -0
  35. {python_esios-2.2.0 → python_esios-2.3.0}/examples/_specs/04_Caching/01_Cache Management.yaml +0 -0
  36. {python_esios-2.2.0 → python_esios-2.3.0}/examples/_specs/05_Advanced/01_Ad-hoc Pandas Expressions.yaml +0 -0
  37. {python_esios-2.2.0 → python_esios-2.3.0}/examples/_specs/05_Advanced/02_Async Client.yaml +0 -0
  38. {python_esios-2.2.0 → python_esios-2.3.0}/examples/generate.py +0 -0
  39. {python_esios-2.2.0 → python_esios-2.3.0}/release-please-config.json +0 -0
  40. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/.agents/skills/esios/SKILL.md +0 -0
  41. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/__init__.py +0 -0
  42. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/async_client.py +0 -0
  43. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/cache.py +0 -0
  44. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/catalog.py +0 -0
  45. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/cli/__init__.py +0 -0
  46. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/cli/app.py +0 -0
  47. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/cli/archives.py +0 -0
  48. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/cli/cache_cmd.py +0 -0
  49. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/cli/catalog_cmd.py +0 -0
  50. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/cli/config.py +0 -0
  51. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/cli/config_cmd.py +0 -0
  52. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/cli/exec_cmd.py +0 -0
  53. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/cli/indicators.py +0 -0
  54. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/client.py +0 -0
  55. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/constants.py +0 -0
  56. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/data/__init__.py +0 -0
  57. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/data/archives.yaml +0 -0
  58. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/data/geos.yaml +0 -0
  59. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/data/indicators.yaml +0 -0
  60. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/data/magnitudes.yaml +0 -0
  61. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/data/time_periods.yaml +0 -0
  62. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/exceptions.py +0 -0
  63. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/managers/__init__.py +0 -0
  64. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/managers/archives.py +0 -0
  65. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/managers/base.py +0 -0
  66. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/managers/offer_indicators.py +0 -0
  67. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/models/__init__.py +0 -0
  68. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/models/archive.py +0 -0
  69. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/models/indicator.py +0 -0
  70. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/models/offer_indicator.py +0 -0
  71. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/processing/__init__.py +0 -0
  72. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/processing/dataframes.py +0 -0
  73. {python_esios-2.2.0 → python_esios-2.3.0}/src/esios/processing/zip.py +0 -0
  74. {python_esios-2.2.0 → python_esios-2.3.0}/tests/__init__.py +0 -0
  75. {python_esios-2.2.0 → python_esios-2.3.0}/tests/conftest.py +0 -0
  76. {python_esios-2.2.0 → python_esios-2.3.0}/tests/test_cache.py +0 -0
  77. {python_esios-2.2.0 → python_esios-2.3.0}/tests/test_client.py +0 -0
  78. {python_esios-2.2.0 → python_esios-2.3.0}/tests/test_dataframes.py +0 -0
  79. {python_esios-2.2.0 → python_esios-2.3.0}/tests/test_exceptions.py +0 -0
  80. {python_esios-2.2.0 → python_esios-2.3.0}/tests/test_i90.py +0 -0
  81. {python_esios-2.2.0 → python_esios-2.3.0}/tests/test_managers.py +0 -0
  82. {python_esios-2.2.0 → python_esios-2.3.0}/tests/test_models.py +0 -0
  83. {python_esios-2.2.0 → python_esios-2.3.0}/tests/test_zip.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "2.3.0"
3
+ }
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.3.0](https://github.com/datons/python-esios/compare/python-esios-v2.2.0...python-esios-v2.3.0) (2026-03-16)
4
+
5
+
6
+ ### Features
7
+
8
+ * restore column_name parameter in historical() ([a967b6f](https://github.com/datons/python-esios/commit/a967b6f54cd4e84d18469d99f0d4e03665b73e81))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * handle DST transitions in I90 datetime parsing ([aa1deef](https://github.com/datons/python-esios/commit/aa1deefd442f2c53a376a4f3773b6fffea562121))
14
+
3
15
  ## [2.2.0](https://github.com/datons/python-esios/compare/python-esios-v2.1.0...python-esios-v2.2.0) (2026-03-04)
4
16
 
5
17
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-esios
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: A Python wrapper for the ESIOS API (Spanish electricity market)
5
5
  Project-URL: Homepage, https://github.com/datons/python-esios
6
6
  Project-URL: Repository, https://github.com/datons/python-esios
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-esios"
7
- version = "2.2.0"
7
+ version = "2.3.0"
8
8
  description = "A Python wrapper for the ESIOS API (Spanish electricity market)"
9
9
  readme = "README.md"
10
10
  license = "GPL-3.0-only"
@@ -142,6 +142,7 @@ class IndicatorHandle:
142
142
  geo_agg: str | None = None,
143
143
  time_trunc: str | None = None,
144
144
  geo_trunc: str | None = None,
145
+ column_name: str | None = None,
145
146
  ) -> pd.DataFrame:
146
147
  """Fetch historical values as a DataFrame with DatetimeIndex.
147
148
 
@@ -151,6 +152,12 @@ class IndicatorHandle:
151
152
  When multiple geo_ids are present (e.g. indicator 600 returns data for
152
153
  several countries), the result is pivoted so each geo becomes a column
153
154
  named by its geo_name. Use *geo_ids* to filter to specific geos.
155
+
156
+ Args:
157
+ column_name: If provided, rename the output column(s) to this name.
158
+ Useful for single-column results where a stable name like
159
+ ``"value"`` is preferred over the default geo_name or
160
+ indicator ID.
154
161
  """
155
162
  base_params: dict[str, Any] = {
156
163
  "locale": locale,
@@ -250,7 +257,7 @@ class IndicatorHandle:
250
257
  if existing:
251
258
  result = result[existing]
252
259
 
253
- return self._finalize(result)
260
+ return self._finalize(result, column_name=column_name)
254
261
 
255
262
  def _to_wide(self, values: list[dict]) -> pd.DataFrame:
256
263
  """Convert raw API value dicts to wide-format DataFrame.
@@ -283,16 +290,26 @@ class IndicatorHandle:
283
290
  df = df.drop(columns=geo_drop, errors="ignore")
284
291
  return df
285
292
 
286
- def _finalize(self, df: pd.DataFrame) -> pd.DataFrame:
293
+ def _finalize(
294
+ self, df: pd.DataFrame, *, column_name: str | None = None,
295
+ ) -> pd.DataFrame:
287
296
  """Prepare DataFrame for user-facing output.
288
297
 
289
298
  Cache stores columns as str(geo_id). This method renames them to
290
299
  human-readable geo_names at the very end, just before returning to
291
300
  the caller. Single-value/single-geo indicators get the indicator ID.
301
+
302
+ If ``column_name`` is provided and the result has a single column,
303
+ that column is renamed to ``column_name`` (e.g. ``"value"``).
292
304
  """
293
305
  if df.empty:
294
306
  return df
295
307
 
308
+ # If caller wants a specific column name and there's a single column, use it
309
+ if column_name and len(df.columns) == 1:
310
+ df = df.rename(columns={df.columns[0]: column_name})
311
+ return df
312
+
296
313
  if len(df.columns) == 1:
297
314
  col = df.columns[0]
298
315
  if col == "value":
@@ -305,11 +322,15 @@ class IndicatorHandle:
305
322
  if rename:
306
323
  df = df.rename(columns=rename)
307
324
 
325
+ # If caller wants a specific column name for multi-column, skip
326
+ # (ambiguous which column to rename)
327
+ if column_name and len(df.columns) == 1:
328
+ df = df.rename(columns={df.columns[0]: column_name})
329
+ return df
330
+
308
331
  # Single-geo after rename: use indicator ID as column name
309
332
  if len(df.columns) == 1:
310
333
  col = df.columns[0]
311
- # If the single column is a geo_name, keep it (user filtered to one geo)
312
- # If it's still a geo_id string, rename to indicator ID
313
334
  if col not in geo_map.values():
314
335
  df = df.rename(columns={col: str(self.id)})
315
336
 
@@ -166,11 +166,15 @@ class I90Sheet:
166
166
  def _normalize_datetime_columns(self, columns: np.ndarray) -> np.ndarray:
167
167
  """Normalize time column headers to integer period indices.
168
168
 
169
- Handles three column formats found in I90 files:
170
- - Sequential integers 1–24 (hourly) or 1–96 (quarterly)
171
- - H-Q format with dash notation: "1-1", "1-2", "1-3", "1-4", "2-1", …
172
- - NaN-filler format: [1, NaN, NaN, NaN, 2, …] (one label per hour,
173
- three trailing NaNs for quarters 2–4)
169
+ Handles four column formats found in I90 files:
170
+
171
+ 1. Sequential integers: 1–24 (hourly) or 1–96 (quarterly)
172
+ 2. H-Q format: "1-1", "1-2", "1-3", "1-4", "2-1", …
173
+ 3. NaN-filler format: [1, NaN, NaN, NaN, 2, …]
174
+ 4. Range format (DST days): "00-01", "01-02", "02-03a", "02-03b", …
175
+ where the first number is the start hour and a/b suffix marks
176
+ the repeated hour on fall-back days. Detected by the first
177
+ column starting with "0" (e.g. "00-01").
174
178
  """
175
179
  if any(pd.isna(columns)):
176
180
  self._n_columns_totals = 3
@@ -178,6 +182,17 @@ class I90Sheet:
178
182
  self._n_columns_totals = 2
179
183
 
180
184
  series = pd.Series(columns, dtype=str).ffill()
185
+
186
+ # Range format (DST): "00-01", "01-02", "02-03a", "02-03b", ...
187
+ # Detected by first column starting with "0" (sequential ints start at 1).
188
+ first_val = str(columns[0]).strip()
189
+ if first_val.startswith("0") and "-" in first_val:
190
+ # Simply assign sequential 1-based indices.
191
+ # The count of columns (23, 24, or 25 for hourly; 92, 96, or 100
192
+ # for QH) already encodes the DST information. The datetime builder
193
+ # in _preprocess uses these as offsets from midnight UTC.
194
+ return np.arange(1, len(columns) + 1)
195
+
181
196
  parts = series.str.split("-")
182
197
  hours = parts.str[0].astype(float).astype(int)
183
198
 
@@ -251,12 +266,18 @@ class I90Sheet:
251
266
  self.frequency = "hourly"
252
267
  time_deltas = columns_date * 60 # minutes
253
268
 
254
- # Build datetime index
255
- base_date = pd.to_datetime(self.metadata["date_data"])
256
- columns_datetime = base_date + pd.to_timedelta(time_deltas, unit="m")
257
- columns_datetime = pd.DatetimeIndex(columns_datetime).tz_localize(
258
- "Europe/Madrid", ambiguous="infer"
259
- )
269
+ # Build datetime index in UTC to avoid DST ambiguity.
270
+ # On fall-back days (Oct), I90 has 25 hourly periods (or 100 QH).
271
+ # Naïve offset arithmetic creates a single 02:00 that tz_localize
272
+ # cannot disambiguate. By anchoring midnight in Europe/Madrid,
273
+ # converting to UTC, then adding offsets, each period maps to a
274
+ # unique UTC instant — no ambiguity.
275
+ # On spring-forward days (Mar), I90 has 23 periods (or 92 QH)
276
+ # and this approach naturally skips the non-existent hour.
277
+ midnight_utc = pd.Timestamp(
278
+ self.metadata["date_data"], tz="Europe/Madrid"
279
+ ).tz_convert("UTC")
280
+ columns_datetime = midnight_utc + pd.to_timedelta(time_deltas, unit="m")
260
281
 
261
282
  data = pd.DataFrame(self.rows[idx + 1 :], columns=columns)
262
283
 
@@ -1,3 +0,0 @@
1
- {
2
- ".": "2.2.0"
3
- }
File without changes
File without changes
File without changes
File without changes