e-data 2.0.2.dev135__py3-none-any.whl → 2.0.2.dev136__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: e-data
3
- Version: 2.0.2.dev135
3
+ Version: 2.0.2.dev136
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
@@ -1,4 +1,4 @@
1
- e_data-2.0.2.dev135.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
1
+ e_data-2.0.2.dev136.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=CsBYDggt0grkLb4cebrckOvJyHr9PaGTnlWqT4yjuFo,5215
4
4
  edata/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -6,7 +6,7 @@ edata/core/completion.py,sha256=A0ekfu7eCjaLblUYO5DrvaWuDPAO9k1buzy2qdfjdNc,3914
6
6
  edata/core/const.py,sha256=X9J2f7lt9jXJnghqz0JFi-J7USa3xlYvbuiki70eHNE,43
7
7
  edata/core/utils.py,sha256=uQX8GmNjuhof56jiXXoV-Fk-d590OuIoBJbM9ymZyV8,3194
8
8
  edata/database/__init__.py,sha256=HP_pDbGLzeMY9Ufm_Dps5cxzW_3YTonT8w8dstG558c,46
9
- edata/database/controller.py,sha256=xKBNwv1PYfEkuaunz3Qh5zLAPI_6sDkxiohUGEP0jN4,22831
9
+ edata/database/controller.py,sha256=bI3cE1Q4SK9U1Eep_61EhlmyVjgYH87F5lfBLhwSpmg,23211
10
10
  edata/database/models.py,sha256=tSkxf2OKgT1m2F1td3j3GtYcboUtSuYbhnHWQtpvJuY,5525
11
11
  edata/database/queries.py,sha256=XziDSG2V-PVFmvRkj8UrEvgrc9Qc-ShhnxgsPHIsyik,8304
12
12
  edata/database/utils.py,sha256=V-FJfyAh8YJBGHB85BbWtG3Ilq09pM9Gl-L86TffB4g,1062
@@ -24,14 +24,14 @@ edata/services/bill_service.py,sha256=KAM78u7TpoVF-KTlB7bqj2Kp-rJERiPPoGTJCc-7rM
24
24
  edata/services/data_service.py,sha256=JJCdivjrwNt3aLhz3uMBWv06y-EL7UiK2G5EBr97l_8,19374
25
25
  edata/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  edata/tests/test_completion.py,sha256=-iyVt-juU3U1HqMuLUJXJthil2-n_4ddWiJq2GwnXTo,3551
27
- edata/tests/test_controller.py,sha256=7sqDR3_Vrxy4S084Eh6VYkLQNuhfYAYzJFCtMSCudd8,2815
27
+ edata/tests/test_controller.py,sha256=E1Blvel2dtS8CiyxErP4V_wpzWlEcxTYA1gW1f8-Gg4,3449
28
28
  edata/tests/test_datadis_connector.py,sha256=8Xet2M2eG-kMhiRDTCSH0R5nYO2oIpQTPOuTZKMKoLo,12538
29
29
  edata/tests/test_incremental.py,sha256=dn1LRgjBi2HuNYELPKDVdZQlS01hk4lsP9L-vyvtEHo,3479
30
30
  edata/tests/test_migrations.py,sha256=3Io2nokZVdv2xU9fCZCMIvwAwPVtGc0UivClSouP3pw,6418
31
31
  edata/tests/test_redata_connector.py,sha256=SBvxsma5t-qvz2drxNisNM2P1STTurqEs1TSDgwaPJ8,508
32
32
  edata/tests/test_services.py,sha256=Ya5cbADHj1Qsffb_IuMeDD0BViz8nqrgwazUWK0-hRc,8297
33
33
  edata/tests/test_utils.py,sha256=h6yBzT7j3eA42zbXJ-AK9lXcZkjCMf16wnob1oqGvUE,2307
34
- e_data-2.0.2.dev135.dist-info/METADATA,sha256=FWIkMXs3931Au8RSJa5QzAEaFUXJzIboaKHzLt7U0x0,48087
35
- e_data-2.0.2.dev135.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
36
- e_data-2.0.2.dev135.dist-info/top_level.txt,sha256=Ez-fReWtUVTMcFuH0dzCnoKIgMBHgjLaBuba8QiKC7g,6
37
- e_data-2.0.2.dev135.dist-info/RECORD,,
34
+ e_data-2.0.2.dev136.dist-info/METADATA,sha256=YZbWL0ZGXBHvfFBODBeXBwD8CcQHH5VXqYqY_NV42GE,48087
35
+ e_data-2.0.2.dev136.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
36
+ e_data-2.0.2.dev136.dist-info/top_level.txt,sha256=Ez-fReWtUVTMcFuH0dzCnoKIgMBHgjLaBuba8QiKC7g,6
37
+ e_data-2.0.2.dev136.dist-info/RECORD,,
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import logging
2
3
  import os
3
4
  import typing
@@ -93,6 +94,7 @@ class EdataDB:
93
94
  cls._engine = create_async_engine(db_url, future=True)
94
95
  event.listen(cls._engine.sync_engine, "connect", _set_sqlite_pragmas)
95
96
  cls._instance._tables_initialized = False
97
+ cls._instance._tables_lock = asyncio.Lock()
96
98
  elif db_url != cls._db_url:
97
99
  raise ValueError("EdataDB already initialized with a different db_url")
98
100
  return cls._instance
@@ -108,7 +110,12 @@ class EdataDB:
108
110
 
109
111
  if self._tables_initialized:
110
112
  return
111
- if self.engine:
113
+ # concurrent first calls (e.g. several websocket requests right after
114
+ # startup) would otherwise all see the index missing and race to create
115
+ # it, failing with "index ... already exists"
116
+ async with self._tables_lock:
117
+ if self._tables_initialized or not self.engine:
118
+ return
112
119
  async with self.engine.begin() as conn:
113
120
  await conn.run_sync(SQLModel.metadata.create_all)
114
121
  await conn.run_sync(_create_missing_indexes)
@@ -1,5 +1,6 @@
1
- """Bulk upsert tests for the database controller."""
1
+ """Tests for the database controller."""
2
2
 
3
+ import asyncio
3
4
  from collections.abc import AsyncIterator
4
5
  from datetime import datetime, timedelta
5
6
 
@@ -89,3 +90,19 @@ async def test_add_bill_list_applies_overrides(db: EdataDB) -> None:
89
90
  stored = await db.list_bill(CUPS, "hour")
90
91
  assert len(stored) == 3
91
92
  assert all(x.complete and x.confhash == "hash-b" for x in stored)
93
+
94
+
95
+ @pytest.mark.asyncio
96
+ async def test_concurrent_first_calls_do_not_race_index_creation(db: EdataDB) -> None:
97
+ # simulate a database created before the index existed, opened fresh
98
+ async with db.engine.begin() as conn:
99
+ await conn.exec_driver_sql("DROP INDEX ix_energy_cups_datetime")
100
+ db._tables_initialized = False
101
+
102
+ await asyncio.gather(*(db.get_last_energy(CUPS) for _ in range(10)))
103
+
104
+ async with db.engine.connect() as conn:
105
+ result = await conn.exec_driver_sql(
106
+ "SELECT name FROM sqlite_master WHERE name='ix_energy_cups_datetime'"
107
+ )
108
+ assert result.first() is not None