diff --git a/documentation/objects.md b/documentation/objects.md index 3891898..2b54dab 100644 --- a/documentation/objects.md +++ b/documentation/objects.md @@ -19,7 +19,7 @@ Additionally it provides an **Interface** to: ### DatabaseObject.merge() -To merge the data of two instances of the same type, the attributes defined in `DatabaseObject.COLLECTION_ATTRIBUTES` and `SIMPLE_ATTRIBUTES` are used. +To merge the data of two instances of the same type, the attributes defined in `DatabaseObject.COLLECTION_STRING_ATTRIBUTES` and `SIMPLE_STRING_ATTRIBUTES` are used. The simple attributes just get carried from the other instance, to the self instance. diff --git a/src/music_kraken/objects/contact.py b/src/music_kraken/objects/contact.py index cdad4da..2041297 100644 --- a/src/music_kraken/objects/contact.py +++ b/src/music_kraken/objects/contact.py @@ -5,8 +5,8 @@ from .parents import DatabaseObject class Contact(DatabaseObject): - COLLECTION_ATTRIBUTES = tuple() - SIMPLE_ATTRIBUTES = { + COLLECTION_STRING_ATTRIBUTES = tuple() + SIMPLE_STRING_ATTRIBUTES = { "contact_method": None, "value": None, } diff --git a/src/music_kraken/objects/lyrics.py b/src/music_kraken/objects/lyrics.py index 465b96d..bcd1c1e 100644 --- a/src/music_kraken/objects/lyrics.py +++ b/src/music_kraken/objects/lyrics.py @@ -8,8 +8,8 @@ from .formatted_text import FormattedText class Lyrics(DatabaseObject): - COLLECTION_ATTRIBUTES = ("source_collection",) - SIMPLE_ATTRIBUTES = { + COLLECTION_STRING_ATTRIBUTES = ("source_collection",) + SIMPLE_STRING_ATTRIBUTES = { "text": FormattedText(), "language": None } diff --git a/src/music_kraken/objects/parents.py b/src/music_kraken/objects/parents.py index 7297b17..e0f629d 100644 --- a/src/music_kraken/objects/parents.py +++ b/src/music_kraken/objects/parents.py @@ -1,6 +1,7 @@ import random from collections import defaultdict -from typing import Optional, Dict, Tuple, List, Type +from typing import Optional, Dict, Tuple, List, Type, Generic, TypeVar, Any +from dataclasses import dataclass from .metadata import Metadata from .option import Options @@ -10,15 +11,46 @@ from ..utils.config import main_settings, logging_settings LOGGER = logging_settings["object_logger"] +T = TypeVar('T') + +@dataclass +class StaticAttribute(Generic[T]): + name: str + + default_value: Any = None + weight: float = 0 + + is_simple: bool = True + + is_collection: bool = False + is_downwards_collection: bool = False + is_upwards_collection: bool = False + + + +class Attribute(Generic[T]): + def __init__(self, database_object: "DatabaseObject", static_attribute: StaticAttribute) -> None: + self.database_object: DatabaseObject = database_object + self.static_attribute: StaticAttribute = static_attribute + + def get(self) -> T: + return self.database_object.__getattribute__(self.name) + + def set(self, value: T): + self.database_object.__setattr__(self.name, value) + + class DatabaseObject: - COLLECTION_ATTRIBUTES: tuple = tuple() - SIMPLE_ATTRIBUTES: dict = dict() + COLLECTION_STRING_ATTRIBUTES: tuple = tuple() + SIMPLE_STRING_ATTRIBUTES: dict = dict() # contains all collection attributes, which describe something "smaller" # e.g. album has songs, but not artist. - DOWNWARDS_COLLECTION_ATTRIBUTES: tuple = tuple() - UPWARDS_COLLECTION_ATTRIBUTES: tuple = tuple() + DOWNWARDS_COLLECTION_STRING_ATTRIBUTES: tuple = tuple() + UPWARDS_COLLECTION_STRING_ATTRIBUTES: tuple = tuple() + + STATIC_ATTRIBUTES: List[StaticAttribute] = list() def __init__(self, _id: int = None, dynamic: bool = False, **kwargs) -> None: self.automatic_id: bool = False @@ -33,13 +65,43 @@ class DatabaseObject: self.automatic_id = True # LOGGER.debug(f"Id for {type(self).__name__} isn't set. Setting to {_id}") + self._attributes: List[Attribute] = [] + self._simple_attribute_list: List[Attribute] = [] + self._collection_attributes: List[Attribute] = [] + self._downwards_collection_attributes: List[Attribute] = [] + self._upwards_collection_attributes: List[Attribute] = [] + + for static_attribute in self.STATIC_ATTRIBUTES: + attribute: Attribute = Attribute(self, static_attribute) + self._attributes.append(attribute) + + if static_attribute.is_simple: + self._simple_attribute_list.append(attribute) + else: + if static_attribute.is_collection: + self._collection_attributes.append(attribute) + if static_attribute.is_upwards_collection: + self._upwards_collection_attributes.append(attribute) + if static_attribute.is_downwards_collection: + self._downwards_collection_attributes.append(attribute) + + # The id can only be None, if the object is dynamic (self.dynamic = True) self.id: Optional[int] = _id self.dynamic = dynamic - self.build_version = -1 + @property + def upwards_collection(self) -> "Collection": + for attribute in self._upwards_collection_attributes: + yield attribute.get() + + @property + def downwards_collection(self) -> "Collection": + for attribute in self._downwards_collection_attributes: + yield attribute.get() + def __hash__(self): if self.dynamic: raise TypeError("Dynamic DatabaseObjects are unhashable.") @@ -89,10 +151,10 @@ class DatabaseObject: LOGGER.warning(f"can't merge \"{type(other)}\" into \"{type(self)}\"") return - for collection in type(self).COLLECTION_ATTRIBUTES: + for collection in type(self).COLLECTION_STRING_ATTRIBUTES: getattr(self, collection).extend(getattr(other, collection)) - for simple_attribute, default_value in type(self).SIMPLE_ATTRIBUTES.items(): + for simple_attribute, default_value in type(self).SIMPLE_STRING_ATTRIBUTES.items(): if getattr(other, simple_attribute) == default_value: continue @@ -100,7 +162,7 @@ class DatabaseObject: setattr(self, simple_attribute, getattr(other, simple_attribute)) def strip_details(self): - for collection in type(self).DOWNWARDS_COLLECTION_ATTRIBUTES: + for collection in type(self).DOWNWARDS_COLLECTION_STRING_ATTRIBUTES: getattr(self, collection).clear() @property diff --git a/src/music_kraken/objects/song.py b/src/music_kraken/objects/song.py index 36cd77a..dae43ea 100644 --- a/src/music_kraken/objects/song.py +++ b/src/music_kraken/objects/song.py @@ -15,7 +15,7 @@ from .metadata import ( Metadata ) from .option import Options -from .parents import MainObject, DatabaseObject +from .parents import MainObject, DatabaseObject, StaticAttribute from .source import Source, SourceCollection from .target import Target from ..utils.string_processing import unify @@ -36,10 +36,10 @@ class Song(MainObject): tracksort, genre, source_list, target, lyrics_list, album, main_artist_list, and feature_artist_list. """ - COLLECTION_ATTRIBUTES = ( + COLLECTION_STRING_ATTRIBUTES = ( "lyrics_collection", "album_collection", "main_artist_collection", "feature_artist_collection", "source_collection") - SIMPLE_ATTRIBUTES = { + SIMPLE_STRING_ATTRIBUTES = { "title": None, "unified_title": None, "isrc": None, @@ -49,7 +49,23 @@ class Song(MainObject): "notes": FormattedText() } - UPWARDS_COLLECTION_ATTRIBUTES = ("album_collection", "main_artist_collection", "feature_artist_collection") + UPWARDS_COLLECTION_STRING_ATTRIBUTES = ("album_collection", "main_artist_collection", "feature_artist_collection") + + STATIC_ATTRIBUTES = [ + StaticAttribute(name="title", weight=.5), + StaticAttribute(name="unified_title", weight=.3), + StaticAttribute(name="isrc", weight=1), + StaticAttribute(name="length"), + StaticAttribute(name="tracksort", default_value=0), + StaticAttribute(name="genre"), + StaticAttribute(name="notes", default_value=FormattedText()), + + StaticAttribute(name="source_collection", is_collection=True), + StaticAttribute(name="lyrics_collection", is_collection=True), + StaticAttribute(name="album_collection", is_collection=True, is_upwards_collection=True), + StaticAttribute(name="main_artist_collection", is_collection=True, is_upwards_collection=True), + StaticAttribute(name="feature_artist_collection", is_collection=True, is_upwards_collection=True) + ] def __init__( self, @@ -212,8 +228,8 @@ All objects dependent on Album class Album(MainObject): - COLLECTION_ATTRIBUTES = ("label_collection", "artist_collection", "song_collection") - SIMPLE_ATTRIBUTES = { + COLLECTION_STRING_ATTRIBUTES = ("label_collection", "artist_collection", "song_collection") + SIMPLE_STRING_ATTRIBUTES = { "title": None, "unified_title": None, "album_status": None, @@ -225,8 +241,25 @@ class Album(MainObject): "notes": FormattedText() } - DOWNWARDS_COLLECTION_ATTRIBUTES = ("song_collection", ) - UPWARDS_COLLECTION_ATTRIBUTES = ("artist_collection", "label_collection") + DOWNWARDS_COLLECTION_STRING_ATTRIBUTES = ("song_collection", ) + UPWARDS_COLLECTION_STRING_ATTRIBUTES = ("artist_collection", "label_collection") + + STATIC_ATTRIBUTES = [ + StaticAttribute(name="title", weight=.5), + StaticAttribute(name="unified_title", weight=.3), + StaticAttribute(name="language"), + StaticAttribute(name="barcode", weight=1), + StaticAttribute(name="albumsort"), + StaticAttribute(name="album_status"), + StaticAttribute(name="album_type", default_value=AlbumType.OTHER), + StaticAttribute(name="date", default_value=ID3Timestamp()), + StaticAttribute(name="notes", default_value=FormattedText()), + + StaticAttribute(name="source_collection", is_collection=True), + StaticAttribute(name="song_collection", is_collection=True, is_downwards_collection=True), + StaticAttribute(name="artist_collection", is_collection=True, is_upwards_collection=True), + StaticAttribute(name="label_collection", is_collection=True, is_upwards_collection=True), + ] def __init__( self, @@ -454,13 +487,13 @@ All objects dependent on Artist class Artist(MainObject): - COLLECTION_ATTRIBUTES = ( + COLLECTION_STRING_ATTRIBUTES = ( "feature_song_collection", "main_album_collection", "label_collection", "source_collection" ) - SIMPLE_ATTRIBUTES = { + SIMPLE_STRING_ATTRIBUTES = { "name": None, "unified_name": None, "country": None, @@ -471,8 +504,26 @@ class Artist(MainObject): "unformated_location": None, } - DOWNWARDS_COLLECTION_ATTRIBUTES = ("feature_song_collection", "main_album_collection") - UPWARDS_COLLECTION_ATTRIBUTES = ("label_collection", ) + DOWNWARDS_COLLECTION_STRING_ATTRIBUTES = ("feature_song_collection", "main_album_collection") + UPWARDS_COLLECTION_STRING_ATTRIBUTES = ("label_collection", ) + + + STATIC_ATTRIBUTES = [ + StaticAttribute(name="name", weight=.5), + StaticAttribute(name="unified_name", weight=.3), + StaticAttribute(name="country"), + StaticAttribute(name="formed_in", default_value=ID3Timestamp()), + StaticAttribute(name="lyrical_themes", default_value=[]), + StaticAttribute(name="general_genre", default_value=""), + StaticAttribute(name="notes", default_value=FormattedText()), + StaticAttribute(name="unformated_location"), + + StaticAttribute(name="source_collection", is_collection=True), + StaticAttribute(name="contact_collection", is_collection=True), + StaticAttribute(name="feature_song_collection", is_collection=True, is_downwards_collection=True), + StaticAttribute(name="main_album_collection", is_collection=True, is_downwards_collection=True), + StaticAttribute(name="label_collection", is_collection=True, is_upwards_collection=True), + ] def __init__( self, @@ -713,14 +764,23 @@ Label class Label(MainObject): - COLLECTION_ATTRIBUTES = ("album_collection", "current_artist_collection") - SIMPLE_ATTRIBUTES = { + COLLECTION_STRING_ATTRIBUTES = ("album_collection", "current_artist_collection") + SIMPLE_STRING_ATTRIBUTES = { "name": None, "unified_name": None, "notes": FormattedText() } - DOWNWARDS_COLLECTION_ATTRIBUTES = COLLECTION_ATTRIBUTES + DOWNWARDS_COLLECTION_STRING_ATTRIBUTES = COLLECTION_STRING_ATTRIBUTES + + STATIC_ATTRIBUTES = [ + StaticAttribute(name="name", weight=.5), + StaticAttribute(name="unified_name", weight=.3), + StaticAttribute(name="notes", default_value=FormattedText()), + + StaticAttribute(name="album_collection", is_collection=True, is_downwards_collection=True), + StaticAttribute(name="current_artist_collection", is_collection=True, is_downwards_collection=True), + ] def __init__( self, diff --git a/src/music_kraken/objects/source.py b/src/music_kraken/objects/source.py index 38fd062..fd0dd0f 100644 --- a/src/music_kraken/objects/source.py +++ b/src/music_kraken/objects/source.py @@ -19,8 +19,8 @@ class Source(DatabaseObject): Source(src="youtube", url="https://youtu.be/dfnsdajlhkjhsd") ``` """ - COLLECTION_ATTRIBUTES = tuple() - SIMPLE_ATTRIBUTES = { + COLLECTION_STRING_ATTRIBUTES = tuple() + SIMPLE_STRING_ATTRIBUTES = { "page_enum": None, "url": None, "referer_page": None, diff --git a/src/music_kraken/objects/target.py b/src/music_kraken/objects/target.py index fa06177..4faee32 100644 --- a/src/music_kraken/objects/target.py +++ b/src/music_kraken/objects/target.py @@ -22,11 +22,11 @@ class Target(DatabaseObject): ``` """ - SIMPLE_ATTRIBUTES = { + SIMPLE_STRING_ATTRIBUTES = { "_file": None, "_path": None } - COLLECTION_ATTRIBUTES = tuple() + COLLECTION_STRING_ATTRIBUTES = tuple() def __init__( self, diff --git a/src/music_kraken/pages/abstract.py b/src/music_kraken/pages/abstract.py index c8c3973..4995b99 100644 --- a/src/music_kraken/pages/abstract.py +++ b/src/music_kraken/pages/abstract.py @@ -303,7 +303,7 @@ class Page: if stop_at_level > 1: collection: Collection - for collection_str in music_object.DOWNWARDS_COLLECTION_ATTRIBUTES: + for collection_str in music_object.DOWNWARDS_COLLECTION_STRING_ATTRIBUTES: collection = music_object.__getattribute__(collection_str) for sub_element in collection: @@ -332,7 +332,7 @@ class Page: def fill_naming_objects(naming_music_object: DatabaseObject): nonlocal naming_dict - for collection_name in naming_music_object.UPWARDS_COLLECTION_ATTRIBUTES: + for collection_name in naming_music_object.UPWARDS_COLLECTION_STRING_ATTRIBUTES: collection: Collection = getattr(naming_music_object, collection_name) if collection.empty: @@ -368,7 +368,7 @@ class Page: download_result: DownloadResult = DownloadResult() - for collection_name in music_object.DOWNWARDS_COLLECTION_ATTRIBUTES: + for collection_name in music_object.DOWNWARDS_COLLECTION_STRING_ATTRIBUTES: collection: Collection = getattr(music_object, collection_name) sub_ordered_music_object: DatabaseObject