Skip to content
This repository was archived by the owner on Dec 24, 2024. It is now read-only.

Commit 49575cd

Browse files
authored
Merge pull request #361 from PainterQubits/main
Update to SQLAlchemy 2.0
2 parents aee1969 + b1e68de commit 49575cd

12 files changed

Lines changed: 148 additions & 151 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Changed
10+
* Updated SQLAlchemy from version 1.4 to 2.0
11+
912
## [0.15.8]
1013
### Changed
1114
* supporting python versions 3.8, 3.9, 3.10, 3.11

entropylab/pipeline/params/persistence/sqlalchemy/alembic/env.py

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
from logging.config import fileConfig
22

33
from alembic import context
4-
from sqlalchemy import engine_from_config
5-
from sqlalchemy import pool
64

75
from entropylab.pipeline.params.persistence.sqlalchemy.model import Base
86

@@ -59,24 +57,10 @@ def run_migrations_online() -> None:
5957
and associate a connection with the context.
6058
6159
"""
62-
connectable = config.attributes.get("connection", None)
63-
64-
if connectable is None:
65-
# only create Engine if we don't have a Connection
66-
# from the outside
67-
connectable = engine_from_config(
68-
config.get_section(config.config_ini_section),
69-
prefix="sqlalchemy.",
70-
poolclass=pool.NullPool,
71-
)
72-
73-
# when connectable is already a Connection object, calling
74-
# connect() gives us a *branched connection*.
75-
with connectable.connect() as connection:
76-
context.configure(connection=connection, target_metadata=target_metadata)
77-
78-
with context.begin_transaction():
79-
context.run_migrations()
60+
connection = config.attributes["connection"]
61+
context.configure(connection=connection, target_metadata=target_metadata)
62+
with context.begin_transaction():
63+
context.run_migrations()
8064

8165

8266
if context.is_offline_mode():

entropylab/pipeline/params/persistence/sqlalchemy/sqlalchemypersistence.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import uuid
3+
from uuid import UUID
34
from pathlib import Path
45
from typing import Optional, Set, List
56

@@ -8,7 +9,7 @@
89
from alembic.config import Config
910
from sqlalchemy import create_engine, text
1011
from sqlalchemy.engine import Connection
11-
from sqlalchemy.orm import sessionmaker
12+
from sqlalchemy.orm import sessionmaker, close_all_sessions
1213

1314
from entropylab.pipeline.api.errors import EntropyError
1415
from entropylab.pipeline.params.persistence.persistence import Persistence, Commit
@@ -17,7 +18,7 @@
1718
TempTable,
1819
)
1920

20-
TEMP_COMMIT_ID = "00000000-0000-0000-0000-000000000000"
21+
TEMP_COMMIT_ID = UUID("00000000-0000-0000-0000-000000000000")
2122

2223

2324
class SqlAlchemyPersistence(Persistence):
@@ -57,7 +58,7 @@ def _abs_path_to(rel_path: str) -> str:
5758
return os.path.join(source_dir, rel_path)
5859

5960
def close(self):
60-
self.__session_maker.close_all()
61+
close_all_sessions()
6162

6263
def get_commit(
6364
self, commit_id: Optional[str] = None, commit_num: Optional[int] = None
@@ -66,7 +67,7 @@ def get_commit(
6667
with self.__session_maker() as session:
6768
commit = (
6869
session.query(CommitTable)
69-
.filter(CommitTable.id == commit_id)
70+
.filter(CommitTable.id == UUID(commit_id))
7071
.one_or_none()
7172
)
7273
if commit:
@@ -108,15 +109,15 @@ def commit(
108109
# TODO: Perhaps create the timestamp here?
109110
self.stamp_dirty_params_with_commit(commit, dirty_keys)
110111
commit_table = CommitTable()
111-
commit_table.id = commit.id
112+
commit_table.id = UUID(commit.id)
112113
commit_table.timestamp = commit.timestamp
113114
commit_table.label = commit.label
114115
commit_table.params = commit.params
115116
commit_table.tags = commit.tags
116117
with self.__session_maker() as session:
117118
session.add(commit_table)
118119
session.commit()
119-
return commit_table.id
120+
return commit.id
120121

121122
@staticmethod
122123
def __generate_commit_id() -> str:

entropylab/pipeline/params/persistence/sqlalchemy/tests/test_sqlalchemypersistence.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,29 @@ def target(tmp_path) -> SqlAlchemyPersistence:
2323

2424

2525
def test_ctor_creates_schema(target):
26-
cursor = target.engine.execute("SELECT sql FROM sqlite_master WHERE type = 'table'")
27-
assert len(cursor.fetchall()) == 3
26+
with target.engine.connect() as connection:
27+
cursor = connection.execute(
28+
text("SELECT sql FROM sqlite_master WHERE type = 'table'")
29+
)
30+
assert len(cursor.fetchall()) == 3
2831

2932

3033
def test_ctor_stamps_head(target):
31-
cursor = target.engine.execute("SELECT version_num FROM alembic_version")
32-
assert cursor.first() == ("000c6a88457f",)
34+
with target.engine.connect() as connection:
35+
cursor = connection.execute(text("SELECT version_num FROM alembic_version"))
36+
assert cursor.first() == ("000c6a88457f",)
3337

3438

3539
""" get_commit """
3640

3741

3842
def test_get_commit_when_commit_id_exists_then_commit_is_returned(target):
3943
commit_id = "f74c808e-2388-4b0a-a051-17eb9eb14339"
40-
with target.engine.connect() as connection:
44+
with target.engine.begin() as connection:
4145
connection.execute(
4246
text(
4347
"INSERT INTO 'commit' VALUES "
44-
f"('{commit_id}', '{pd.Timestamp.now()}', 'bar', '0', '0');"
48+
f"('{UUID(commit_id).hex}', '{pd.Timestamp.now()}', 'bar', '0', '0');"
4549
)
4650
)
4751
actual = target.get_commit(commit_id)
@@ -50,20 +54,20 @@ def test_get_commit_when_commit_id_exists_then_commit_is_returned(target):
5054

5155
def test_get_commit_when_commit_id_does_not_exist_then_error_is_raised(target):
5256
with pytest.raises(EntropyError):
53-
target.get_commit("foo")
57+
target.get_commit("f74c808e-2388-4b0a-a051-17eb9eb14339")
5458

5559

5660
def test_get_commit_when_commit_num_exists_then_commit_is_returned(target):
5761
commit_id1 = "f74c808e-2388-4b0a-a051-17eb9eb11111"
5862
commit_id2 = "f74c808e-2388-4b0a-a051-17eb9eb22222"
5963
commit_id3 = "f74c808e-2388-4b0a-a051-17eb9eb33333"
60-
with target.engine.connect() as connection:
64+
with target.engine.begin() as connection:
6165
connection.execute(
6266
text(
6367
"INSERT INTO 'commit' VALUES "
64-
f"('{commit_id1}', '{pd.Timestamp.now()}', 'bar', '0', '0'),"
65-
f"('{commit_id2}', '{pd.Timestamp.now()}', 'bar', '0', '0'),"
66-
f"('{commit_id3}', '{pd.Timestamp.now()}', 'bar', '0', '0');"
68+
f"('{UUID(commit_id1).hex}', '{pd.Timestamp.now()}', 'bar', '0', '0'),"
69+
f"('{UUID(commit_id2).hex}', '{pd.Timestamp.now()}', 'bar', '0', '0'),"
70+
f"('{UUID(commit_id3).hex}', '{pd.Timestamp.now()}', 'bar', '0', '0');"
6771
)
6872
)
6973
actual = target.get_commit(commit_num=2)

entropylab/pipeline/results_backend/sqlalchemy/alembic/env.py

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
from logging.config import fileConfig
22

33
from alembic import context
4-
from sqlalchemy import engine_from_config
5-
from sqlalchemy import pool
64

75
# this is the Alembic Config object, which provides
86
# access to the values within the .ini file in use.
@@ -61,24 +59,10 @@ def run_migrations_online():
6159
https://alembic.sqlalchemy.org/en/latest/cookbook.html#connection-sharing
6260
6361
"""
64-
connectable = config.attributes.get("connection", None)
65-
66-
if connectable is None:
67-
# only create Engine if we don't have a Connection
68-
# from the outside
69-
connectable = engine_from_config(
70-
config.get_section(config.config_ini_section),
71-
prefix="sqlalchemy.",
72-
poolclass=pool.NullPool,
73-
)
74-
75-
# when connectable is already a Connection object, calling
76-
# connect() gives us a *branched connection*.
77-
with connectable.connect() as connection:
78-
context.configure(connection=connection, target_metadata=target_metadata)
79-
80-
with context.begin_transaction():
81-
context.run_migrations()
62+
connection = config.attributes["connection"]
63+
context.configure(connection=connection, target_metadata=target_metadata)
64+
with context.begin_transaction():
65+
context.run_migrations()
8266

8367

8468
if context.is_offline_mode():

entropylab/pipeline/results_backend/sqlalchemy/db.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
from datetime import datetime
2+
from contextlib import contextmanager
23
from typing import List, TypeVar, Optional, ContextManager, Iterable, Union, Any
34
from typing import Set
45
from warnings import warn
56

67
import jsonpickle
7-
import pandas as pd
88
from pandas import DataFrame
99
from plotly import graph_objects as go
10-
from sqlalchemy import desc
10+
from sqlalchemy import text, desc
1111
from sqlalchemy.exc import DBAPIError
1212
from sqlalchemy.orm import sessionmaker, Session
1313
from sqlalchemy.sql import Selectable
14-
from sqlalchemy.util.compat import contextmanager
1514

1615
from entropylab.components.instrument_driver import Function, Parameter
1716
from entropylab.components.lab_model import (
@@ -336,11 +335,12 @@ def __get_last_result_of_experiment_from_sqlalchemy(
336335
def custom_query(self, query: Union[str, Selectable]) -> DataFrame:
337336
with self._session_maker() as sess:
338337
if isinstance(query, str):
339-
selectable = query
338+
selectable = text(query)
340339
else:
341340
selectable = query.statement
342341

343-
return pd.read_sql(selectable, sess.bind)
342+
result = sess.execute(selectable)
343+
return DataFrame(result.all(), columns=result.keys())
344344

345345
def _execute_transaction(self, transaction):
346346
with self._session_maker() as sess:
@@ -350,7 +350,8 @@ def _execute_transaction(self, transaction):
350350

351351
@staticmethod
352352
def _query_pandas(query):
353-
return pd.read_sql(query.statement, query.session.bind)
353+
result = query.session.execute(query.statement)
354+
return DataFrame(result.all(), columns=result.keys())
354355

355356
@contextmanager
356357
def _session_maker(self) -> ContextManager[Session]:

entropylab/pipeline/results_backend/sqlalchemy/db_initializer.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import TypeVar, Type, Tuple
55

66
import sqlalchemy.engine
7-
from sqlalchemy import create_engine
7+
from sqlalchemy import create_engine, text
88
from sqlalchemy.orm import sessionmaker
99

1010
from entropylab.logger import logger
@@ -117,10 +117,11 @@ def _validate_path(path):
117117
)
118118

119119
def _db_is_empty(self) -> bool:
120-
cursor = self._engine.execute(
121-
"SELECT sql FROM sqlite_master WHERE type = 'table'"
122-
)
123-
return len(cursor.fetchall()) == 0
120+
with self._engine.connect() as connection:
121+
cursor = connection.execute(
122+
text("SELECT sql FROM sqlite_master WHERE type = 'table'")
123+
)
124+
return len(cursor.fetchall()) == 0
124125

125126

126127
class _DbUpgrader:

entropylab/pipeline/results_backend/sqlalchemy/model.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
Enum,
1919
Boolean,
2020
)
21-
from sqlalchemy.ext.declarative import declarative_base
22-
from sqlalchemy.orm import relationship
21+
from sqlalchemy.orm import declarative_base, relationship
2322

2423
from entropylab.logger import logger
2524
from entropylab.pipeline.api.data_reader import (

entropylab/pipeline/results_backend/sqlalchemy/tests/test_db_upgrader.py

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import shutil
33

44
import pytest
5-
from sqlalchemy import create_engine
5+
from sqlalchemy import create_engine, text
66

77
from entropylab import SqlAlchemyDB, RawResultData
88
from entropylab.conftest import _copy_template
@@ -76,9 +76,12 @@ def test_upgrade_db_when_initial_db_is_empty(initialized_project_dir_path):
7676
engine = create_engine(
7777
f"sqlite:///{initialized_project_dir_path}/{_ENTROPY_DIRNAME}/{_DB_FILENAME}"
7878
)
79-
cur = engine.execute("SELECT sql FROM sqlite_master WHERE name = 'Results'")
80-
res = cur.fetchone()
81-
cur.close()
79+
with engine.connect() as connection:
80+
cur = connection.execute(
81+
text("SELECT sql FROM sqlite_master WHERE name = 'Results'")
82+
)
83+
res = cur.fetchone()
84+
cur.close()
8285
assert "saved_in_hdf5" in res[0]
8386

8487

@@ -88,9 +91,12 @@ def test_upgrade_db_when_db_is_in_memory():
8891
# act
8992
target.upgrade_db()
9093
# assert
91-
cur = target._engine.execute("SELECT sql FROM sqlite_master WHERE name = 'Results'")
92-
res = cur.fetchone()
93-
cur.close()
94+
with target._engine.connect() as connection:
95+
cur = connection.execute(
96+
text("SELECT sql FROM sqlite_master WHERE name = 'Results'")
97+
)
98+
res = cur.fetchone()
99+
cur.close()
94100
assert "saved_in_hdf5" in res[0]
95101

96102

@@ -112,8 +118,9 @@ def test__migrate_results_to_hdf5(initialized_project_dir_path):
112118
)
113119
hdf5_results = storage.get_result_records()
114120
assert len(list(hdf5_results)) == 5
115-
cur = target._engine.execute("SELECT * FROM Results WHERE saved_in_hdf5 = 1")
116-
res = cur.all()
121+
with target._engine.connect() as connection:
122+
cur = connection.execute(text("SELECT * FROM Results WHERE saved_in_hdf5 = 1"))
123+
res = cur.all()
117124
assert len(res) == 5
118125

119126

@@ -135,10 +142,11 @@ def test__migrate_metadata_to_hdf5(initialized_project_dir_path):
135142
)
136143
hdf5_metadata = storage.get_metadata_records()
137144
assert len(list(hdf5_metadata)) == 5
138-
cur = target._engine.execute(
139-
"SELECT * FROM ExperimentMetadata WHERE saved_in_hdf5 = 1"
140-
)
141-
res = cur.all()
145+
with target._engine.connect() as connection:
146+
cur = connection.execute(
147+
text("SELECT * FROM ExperimentMetadata WHERE saved_in_hdf5 = 1")
148+
)
149+
res = cur.all()
142150
assert len(res) == 5
143151

144152

@@ -205,14 +213,16 @@ def test_upgrade_db_deletes_results_and_metadata_from_sqlite(
205213
# act
206214
target.upgrade_db()
207215
# assert for results
208-
cur = target._engine.execute("SELECT * FROM Results WHERE saved_in_hdf5 = 1")
209-
res = cur.all()
216+
with target._engine.connect() as connection:
217+
cur = connection.execute(text("SELECT * FROM Results WHERE saved_in_hdf5 = 1"))
218+
res = cur.all()
210219
assert len(res) == 0
211220
# assert for metadata
212-
cur = target._engine.execute(
213-
"SELECT * FROM ExperimentMetadata WHERE saved_in_hdf5 = 1"
214-
)
215-
res = cur.all()
221+
with target._engine.connect() as connection:
222+
cur = connection.execute(
223+
text("SELECT * FROM ExperimentMetadata WHERE saved_in_hdf5 = 1")
224+
)
225+
res = cur.all()
216226
assert len(res) == 0
217227

218228

@@ -230,8 +240,11 @@ def test_upgrade_db_adds_favorite_column_to_experiments_table(
230240
target = _DbUpgrader(initialized_project_dir_path)
231241
# act
232242
target.upgrade_db()
233-
cur = target._engine.execute(
234-
"SELECT COUNT(*) FROM pragma_table_info('Experiments') WHERE name='favorite'; "
235-
)
236-
res = cur.all()
243+
with target._engine.connect() as connection:
244+
cur = connection.execute(
245+
text(
246+
"SELECT COUNT(*) FROM pragma_table_info('Experiments') WHERE name='favorite'; "
247+
)
248+
)
249+
res = cur.all()
237250
assert res[0][0] == 1

0 commit comments

Comments
 (0)