Source code for mmusicc.database.metadb
import logging
import pathlib
from sqlalchemy import MetaData, Table, Column, String, PickleType
from sqlalchemy import create_engine
from mmusicc.util.allocationmap import list_tags
[docs]class MetaDB:
"""Object representing a database connection in Metadata,
holds connection parameters and inserts and reads data. When writing to the
Database, always all data is written, while you can load only select which
tags to load. See https://docs.sqlalchemy.org/en/13/core/engines.html for
Database Url examples.
There will be two tables. One only containing strings and string
representation of objects and one containing the pickled strings or
objects. This way all object types can be restored at loading while at the
same time the database can be search with standard database queries.
Args:
database_url (str): database url following RFC-1738*. If the sting,
does not contain '://', a filepath for a sqlite database is
assumed.
"""
def __init__(self, database_url):
if "://" not in database_url:
# it is assumed the database url is filepath and therefore SQLite
database_url = pathlib.Path(database_url).expanduser().resolve()
database_url = "sqlite:///" + str(database_url)
self._database_url = database_url
self._engine = create_engine(self._database_url)
if not list_tags:
logging.warning("no tags found! Is project initialized")
self._create_table(list_tags)
@property
def database_url(self):
"""Get URL of connected database."""
return self._engine.url
def _create_table(self, list_keys):
"""Create a Tables in Database if it does not already exists,
where each column represents a tag (=key in list_keys).
Args:
list keys (list of str): list of tags in Metadata
"""
self.list_keys = list_keys
sql_metadata = MetaData()
self.tags = Table(
"tags", sql_metadata, Column("_primary_key", String(200), primary_key=True)
)
self.pickle_tags = Table(
"pickle_tags",
sql_metadata,
Column("_primary_key", String(200), primary_key=True),
)
for key in list_keys:
self.tags.append_column(Column(key, String(100)))
self.pickle_tags.append_column(Column(key, PickleType()))
self.tags.create(self._engine, checkfirst=True)
self.pickle_tags.create(self._engine, checkfirst=True)
[docs] def insert_meta(self, dict_data, primary_key):
"""Inserts a row into the database, with the values from the dict.
Args:
dict_data (dict): metadata dictionary (`Dict[str, object]`) to be
written.
primary_key (str): unique identifier of the item which data is to
be written. Metadata uses the absolute filepath.
"""
dict_meta_pickle = dict_data.copy()
dict_meta_pickle["_primary_key"] = primary_key
dict_meta = dict()
dict_meta["_primary_key"] = primary_key
for key in list(dict_meta_pickle):
dict_meta[key] = str(dict_meta_pickle[key])
with self._engine.connect() as conn:
conn.execute(self.tags.insert().values(dict_meta))
conn.execute(self.pickle_tags.insert().values(dict_meta_pickle))
[docs] def read_meta(self, primary_key, tags=None):
"""returns values of a row with given primary key as metadata dict.
Args:
primary_key (str): unique identifier of the item which data
is to read (eg. Filepath).
tags (list of str): list of strings to be read, reads all if
None. Defaults to None.
Returns:
dict_data (dict<str:obj>): metadata dictionary
"""
with self._engine.connect() as conn:
foo_col = Column("_primary_key")
result = conn.execute(
self.pickle_tags.select().where(foo_col == primary_key)
).first()
if result:
dict_data_tmp = dict(result)
dict_data_tmp.pop("_primary_key")
if tags:
for key in list(dict_data_tmp):
if key not in tags:
dict_data_tmp.pop(key)
return dict_data_tmp
return None
[docs] def get_list_of_primary_keys(self):
"""reads the primary keys from database and returns them.
Returns:
list of str: list of primary key strings in database
"""
with self._engine.connect() as conn:
result = list(conn.execute("SELECT _primary_key FROM tags"))
return [s[0] for s in result]