From e12c9059d7344f1538bfea6db8daed0ac1676ed6 Mon Sep 17 00:00:00 2001 From: Emily Frost Date: Sat, 23 May 2020 23:51:55 -0500 Subject: [PATCH] Added metadata relationships to ORM classes. This file is feature-complete! --- lark | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++- metadatadb.py | 28 ++++++++++++++++++----- 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/lark b/lark index 67bc3b8..c86b454 100755 --- a/lark +++ b/lark @@ -101,7 +101,7 @@ if action_object == 'platform': platform_name = sys.argv[4] platform_data = metadatadb.Platform(shortcode=platform_shortcode, - fullname=platform_name) + fullname=platform_name) with metadatadb.get_db_session() as session: session.add(platform_data) @@ -132,5 +132,66 @@ if action_object == 'platform': with metadatadb.get_db_session() as session: print(metadatadb.search(session, metadatadb.Platform)) + +elif action_object == 'release-group': + if action == 'add': + properties = _kwargs_parse(sys.argv[3:]) + release_group = metadatadb.ReleaseGroup(name=properties['name']) + + with metadatadb.get_db_session() as session: + if properties['platform']: + platform = metadatadb.search(session, metadatadb.Platform, + shortcode=properties['platform'])[0] + release_group.platform = platform + session.add(release_group) + + if action == 'list': + # TODO: Filter support is exclusively limited to SQLAlchemy's filter.ilike function. Figure + # out a good way to include other filters. + print('Listing release groups.') + filters = _kwargs_parse(sys.argv[3:]) + with metadatadb.get_db_session() as session: + print(metadatadb.search(session, metadatadb.ReleaseGroup, **filters)) + + elif action == 'remove': + constraints = sys.argv[3:] + filters = _kwargs_parse(sys.argv[3:]) + + with metadatadb.get_db_session() as session: + release_groups = metadatadb.search(session, metadatadb.ReleaseGroup, **filters) + for release_group in release_groups: + print('Removing %s.' % release_group.name) + session.delete(release_group) + +elif action_object == 'image': + if action == 'add': + properties = _kwargs_parse(sys.argv[3:]) + image = metadatadb.Image(filename=properties['filename'], + sha1sum=properties['sha1sum']) + + with metadatadb.get_db_session() as session: + if properties['release-group']: + release_group = metadatadb.search(session, metadatadb.ReleaseGroup, + name=properties['release-group'])[0] + image.release_group = release_group + session.add(image) + + if action == 'list': + # TODO: Filter support is exclusively limited to SQLAlchemy's filter.ilike function. Figure + # out a good way to include other filters. + print('Listing release groups.') + filters = _kwargs_parse(sys.argv[3:]) + with metadatadb.get_db_session() as session: + print(metadatadb.search(session, metadatadb.Image, **filters)) + + elif action == 'remove': + constraints = sys.argv[3:] + filters = _kwargs_parse(sys.argv[3:]) + + with metadatadb.get_db_session() as session: + release_groups = metadatadb.search(session, metadatadb.Image, **filters) + for image in release_groups: + print('Removing %s.' % image.name) + session.delete(image) else: print('Unknown object.') diff --git a/metadatadb.py b/metadatadb.py index 2e92f1f..304ef9d 100644 --- a/metadatadb.py +++ b/metadatadb.py @@ -4,6 +4,7 @@ metadatadb.py Everything needed to interact with the metadata database; namely SQL ORM objects, a context generator for the actual database, and a handful of convenience functions. ''' +# TODO: Rename file to metadata.py import contextlib import collections import hashlib @@ -18,6 +19,7 @@ HASH_CHUNK_SIZE = 10485760 # 10mb _db_session_maker = sqlalchemy.orm.sessionmaker() _engine = None +# TODO: Support DAT credit, DAT filenames, and checking DAT completeness. DatData = collections.namedtuple('DatData', 'UUID, name, website, version, image_list') @@ -26,6 +28,7 @@ def _uuidgen(): _SQLBase = sqlalchemy.ext.declarative.declarative_base() +# TODO: Rename Image concept to "Release". class Image(_SQLBase): '''SQLAlchemy ORM class for ROM image metadata.''' # TODO: Split filenames into more meaningful metadata. @@ -35,12 +38,14 @@ class Image(_SQLBase): uuid = sqlalchemy.Column(sqlalchemy.String, nullable=False, default=_uuidgen) sha1sum = sqlalchemy.Column(sqlalchemy.String, unique=True, nullable=False) filename = sqlalchemy.Column(sqlalchemy.String, unique=True, nullable=False) + release_group_id = sqlalchemy.Column(sqlalchemy.Integer, + sqlalchemy.ForeignKey('release_groups.id')) - # TODO: Add many-to-one relationship to release groups. + release_group = sqlalchemy.orm.relationship('ReleaseGroup', back_populates='images') def __repr__(self): - return 'ROM Image: id: %s, uuid: %s, sha1sum: %s, filename: %s' % (self.id, self.uuid, - self.sha1sum, self.filename) + return 'ROM Image: id: %s, uuid: %s, sha1sum: %s, filename: %s, release-group: %s' % ( + self.id, self.uuid, self.sha1sum, self.filename, self.release_group.name) class ReleaseGroup(_SQLBase): '''SQLAlchemy ORM class for release group metadata.''' @@ -49,11 +54,14 @@ class ReleaseGroup(_SQLBase): primary_key=True) uuid = sqlalchemy.Column(sqlalchemy.String, nullable=False, default=_uuidgen) name = sqlalchemy.Column(sqlalchemy.String, unique=True, nullable=False) - # TODO: Add many-to-one relationship to platforms. + platform_id = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey('platforms.id')) + + platform = sqlalchemy.orm.relationship('Platform', back_populates='release_groups') + images = sqlalchemy.orm.relationship('Image', back_populates='release_group') def __repr__(self): - return 'Release Group: id: %s, uuid: %s, name: %s' % (self.id, self.uuid, - self.name) + return 'Release Group: id: %s, uuid: %s, name: %s, platform:%s' % (self.id, self.uuid, + self.name, self.platform.fullname) class Platform(_SQLBase): @@ -65,6 +73,9 @@ class Platform(_SQLBase): fullname = sqlalchemy.Column(sqlalchemy.String, nullable=False) shortcode = sqlalchemy.Column(sqlalchemy.String, unique=True, nullable=False) + release_groups = sqlalchemy.orm.relationship('ReleaseGroup', order_by=ReleaseGroup.id, + back_populates='platform') + def __repr__(self): return 'Platform: id: %s, uuid: %s, fullname: %s, shortcode: %s' % (self.id, self.uuid, self.fullname, self.shortcode) @@ -94,6 +105,8 @@ def configure(db_path): _SQLBase.metadata.create_all(_engine) _db_session_maker.configure(bind=_engine) +# TODO: Passing the session object is a little clunky. Maybe there's a way to infer it somehow? +# Maybe setting a _session class variable? def search(session, table_object, **constraints): ''' Search the database for entries matching the given constraints. @@ -118,11 +131,14 @@ def search(session, table_object, **constraints): @contextlib.contextmanager def get_db_session(): '''Get a SQLAlchemy database session with a proper context object. ''' + # TODO: Raise an exception if _db_session_maker() isn't configured. session = _db_session_maker() try: yield session except: + # TODO: Decide which exceptions to handle/eat here and which ones belong in UI. + # This one is okay to put off until you start really building UI. session.rollback() raise else: