mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
fix(performance): lazily load expensive modules within aurweb.db
Closes #374 Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
0e82916b0a
commit
913ce8a4f0
1 changed files with 47 additions and 34 deletions
81
aurweb/db.py
81
aurweb/db.py
|
@ -1,34 +1,15 @@
|
||||||
import functools
|
# Supported database drivers.
|
||||||
import hashlib
|
|
||||||
import math
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
from typing import Iterable, NewType
|
|
||||||
|
|
||||||
import sqlalchemy
|
|
||||||
|
|
||||||
from sqlalchemy import create_engine, event
|
|
||||||
from sqlalchemy.engine.base import Engine
|
|
||||||
from sqlalchemy.engine.url import URL
|
|
||||||
from sqlalchemy.orm import Query, Session, SessionTransaction, scoped_session, sessionmaker
|
|
||||||
|
|
||||||
import aurweb.config
|
|
||||||
import aurweb.util
|
|
||||||
|
|
||||||
DRIVERS = {
|
DRIVERS = {
|
||||||
"mysql": "mysql+mysqldb"
|
"mysql": "mysql+mysqldb"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Some types we don't get access to in this module.
|
|
||||||
Base = NewType("Base", "aurweb.models.declarative_base.Base")
|
|
||||||
|
|
||||||
|
|
||||||
def make_random_value(table: str, column: str, length: int):
|
def make_random_value(table: str, column: str, length: int):
|
||||||
""" Generate a unique, random value for a string column in a table.
|
""" Generate a unique, random value for a string column in a table.
|
||||||
|
|
||||||
:return: A unique string that is not in the database
|
:return: A unique string that is not in the database
|
||||||
"""
|
"""
|
||||||
|
import aurweb.util
|
||||||
string = aurweb.util.make_random_string(length)
|
string = aurweb.util.make_random_string(length)
|
||||||
while query(table).filter(column == string).first():
|
while query(table).filter(column == string).first():
|
||||||
string = aurweb.util.make_random_string(length)
|
string = aurweb.util.make_random_string(length)
|
||||||
|
@ -52,6 +33,10 @@ def test_name() -> str:
|
||||||
|
|
||||||
:return: Unhashed database name
|
:return: Unhashed database name
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
import aurweb.config
|
||||||
|
|
||||||
db = os.environ.get("PYTEST_CURRENT_TEST",
|
db = os.environ.get("PYTEST_CURRENT_TEST",
|
||||||
aurweb.config.get("database", "name"))
|
aurweb.config.get("database", "name"))
|
||||||
return db.split(":")[0]
|
return db.split(":")[0]
|
||||||
|
@ -70,7 +55,10 @@ def name() -> str:
|
||||||
dbname = test_name()
|
dbname = test_name()
|
||||||
if not dbname.startswith("test/"):
|
if not dbname.startswith("test/"):
|
||||||
return dbname
|
return dbname
|
||||||
|
|
||||||
|
import hashlib
|
||||||
sha1 = hashlib.sha1(dbname.encode()).hexdigest()
|
sha1 = hashlib.sha1(dbname.encode()).hexdigest()
|
||||||
|
|
||||||
return "db" + sha1
|
return "db" + sha1
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,12 +66,13 @@ def name() -> str:
|
||||||
_sessions = dict()
|
_sessions = dict()
|
||||||
|
|
||||||
|
|
||||||
def get_session(engine: Engine = None) -> Session:
|
def get_session(engine=None):
|
||||||
""" Return aurweb.db's global session. """
|
""" Return aurweb.db's global session. """
|
||||||
dbname = name()
|
dbname = name()
|
||||||
|
|
||||||
global _sessions
|
global _sessions
|
||||||
if dbname not in _sessions:
|
if dbname not in _sessions:
|
||||||
|
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||||
|
|
||||||
if not engine: # pragma: no cover
|
if not engine: # pragma: no cover
|
||||||
engine = get_engine()
|
engine = get_engine()
|
||||||
|
@ -106,13 +95,17 @@ def pop_session(dbname: str) -> None:
|
||||||
_sessions.pop(dbname)
|
_sessions.pop(dbname)
|
||||||
|
|
||||||
|
|
||||||
def refresh(model: Base) -> Base:
|
def refresh(model):
|
||||||
""" Refresh the session's knowledge of `model`. """
|
"""
|
||||||
|
Refresh the session's knowledge of `model`.
|
||||||
|
|
||||||
|
:returns: Passed in `model`
|
||||||
|
"""
|
||||||
get_session().refresh(model)
|
get_session().refresh(model)
|
||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
def query(Model: Base, *args, **kwargs) -> Query:
|
def query(Model, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Perform an ORM query against the database session.
|
Perform an ORM query against the database session.
|
||||||
|
|
||||||
|
@ -124,7 +117,7 @@ def query(Model: Base, *args, **kwargs) -> Query:
|
||||||
return get_session().query(Model).filter(*args, **kwargs)
|
return get_session().query(Model).filter(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def create(Model: Base, *args, **kwargs) -> Base:
|
def create(Model, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Create a record and add() it to the database session.
|
Create a record and add() it to the database session.
|
||||||
|
|
||||||
|
@ -135,7 +128,7 @@ def create(Model: Base, *args, **kwargs) -> Base:
|
||||||
return add(instance)
|
return add(instance)
|
||||||
|
|
||||||
|
|
||||||
def delete(model: Base) -> None:
|
def delete(model) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a set of records found by Query.filter(*args, **kwargs).
|
Delete a set of records found by Query.filter(*args, **kwargs).
|
||||||
|
|
||||||
|
@ -144,8 +137,9 @@ def delete(model: Base) -> None:
|
||||||
get_session().delete(model)
|
get_session().delete(model)
|
||||||
|
|
||||||
|
|
||||||
def delete_all(iterable: Iterable) -> None:
|
def delete_all(iterable) -> None:
|
||||||
""" Delete each instance found in `iterable`. """
|
""" Delete each instance found in `iterable`. """
|
||||||
|
import aurweb.util
|
||||||
session_ = get_session()
|
session_ = get_session()
|
||||||
aurweb.util.apply_all(iterable, session_.delete)
|
aurweb.util.apply_all(iterable, session_.delete)
|
||||||
|
|
||||||
|
@ -155,23 +149,29 @@ def rollback() -> None:
|
||||||
get_session().rollback()
|
get_session().rollback()
|
||||||
|
|
||||||
|
|
||||||
def add(model: Base) -> Base:
|
def add(model):
|
||||||
""" Add `model` to the database session. """
|
""" Add `model` to the database session. """
|
||||||
get_session().add(model)
|
get_session().add(model)
|
||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
def begin() -> SessionTransaction:
|
def begin():
|
||||||
""" Begin an SQLAlchemy SessionTransaction. """
|
""" Begin an SQLAlchemy SessionTransaction. """
|
||||||
return get_session().begin()
|
return get_session().begin()
|
||||||
|
|
||||||
|
|
||||||
def get_sqlalchemy_url() -> URL:
|
def get_sqlalchemy_url():
|
||||||
"""
|
"""
|
||||||
Build an SQLAlchemy URL for use with create_engine.
|
Build an SQLAlchemy URL for use with create_engine.
|
||||||
|
|
||||||
:return: sqlalchemy.engine.url.URL
|
:return: sqlalchemy.engine.url.URL
|
||||||
"""
|
"""
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
from sqlalchemy.engine.url import URL
|
||||||
|
|
||||||
|
import aurweb.config
|
||||||
|
|
||||||
constructor = URL
|
constructor = URL
|
||||||
|
|
||||||
parts = sqlalchemy.__version__.split('.')
|
parts = sqlalchemy.__version__.split('.')
|
||||||
|
@ -209,13 +209,17 @@ def get_sqlalchemy_url() -> URL:
|
||||||
|
|
||||||
def sqlite_regexp(regex, item) -> bool: # pragma: no cover
|
def sqlite_regexp(regex, item) -> bool: # pragma: no cover
|
||||||
""" Method which mimics SQL's REGEXP for SQLite. """
|
""" Method which mimics SQL's REGEXP for SQLite. """
|
||||||
|
import re
|
||||||
return bool(re.search(regex, str(item)))
|
return bool(re.search(regex, str(item)))
|
||||||
|
|
||||||
|
|
||||||
def setup_sqlite(engine: Engine) -> None: # pragma: no cover
|
def setup_sqlite(engine) -> None: # pragma: no cover
|
||||||
""" Perform setup for an SQLite engine. """
|
""" Perform setup for an SQLite engine. """
|
||||||
|
from sqlalchemy import event
|
||||||
|
|
||||||
@event.listens_for(engine, "connect")
|
@event.listens_for(engine, "connect")
|
||||||
def do_begin(conn, record):
|
def do_begin(conn, record):
|
||||||
|
import functools
|
||||||
create_deterministic_function = functools.partial(
|
create_deterministic_function = functools.partial(
|
||||||
conn.create_function,
|
conn.create_function,
|
||||||
deterministic=True
|
deterministic=True
|
||||||
|
@ -227,7 +231,7 @@ def setup_sqlite(engine: Engine) -> None: # pragma: no cover
|
||||||
_engines = dict()
|
_engines = dict()
|
||||||
|
|
||||||
|
|
||||||
def get_engine(dbname: str = None, echo: bool = False) -> Engine:
|
def get_engine(dbname: str = None, echo: bool = False):
|
||||||
"""
|
"""
|
||||||
Return the SQLAlchemy engine for `dbname`.
|
Return the SQLAlchemy engine for `dbname`.
|
||||||
|
|
||||||
|
@ -238,6 +242,8 @@ def get_engine(dbname: str = None, echo: bool = False) -> Engine:
|
||||||
:param echo: Flag passed through to sqlalchemy.create_engine
|
:param echo: Flag passed through to sqlalchemy.create_engine
|
||||||
:return: SQLAlchemy Engine instance
|
:return: SQLAlchemy Engine instance
|
||||||
"""
|
"""
|
||||||
|
import aurweb.config
|
||||||
|
|
||||||
if not dbname:
|
if not dbname:
|
||||||
dbname = name()
|
dbname = name()
|
||||||
|
|
||||||
|
@ -254,6 +260,7 @@ def get_engine(dbname: str = None, echo: bool = False) -> Engine:
|
||||||
"echo": echo,
|
"echo": echo,
|
||||||
"connect_args": connect_args
|
"connect_args": connect_args
|
||||||
}
|
}
|
||||||
|
from sqlalchemy import create_engine
|
||||||
_engines[dbname] = create_engine(get_sqlalchemy_url(), **kwargs)
|
_engines[dbname] = create_engine(get_sqlalchemy_url(), **kwargs)
|
||||||
|
|
||||||
if is_sqlite: # pragma: no cover
|
if is_sqlite: # pragma: no cover
|
||||||
|
@ -301,7 +308,10 @@ class ConnectionExecutor:
|
||||||
_conn = None
|
_conn = None
|
||||||
_paramstyle = None
|
_paramstyle = None
|
||||||
|
|
||||||
def __init__(self, conn, backend=aurweb.config.get("database", "backend")):
|
def __init__(self, conn, backend=None):
|
||||||
|
import aurweb.config
|
||||||
|
|
||||||
|
backend = backend or aurweb.config.get("database", "backend")
|
||||||
self._conn = conn
|
self._conn = conn
|
||||||
if backend == "mysql":
|
if backend == "mysql":
|
||||||
self._paramstyle = "format"
|
self._paramstyle = "format"
|
||||||
|
@ -339,6 +349,7 @@ class Connection:
|
||||||
_conn = None
|
_conn = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
import aurweb.config
|
||||||
aur_db_backend = aurweb.config.get('database', 'backend')
|
aur_db_backend = aurweb.config.get('database', 'backend')
|
||||||
|
|
||||||
if aur_db_backend == 'mysql':
|
if aur_db_backend == 'mysql':
|
||||||
|
@ -357,7 +368,9 @@ class Connection:
|
||||||
elif aur_db_backend == 'sqlite': # pragma: no cover
|
elif aur_db_backend == 'sqlite': # pragma: no cover
|
||||||
# TODO: SQLite support has been removed in FastAPI. It remains
|
# TODO: SQLite support has been removed in FastAPI. It remains
|
||||||
# here to fund its support for PHP until it is removed.
|
# here to fund its support for PHP until it is removed.
|
||||||
|
import math
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
aur_db_name = aurweb.config.get('database', 'name')
|
aur_db_name = aurweb.config.get('database', 'name')
|
||||||
self._conn = sqlite3.connect(aur_db_name)
|
self._conn = sqlite3.connect(aur_db_name)
|
||||||
self._conn.create_function("POWER", 2, math.pow)
|
self._conn.create_function("POWER", 2, math.pow)
|
||||||
|
|
Loading…
Add table
Reference in a new issue