Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for SQLAlchemy 2.0 #1693

Merged
merged 1 commit into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ dependencies = [
"whoosh >= 2.7.0", # needed for indexed search
"pdfminer.six", # pdf -> text/plain conversion
"passlib >= 1.6.0", # strong password hashing (1.6 needed for consteq)
"sqlalchemy < 2.0", # used by sqla store
"sqlalchemy >= 2.0", # used by sqla store
"XStatic >= 0.0.2", # support for static file pypi packages
"XStatic-Bootstrap == 3.1.1.2",
"XStatic-Font-Awesome >= 6.2.1.0",
Expand Down
13 changes: 13 additions & 0 deletions src/moin/storage/backends/_tests/test_stores.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from moin.storage.stores.memory import FileStore as MemoryFileStore
from moin.storage.stores.fs import BytesStore as FSBytesStore
from moin.storage.stores.fs import FileStore as FSFileStore
from moin.storage.stores.sqla import BytesStore as SQLABytesStore
from moin.storage.stores.sqla import FileStore as SQLAFileStore


class TestMemoryBackend(MutableBackendTestBase):
Expand All @@ -40,3 +42,14 @@ def setup_method(self, method):
self.be = MutableBackend(meta_store, data_store)
self.be.create()
self.be.open()


class TestSQLABackend(MutableBackendTestBase):
def setup_method(self, method):
meta_path = tempfile.mktemp()
data_path = tempfile.mktemp()
meta_store = SQLABytesStore(f"sqlite:///{meta_path}")
data_store = SQLAFileStore(f"sqlite:///{data_path}")
self.be = MutableBackend(meta_store, data_store)
self.be.create()
self.be.open()
55 changes: 32 additions & 23 deletions src/moin/storage/stores/sqla.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def __init__(self, db_uri=None, table_name="store", verbose=False):
self.table_name = table_name
if db_uri.startswith("sqlite:///"):
db_path = os.path.dirname(self.db_uri.split("sqlite:///")[1])
if not os.path.exists(db_path):
if db_path and not os.path.exists(db_path):
os.makedirs(db_path)

def open(self):
Expand All @@ -65,11 +65,10 @@ def open(self):
else:
self.engine = create_engine(db_uri, echo=self.verbose, echo_pool=self.verbose)

metadata = MetaData()
metadata.bind = self.engine
self.metadata = MetaData()
self.table = Table(
self.table_name,
metadata,
self.metadata,
Column("key", String(KEY_LEN), primary_key=True),
Column("value", LargeBinary(VALUE_LEN)),
)
Expand All @@ -80,38 +79,48 @@ def close(self):

def create(self):
self.open()
self.table.create()
with self.engine.connect() as conn:
with conn.begin():
self.metadata.create_all(conn)
self.close()

def destroy(self):
self.open()
self.table.drop()
with self.engine.connect() as conn:
with conn.begin():
self.metadata.drop_all(conn)
self.close()

def __iter__(self):
rows = select([self.table.c.key]).execute().fetchall()
for row in rows:
yield row[0]
with self.engine.connect() as conn:
rows = conn.execute(select(self.table.c.key))
for row in rows:
yield row[0]

def __delitem__(self, key):
self.table.delete().where(self.table.c.key == key).execute()
with self.engine.connect() as conn:
with conn.begin():
conn.execute(self.table.delete().where(self.table.c.key == key))

def __getitem__(self, key):
value = select([self.table.c.value], self.table.c.key == key).execute().fetchone()
if value is not None:
return value[0]
else:
raise KeyError(key)
with self.engine.connect() as conn:
value = conn.execute(select(self.table.c.value).where(self.table.c.key == key)).fetchone()
if value is not None:
return value[0]
else:
raise KeyError(key)

def __setitem__(self, key, value):
try:
self.table.insert().execute(key=key, value=value)
except IntegrityError:
if NAMESPACE_USERPROFILES in self.db_uri:
# userprofiles namespace does support revisions so we update existing row
self.table.update().execute(key=key, value=value)
else:
raise
with self.engine.connect() as conn:
with conn.begin():
try:
conn.execute(self.table.insert().values(key=key, value=value))
except IntegrityError:
if NAMESPACE_USERPROFILES in self.db_uri:
# userprofiles namespace does support revisions so we update existing row
conn.execute(self.table.update().values(key=key, value=value))
else:
raise


class FileStore(FileMutableStoreMixin, BytesStore, FileMutableStoreBase):
Expand Down