feat: new attribute system

This commit is contained in:
Hellow 2023-09-14 23:35:37 +02:00
parent 33f97662d7
commit ad4328dd11
8 changed files with 158 additions and 36 deletions

View File

@ -19,7 +19,7 @@ Additionally it provides an **Interface** to:
### DatabaseObject.merge() ### 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. The simple attributes just get carried from the other instance, to the self instance.

View File

@ -5,8 +5,8 @@ from .parents import DatabaseObject
class Contact(DatabaseObject): class Contact(DatabaseObject):
COLLECTION_ATTRIBUTES = tuple() COLLECTION_STRING_ATTRIBUTES = tuple()
SIMPLE_ATTRIBUTES = { SIMPLE_STRING_ATTRIBUTES = {
"contact_method": None, "contact_method": None,
"value": None, "value": None,
} }

View File

@ -8,8 +8,8 @@ from .formatted_text import FormattedText
class Lyrics(DatabaseObject): class Lyrics(DatabaseObject):
COLLECTION_ATTRIBUTES = ("source_collection",) COLLECTION_STRING_ATTRIBUTES = ("source_collection",)
SIMPLE_ATTRIBUTES = { SIMPLE_STRING_ATTRIBUTES = {
"text": FormattedText(), "text": FormattedText(),
"language": None "language": None
} }

View File

@ -1,6 +1,7 @@
import random import random
from collections import defaultdict 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 .metadata import Metadata
from .option import Options from .option import Options
@ -10,15 +11,46 @@ from ..utils.config import main_settings, logging_settings
LOGGER = logging_settings["object_logger"] 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: class DatabaseObject:
COLLECTION_ATTRIBUTES: tuple = tuple() COLLECTION_STRING_ATTRIBUTES: tuple = tuple()
SIMPLE_ATTRIBUTES: dict = dict() SIMPLE_STRING_ATTRIBUTES: dict = dict()
# contains all collection attributes, which describe something "smaller" # contains all collection attributes, which describe something "smaller"
# e.g. album has songs, but not artist. # e.g. album has songs, but not artist.
DOWNWARDS_COLLECTION_ATTRIBUTES: tuple = tuple() DOWNWARDS_COLLECTION_STRING_ATTRIBUTES: tuple = tuple()
UPWARDS_COLLECTION_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: def __init__(self, _id: int = None, dynamic: bool = False, **kwargs) -> None:
self.automatic_id: bool = False self.automatic_id: bool = False
@ -33,13 +65,43 @@ class DatabaseObject:
self.automatic_id = True self.automatic_id = True
# LOGGER.debug(f"Id for {type(self).__name__} isn't set. Setting to {_id}") # 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) # The id can only be None, if the object is dynamic (self.dynamic = True)
self.id: Optional[int] = _id self.id: Optional[int] = _id
self.dynamic = dynamic self.dynamic = dynamic
self.build_version = -1 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): def __hash__(self):
if self.dynamic: if self.dynamic:
raise TypeError("Dynamic DatabaseObjects are unhashable.") raise TypeError("Dynamic DatabaseObjects are unhashable.")
@ -89,10 +151,10 @@ class DatabaseObject:
LOGGER.warning(f"can't merge \"{type(other)}\" into \"{type(self)}\"") LOGGER.warning(f"can't merge \"{type(other)}\" into \"{type(self)}\"")
return return
for collection in type(self).COLLECTION_ATTRIBUTES: for collection in type(self).COLLECTION_STRING_ATTRIBUTES:
getattr(self, collection).extend(getattr(other, collection)) 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: if getattr(other, simple_attribute) == default_value:
continue continue
@ -100,7 +162,7 @@ class DatabaseObject:
setattr(self, simple_attribute, getattr(other, simple_attribute)) setattr(self, simple_attribute, getattr(other, simple_attribute))
def strip_details(self): 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() getattr(self, collection).clear()
@property @property

View File

@ -15,7 +15,7 @@ from .metadata import (
Metadata Metadata
) )
from .option import Options from .option import Options
from .parents import MainObject, DatabaseObject from .parents import MainObject, DatabaseObject, StaticAttribute
from .source import Source, SourceCollection from .source import Source, SourceCollection
from .target import Target from .target import Target
from ..utils.string_processing import unify 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. 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", "lyrics_collection", "album_collection", "main_artist_collection", "feature_artist_collection",
"source_collection") "source_collection")
SIMPLE_ATTRIBUTES = { SIMPLE_STRING_ATTRIBUTES = {
"title": None, "title": None,
"unified_title": None, "unified_title": None,
"isrc": None, "isrc": None,
@ -49,7 +49,23 @@ class Song(MainObject):
"notes": FormattedText() "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__( def __init__(
self, self,
@ -212,8 +228,8 @@ All objects dependent on Album
class Album(MainObject): class Album(MainObject):
COLLECTION_ATTRIBUTES = ("label_collection", "artist_collection", "song_collection") COLLECTION_STRING_ATTRIBUTES = ("label_collection", "artist_collection", "song_collection")
SIMPLE_ATTRIBUTES = { SIMPLE_STRING_ATTRIBUTES = {
"title": None, "title": None,
"unified_title": None, "unified_title": None,
"album_status": None, "album_status": None,
@ -225,8 +241,25 @@ class Album(MainObject):
"notes": FormattedText() "notes": FormattedText()
} }
DOWNWARDS_COLLECTION_ATTRIBUTES = ("song_collection", ) DOWNWARDS_COLLECTION_STRING_ATTRIBUTES = ("song_collection", )
UPWARDS_COLLECTION_ATTRIBUTES = ("artist_collection", "label_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__( def __init__(
self, self,
@ -454,13 +487,13 @@ All objects dependent on Artist
class Artist(MainObject): class Artist(MainObject):
COLLECTION_ATTRIBUTES = ( COLLECTION_STRING_ATTRIBUTES = (
"feature_song_collection", "feature_song_collection",
"main_album_collection", "main_album_collection",
"label_collection", "label_collection",
"source_collection" "source_collection"
) )
SIMPLE_ATTRIBUTES = { SIMPLE_STRING_ATTRIBUTES = {
"name": None, "name": None,
"unified_name": None, "unified_name": None,
"country": None, "country": None,
@ -471,8 +504,26 @@ class Artist(MainObject):
"unformated_location": None, "unformated_location": None,
} }
DOWNWARDS_COLLECTION_ATTRIBUTES = ("feature_song_collection", "main_album_collection") DOWNWARDS_COLLECTION_STRING_ATTRIBUTES = ("feature_song_collection", "main_album_collection")
UPWARDS_COLLECTION_ATTRIBUTES = ("label_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__( def __init__(
self, self,
@ -713,14 +764,23 @@ Label
class Label(MainObject): class Label(MainObject):
COLLECTION_ATTRIBUTES = ("album_collection", "current_artist_collection") COLLECTION_STRING_ATTRIBUTES = ("album_collection", "current_artist_collection")
SIMPLE_ATTRIBUTES = { SIMPLE_STRING_ATTRIBUTES = {
"name": None, "name": None,
"unified_name": None, "unified_name": None,
"notes": FormattedText() "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__( def __init__(
self, self,

View File

@ -19,8 +19,8 @@ class Source(DatabaseObject):
Source(src="youtube", url="https://youtu.be/dfnsdajlhkjhsd") Source(src="youtube", url="https://youtu.be/dfnsdajlhkjhsd")
``` ```
""" """
COLLECTION_ATTRIBUTES = tuple() COLLECTION_STRING_ATTRIBUTES = tuple()
SIMPLE_ATTRIBUTES = { SIMPLE_STRING_ATTRIBUTES = {
"page_enum": None, "page_enum": None,
"url": None, "url": None,
"referer_page": None, "referer_page": None,

View File

@ -22,11 +22,11 @@ class Target(DatabaseObject):
``` ```
""" """
SIMPLE_ATTRIBUTES = { SIMPLE_STRING_ATTRIBUTES = {
"_file": None, "_file": None,
"_path": None "_path": None
} }
COLLECTION_ATTRIBUTES = tuple() COLLECTION_STRING_ATTRIBUTES = tuple()
def __init__( def __init__(
self, self,

View File

@ -303,7 +303,7 @@ class Page:
if stop_at_level > 1: if stop_at_level > 1:
collection: Collection 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) collection = music_object.__getattribute__(collection_str)
for sub_element in collection: for sub_element in collection:
@ -332,7 +332,7 @@ class Page:
def fill_naming_objects(naming_music_object: DatabaseObject): def fill_naming_objects(naming_music_object: DatabaseObject):
nonlocal naming_dict 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) collection: Collection = getattr(naming_music_object, collection_name)
if collection.empty: if collection.empty:
@ -368,7 +368,7 @@ class Page:
download_result: DownloadResult = DownloadResult() 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) collection: Collection = getattr(music_object, collection_name)
sub_ordered_music_object: DatabaseObject sub_ordered_music_object: DatabaseObject