e-data 2.0.0.dev126__tar.gz → 2.0.1.dev128__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 (34) hide show
  1. {e_data-2.0.0.dev126/e_data.egg-info → e_data-2.0.1.dev128}/PKG-INFO +2 -2
  2. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128/e_data.egg-info}/PKG-INFO +2 -2
  3. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/e_data.egg-info/requires.txt +1 -1
  4. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/core/utils.py +14 -1
  5. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/database/controller.py +23 -3
  6. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/services/bill_service.py +1 -1
  7. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/services/data_service.py +2 -2
  8. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/pyproject.toml +2 -2
  9. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/LICENSE +0 -0
  10. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/MANIFEST.in +0 -0
  11. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/README.md +0 -0
  12. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/e_data.egg-info/SOURCES.txt +0 -0
  13. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/e_data.egg-info/dependency_links.txt +0 -0
  14. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/e_data.egg-info/top_level.txt +0 -0
  15. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/__init__.py +0 -0
  16. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/cli.py +0 -0
  17. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/core/__init__.py +0 -0
  18. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/core/const.py +0 -0
  19. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/database/__init__.py +0 -0
  20. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/database/models.py +0 -0
  21. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/database/queries.py +0 -0
  22. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/database/utils.py +0 -0
  23. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/models/__init__.py +0 -0
  24. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/models/bill.py +0 -0
  25. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/models/data.py +0 -0
  26. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/models/supply.py +0 -0
  27. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/providers/__init__.py +0 -0
  28. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/providers/datadis.py +0 -0
  29. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/providers/redata.py +0 -0
  30. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/tests/__init__.py +0 -0
  31. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/tests/test_datadis_connector.py +0 -0
  32. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/tests/test_redata_connector.py +0 -0
  33. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/edata/tests/test_services.py +0 -0
  34. {e_data-2.0.0.dev126 → e_data-2.0.1.dev128}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: e-data
3
- Version: 2.0.0.dev126
3
+ Version: 2.0.1.dev128
4
4
  Summary: Python library for managing spanish energy data from various web providers
5
5
  Author-email: VMG <vmayorg@outlook.es>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -696,7 +696,7 @@ Requires-Dist: Jinja2<4,>=3.1
696
696
  Requires-Dist: pydantic<3,>=2.10
697
697
  Requires-Dist: python_dateutil<3,>=2.8
698
698
  Requires-Dist: Requests<3,>=2.31
699
- Requires-Dist: SQLAlchemy<3,>=2.0
699
+ Requires-Dist: SQLAlchemy[asyncio]<3,>=2.0
700
700
  Requires-Dist: sqlmodel<0.1,>=0.0.22
701
701
  Requires-Dist: typer<1,>=0.12
702
702
  Requires-Dist: aiosqlite<1,>=0.21
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: e-data
3
- Version: 2.0.0.dev126
3
+ Version: 2.0.1.dev128
4
4
  Summary: Python library for managing spanish energy data from various web providers
5
5
  Author-email: VMG <vmayorg@outlook.es>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -696,7 +696,7 @@ Requires-Dist: Jinja2<4,>=3.1
696
696
  Requires-Dist: pydantic<3,>=2.10
697
697
  Requires-Dist: python_dateutil<3,>=2.8
698
698
  Requires-Dist: Requests<3,>=2.31
699
- Requires-Dist: SQLAlchemy<3,>=2.0
699
+ Requires-Dist: SQLAlchemy[asyncio]<3,>=2.0
700
700
  Requires-Dist: sqlmodel<0.1,>=0.0.22
701
701
  Requires-Dist: typer<1,>=0.12
702
702
  Requires-Dist: aiosqlite<1,>=0.21
@@ -7,7 +7,7 @@ Jinja2<4,>=3.1
7
7
  pydantic<3,>=2.10
8
8
  python_dateutil<3,>=2.8
9
9
  Requests<3,>=2.31
10
- SQLAlchemy<3,>=2.0
10
+ SQLAlchemy[asyncio]<3,>=2.0
11
11
  sqlmodel<0.1,>=0.0.22
12
12
  typer<1,>=0.12
13
13
  aiosqlite<1,>=0.21
@@ -2,6 +2,7 @@
2
2
 
3
3
  import logging
4
4
  from datetime import datetime
5
+ from functools import lru_cache
5
6
  from pathlib import Path
6
7
 
7
8
  import holidays
@@ -19,10 +20,22 @@ TARIFF_BY_WEEKDAY = [[], [], [5, 6]]
19
20
  _LOGGER = logging.getLogger(__name__)
20
21
 
21
22
 
23
+ @lru_cache(maxsize=None)
24
+ def _es_holidays(year: int) -> holidays.HolidayBase:
25
+ """Return (and cache) Spanish holidays for a single year.
26
+
27
+ Building the holidays calendar is expensive; get_tariff is called once per
28
+ hourly record, so without this cache a full-history rebuild spends minutes
29
+ reconstructing the same handful of yearly calendars.
30
+ """
31
+
32
+ return holidays.country_holidays("ES", years=year)
33
+
34
+
22
35
  def get_tariff(dt: datetime) -> int:
23
36
  """Return the tariff for the selected datetime."""
24
37
 
25
- hdays = holidays.country_holidays("ES")
38
+ hdays = _es_holidays(dt.year)
26
39
  hour = dt.hour
27
40
  weekday = dt.weekday()
28
41
 
@@ -3,7 +3,7 @@ import os
3
3
  import typing
4
4
  from datetime import datetime
5
5
 
6
- from sqlalchemy import Select, insert
6
+ from sqlalchemy import Select, event, insert
7
7
  from sqlalchemy.exc import IntegrityError
8
8
  from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
9
9
  from sqlmodel import SQLModel
@@ -28,6 +28,25 @@ _LOGGER = logging.getLogger(__name__)
28
28
  T = typing.TypeVar("T", bound=SQLModel)
29
29
 
30
30
 
31
+ def _set_sqlite_pragmas(dbapi_connection, connection_record) -> None:
32
+ """Tune SQLite for this write-heavy, single-writer workload.
33
+
34
+ WAL lets readers proceed while a write is in flight and avoids an fsync per
35
+ statement; synchronous=NORMAL is durable under WAL (only a crash mid-checkpoint
36
+ could lose the last transaction, and the data is re-fetchable from Datadis);
37
+ busy_timeout avoids spurious "database is locked" errors under concurrency.
38
+ journal_mode is persisted in the file; the others are per-connection.
39
+ """
40
+
41
+ cursor = dbapi_connection.cursor()
42
+ try:
43
+ cursor.execute("PRAGMA journal_mode=WAL")
44
+ cursor.execute("PRAGMA synchronous=NORMAL")
45
+ cursor.execute("PRAGMA busy_timeout=5000")
46
+ finally:
47
+ cursor.close()
48
+
49
+
31
50
  class EdataDB:
32
51
 
33
52
  _instance = None
@@ -39,10 +58,11 @@ class EdataDB:
39
58
  if cls._instance is None:
40
59
  cls._instance = super().__new__(cls)
41
60
  cls._db_url = db_url
42
- cls._engine = create_async_engine(db_url, future=True)
43
- # Ensure parent directory exists
61
+ # Ensure parent directory exists before the first connection is opened.
44
62
  dir_path = os.path.dirname(os.path.abspath(sqlite_path))
45
63
  os.makedirs(dir_path, exist_ok=True)
64
+ cls._engine = create_async_engine(db_url, future=True)
65
+ event.listen(cls._engine.sync_engine, "connect", _set_sqlite_pragmas)
46
66
  cls._instance._tables_initialized = False
47
67
  elif db_url != cls._db_url:
48
68
  raise ValueError("EdataDB already initialized with a different db_url")
@@ -286,7 +286,7 @@ class BillService:
286
286
  missing = await self._find_missing_stats()
287
287
  for day in missing:
288
288
  _LOGGER.debug("%s updating daily statistics for date %s", self._scups, day)
289
- end = day + relativedelta.relativedelta(day=1) - timedelta(minutes=1)
289
+ end = day + relativedelta.relativedelta(days=1) - timedelta(minutes=1)
290
290
  await self._update_daily_statistics(day, end)
291
291
 
292
292
  missing_months = list(set([get_month(x) for x in missing]))
@@ -120,7 +120,7 @@ class DataService:
120
120
  missing = await self._find_missing_stats()
121
121
  for day in missing:
122
122
  _LOGGER.info("%s updating daily statistics for date %s", self._scups, day)
123
- end = day + relativedelta.relativedelta(day=1) - timedelta(hours=1)
123
+ end = day + relativedelta.relativedelta(days=1) - timedelta(hours=1)
124
124
  await self._update_daily_statistics(day, end)
125
125
 
126
126
  missing_months = await self._find_missing_stats("month")
@@ -128,7 +128,7 @@ class DataService:
128
128
  _LOGGER.info(
129
129
  "%s updating monthly statistics for date %s", self._scups, month
130
130
  )
131
- end = month + relativedelta.relativedelta(month=1) - timedelta(hours=1)
131
+ end = month + relativedelta.relativedelta(months=1) - timedelta(hours=1)
132
132
  await self._update_monthly_statistics(month, end)
133
133
 
134
134
  async def update(
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "e-data"
7
- version = "2.0.0.dev126"
7
+ version = "2.0.1.dev128"
8
8
  description = "Python library for managing spanish energy data from various web providers"
9
9
  readme = "README.md"
10
10
  authors = [
@@ -29,7 +29,7 @@ dependencies = [
29
29
  "pydantic>=2.10,<3",
30
30
  "python_dateutil>=2.8,<3",
31
31
  "Requests>=2.31,<3",
32
- "SQLAlchemy>=2.0,<3",
32
+ "SQLAlchemy[asyncio]>=2.0,<3",
33
33
  "sqlmodel>=0.0.22,<0.1",
34
34
  "typer>=0.12,<1",
35
35
  "aiosqlite>=0.21,<1",
File without changes
File without changes
File without changes
File without changes