e-data 2.0.0.dev126__py3-none-any.whl → 2.0.1.dev128__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.
- {e_data-2.0.0.dev126.dist-info → e_data-2.0.1.dev128.dist-info}/METADATA +2 -2
- {e_data-2.0.0.dev126.dist-info → e_data-2.0.1.dev128.dist-info}/RECORD +9 -9
- edata/core/utils.py +14 -1
- edata/database/controller.py +23 -3
- edata/services/bill_service.py +1 -1
- edata/services/data_service.py +2 -2
- {e_data-2.0.0.dev126.dist-info → e_data-2.0.1.dev128.dist-info}/WHEEL +0 -0
- {e_data-2.0.0.dev126.dist-info → e_data-2.0.1.dev128.dist-info}/licenses/LICENSE +0 -0
- {e_data-2.0.0.dev126.dist-info → e_data-2.0.1.dev128.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: e-data
|
|
3
|
-
Version: 2.0.
|
|
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,11 +1,11 @@
|
|
|
1
|
-
e_data-2.0.
|
|
1
|
+
e_data-2.0.1.dev128.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
2
2
|
edata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
edata/cli.py,sha256=0pmlaaXPVfbxPVHMovsuK9qf8PGXl38AV3gtIsk95O4,4010
|
|
4
4
|
edata/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
edata/core/const.py,sha256=X9J2f7lt9jXJnghqz0JFi-J7USa3xlYvbuiki70eHNE,43
|
|
6
|
-
edata/core/utils.py,sha256=
|
|
6
|
+
edata/core/utils.py,sha256=BHQaG5jInxD1yrb_ReUgkoONrZjgdk6knkVi3Nv4rGc,2382
|
|
7
7
|
edata/database/__init__.py,sha256=HP_pDbGLzeMY9Ufm_Dps5cxzW_3YTonT8w8dstG558c,46
|
|
8
|
-
edata/database/controller.py,sha256=
|
|
8
|
+
edata/database/controller.py,sha256=mNo8HZTAa6AaaTHnXwXdmeQx_IpqlBS2mg3sSFR_bVY,17659
|
|
9
9
|
edata/database/models.py,sha256=4rlRpPErULqYr6QJxZar1NBpJPWTcHftDN1MB3bwGyA,5096
|
|
10
10
|
edata/database/queries.py,sha256=rYdJ1X_yEY9FDU4NnEFjvanOcz7glZiQ4JrKCzXWXUM,7048
|
|
11
11
|
edata/database/utils.py,sha256=V-FJfyAh8YJBGHB85BbWtG3Ilq09pM9Gl-L86TffB4g,1062
|
|
@@ -16,13 +16,13 @@ edata/models/supply.py,sha256=qM0Q22-7PfBN93-Tu3ljAE58p5gfob0YWw-o5FccjrI,878
|
|
|
16
16
|
edata/providers/__init__.py,sha256=5xaRKPttYM6EpVE1I_vhUAF25L9pEM_qK_5Ix6xnQe0,104
|
|
17
17
|
edata/providers/datadis.py,sha256=a084ArY83ncvrMQvMSD2BY1rHlhp42dwJJkU8avmleo,20137
|
|
18
18
|
edata/providers/redata.py,sha256=uvxO2TTtSNSfPRWHZifLjbaP7zAr6O7CZ1oRQFNbIhQ,3122
|
|
19
|
-
edata/services/bill_service.py,sha256=
|
|
20
|
-
edata/services/data_service.py,sha256=
|
|
19
|
+
edata/services/bill_service.py,sha256=xfz7TaJeS0Z2CEp72Fr7i3sXSoOK1hFabwXLvlU944I,14633
|
|
20
|
+
edata/services/data_service.py,sha256=dVzbWpvSg_936pdzpw9T0KyN7dc1Mhx-ut9S0rat0YY,17122
|
|
21
21
|
edata/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
edata/tests/test_datadis_connector.py,sha256=O3mjI5uIOgFz3PzQsApcG6J2yXd8rlEtj8By-9dsDbg,9270
|
|
23
23
|
edata/tests/test_redata_connector.py,sha256=SBvxsma5t-qvz2drxNisNM2P1STTurqEs1TSDgwaPJ8,508
|
|
24
24
|
edata/tests/test_services.py,sha256=xR_BxNttWzoEGXh02W6Iyy_QQ9odzlLXj7X9nVXd5Co,6889
|
|
25
|
-
e_data-2.0.
|
|
26
|
-
e_data-2.0.
|
|
27
|
-
e_data-2.0.
|
|
28
|
-
e_data-2.0.
|
|
25
|
+
e_data-2.0.1.dev128.dist-info/METADATA,sha256=zlK2VLSDeDzDtzWWRijqPJYB7WTuhllWq7xXJItFbSU,48084
|
|
26
|
+
e_data-2.0.1.dev128.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
27
|
+
e_data-2.0.1.dev128.dist-info/top_level.txt,sha256=Ez-fReWtUVTMcFuH0dzCnoKIgMBHgjLaBuba8QiKC7g,6
|
|
28
|
+
e_data-2.0.1.dev128.dist-info/RECORD,,
|
edata/core/utils.py
CHANGED
|
@@ -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 =
|
|
38
|
+
hdays = _es_holidays(dt.year)
|
|
26
39
|
hour = dt.hour
|
|
27
40
|
weekday = dt.weekday()
|
|
28
41
|
|
edata/database/controller.py
CHANGED
|
@@ -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
|
-
|
|
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")
|
edata/services/bill_service.py
CHANGED
|
@@ -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(
|
|
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]))
|
edata/services/data_service.py
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|