try: ljdsfal

This commit is contained in:
Hellow 2023-10-12 19:24:35 +02:00
parent ad4328dd11
commit 0bf0754f6e
8 changed files with 258 additions and 89 deletions

View File

@ -1,63 +1,104 @@
from music_kraken import objects, recurse from music_kraken.objects import (
Song,
import pycountry Album,
Artist,
Label,
Source,
DatabaseObject
)
from music_kraken.utils.enums import SourcePages
song = objects.Song( only_smile = Artist(
genre="HS Core", name="Only Smile",
title="Vein Deep in the Solution", source_list=[Source(SourcePages.BANDCAMP, "https://onlysmile.bandcamp.com/")],
length=666, main_album_list=[
isrc="US-S1Z-99-00001", Album(
tracksort=2, title="Few words...",
target=[ source_list=[Source(SourcePages.BANDCAMP, "https://onlysmile.bandcamp.com/album/few-words")],
objects.Target(file="song.mp3", path="example") song_list=[
], Song(title="Everything will be fine"),
lyrics_list=[ Song(title="Only Smile"),
objects.Lyrics(text="these are some depressive lyrics", language="en"), Song(title="Dear Diary"),
objects.Lyrics(text="Dies sind depressive Lyrics", language="de") Song(title="Sad Story")
],
source_list=[
objects.Source(objects.SourcePages.YOUTUBE, "https://youtu.be/dfnsdajlhkjhsd"),
objects.Source(objects.SourcePages.MUSIFY, "https://ln.topdf.de/Music-Kraken/")
],
album_list=[
objects.Album(
title="One Final Action",
date=objects.ID3Timestamp(year=1986, month=3, day=1),
language=pycountry.languages.get(alpha_2="en"),
label_list=[
objects.Label(name="an album label")
], ],
source_list=[ artist_list=[
objects.Source(objects.SourcePages.ENCYCLOPAEDIA_METALLUM, "https://www.metal-archives.com/albums/I%27m_in_a_Coffin/One_Final_Action/207614") Artist(
] name="Only Smile",
), source_list=[Source(SourcePages.BANDCAMP, "https://onlysmile.bandcamp.com/")],
], main_album_list=[
main_artist_list=[ Album(
objects.Artist( title="Few words...",
name="I'm in a coffin", source_list=[Source(SourcePages.BANDCAMP, "https://onlysmile.bandcamp.com/album/few-words")],
source_list=[ song_list=[
objects.Source( Song(title="Everything will be fine"),
objects.SourcePages.ENCYCLOPAEDIA_METALLUM, Song(title="Only Smile"),
"https://www.metal-archives.com/bands/I%27m_in_a_Coffin/127727" Song(title="Dear Diary"),
) Song(title="Sad Story")
], ],
label_list=[ artist_list=[
objects.Label(name="Depressive records") Artist(
name="Only Smile",
source_list=[Source(SourcePages.BANDCAMP, "https://onlysmile.bandcamp.com/")]
)
]
),
Album(
title="Your best friend",
source_list=[Source(SourcePages.BANDCAMP, "https://onlysmile.bandcamp.com/album/your-best-friend")]
)
]
),
Artist(
name="Only Smile",
source_list=[Source(SourcePages.BANDCAMP, "https://onlysmile.bandcamp.com/")],
main_album_list=[
Album(
title="Few words...",
source_list=[Source(SourcePages.BANDCAMP, "https://onlysmile.bandcamp.com/album/few-words")],
song_list=[
Song(title="Everything will be fine"),
Song(title="Only Smile"),
Song(title="Dear Diary"),
Song(title="Sad Story")
],
artist_list=[
Artist(
name="Only Smile",
source_list=[Source(SourcePages.BANDCAMP, "https://onlysmile.bandcamp.com/")]
)
]
),
Album(
title="Your best friend",
source_list=[Source(SourcePages.BANDCAMP, "https://onlysmile.bandcamp.com/album/your-best-friend")]
)
]
)
] ]
), ),
objects.Artist(name="some_split_artist") Album(
], title="Your best friend",
feature_artist_list=[ source_list=[Source(SourcePages.BANDCAMP, "https://onlysmile.bandcamp.com/album/your-best-friend")]
objects.Artist(
name="Ruffiction",
label_list=[
objects.Label(name="Ruffiction Productions")
]
) )
], ]
) )
song.compile()
print(song.options) objects_by_id = {}
def add_to_objects_dump(db_obj: DatabaseObject):
objects_by_id[db_obj.id] = db_obj
for collection in db_obj.all_collections:
for new_db_obj in collection:
if new_db_obj.id not in objects_by_id:
add_to_objects_dump(new_db_obj)
add_to_objects_dump(only_smile)
for _id, _object in objects_by_id.items():
print(_id, _object, sep=": ")
print(only_smile)

View File

@ -1,7 +1,6 @@
import logging import logging
import gc import gc
import musicbrainzngs import sys
from .utils.shared import DEBUG, DEBUG_LOGGIN from .utils.shared import DEBUG, DEBUG_LOGGIN
from .utils.config import logging_settings, main_settings, read_config from .utils.config import logging_settings, main_settings, read_config
@ -9,6 +8,10 @@ read_config()
from . import cli from . import cli
# I am SO sorry
print(sys.setrecursionlimit(500))
# configure logger default # configure logger default
logging.basicConfig( logging.basicConfig(
level=logging_settings['log_level'] if not DEBUG_LOGGIN else logging.DEBUG, level=logging_settings['log_level'] if not DEBUG_LOGGIN else logging.DEBUG,

View File

@ -3,6 +3,11 @@ from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from .parents import DatabaseObject from .parents import DatabaseObject
from ..utils.hooks import HookEventTypes, Hooks, Event
class CollectionHooks(HookEventTypes):
APPEND_NEW = "append_new"
T = TypeVar('T', bound=DatabaseObject) T = TypeVar('T', bound=DatabaseObject)
@ -46,6 +51,8 @@ class Collection(Generic[T]):
self._attribute_to_object_map: Dict[str, Dict[object, T]] = defaultdict(dict) self._attribute_to_object_map: Dict[str, Dict[object, T]] = defaultdict(dict)
self._used_ids: set = set() self._used_ids: set = set()
self.hooks: Hooks = Hooks(self)
if data is not None: if data is not None:
self.extend(data, merge_on_conflict=True) self.extend(data, merge_on_conflict=True)
@ -74,7 +81,7 @@ class Collection(Generic[T]):
pass pass
def append(self, element: T, merge_on_conflict: bool = True, def append(self, element: T, merge_on_conflict: bool = True,
merge_into_existing: bool = True) -> AppendResult: merge_into_existing: bool = True, no_hook: bool = False) -> AppendResult:
""" """
:param element: :param element:
:param merge_on_conflict: :param merge_on_conflict:
@ -84,6 +91,10 @@ class Collection(Generic[T]):
if element is None: if element is None:
return AppendResult(False, None, False) return AppendResult(False, None, False)
for existing_element in self._data:
if element is existing_element:
return AppendResult(False, None, False)
# if the element type has been defined in the initializer it checks if the type matches # if the element type has been defined in the initializer it checks if the type matches
if self.element_type is not None and not isinstance(element, self.element_type): if self.element_type is not None and not isinstance(element, self.element_type):
raise TypeError(f"{type(element)} is not the set type {self.element_type}") raise TypeError(f"{type(element)} is not the set type {self.element_type}")
@ -117,18 +128,60 @@ class Collection(Generic[T]):
self.map_element(element) self.map_element(element)
return AppendResult(True, existing_object, False) return AppendResult(True, existing_object, False)
if not no_hook:
self.hooks.trigger_event(CollectionHooks.APPEND_NEW, new_object=element)
self._data.append(element) self._data.append(element)
self.map_element(element) self.map_element(element)
return AppendResult(False, element, False) return AppendResult(False, element, False)
def extend(self, element_list: Iterable[T], merge_on_conflict: bool = True, def extend(self, element_list: Iterable[T], merge_on_conflict: bool = True,
merge_into_existing: bool = True): merge_into_existing: bool = True, no_hook: bool = False):
if element_list is None:
return
if len(element_list) <= 0:
return
if element_list is self:
return
for element in element_list: for element in element_list:
self.append(element, merge_on_conflict=merge_on_conflict, merge_into_existing=merge_into_existing) self.append(element, merge_on_conflict=merge_on_conflict, merge_into_existing=merge_into_existing, no_hook=no_hook)
def sync_collection(self, collection_attribute: str):
def on_append(event: Event, new_object: T, *args, **kwargs):
new_collection = new_object.__getattribute__(collection_attribute)
if self is new_collection:
return
self.extend(new_object.__getattribute__(collection_attribute), no_hook=True)
new_object.__setattr__(collection_attribute, self)
self.hooks.add_event_listener(CollectionHooks.APPEND_NEW, on_append)
def sync_main_collection(self, main_collection: "Collection", collection_attribute: str):
def on_append(event: Event, new_object: T, *args, **kwargs):
new_collection = new_object.__getattribute__(collection_attribute)
if main_collection is new_collection:
return
main_collection.extend(new_object.__getattribute__(collection_attribute), no_hook=True)
new_object.__setattr__(collection_attribute, main_collection)
self.hooks.add_event_listener(CollectionHooks.APPEND_NEW, on_append)
"""
def on_append(event: Event, new_object: T, *args, **kwargs):
new_collection: Collection = new_object.__getattribute__(collection_attribute)
if self is new_collection:
return
self.extend(new_collection.shallow_list, no_hook=False)
new_object.__setattr__(collection_attribute, self)
self.hooks.add_event_listener(CollectionHooks.APPEND_NEW, on_append)
"""
def __iter__(self) -> Iterator[T]: def __iter__(self) -> Iterator[T]:
for element in self.shallow_list: for element in self._data:
yield element yield element
def __str__(self) -> str: def __str__(self) -> str:

View File

@ -11,32 +11,36 @@ from ..utils.config import main_settings, logging_settings
LOGGER = logging_settings["object_logger"] LOGGER = logging_settings["object_logger"]
T = TypeVar('T') P = TypeVar('P')
@dataclass @dataclass
class StaticAttribute(Generic[T]): class StaticAttribute(Generic[P]):
name: str name: str
default_value: Any = None default_value: Any = None
weight: float = 0 weight: float = 0
is_simple: bool = True
is_collection: bool = False is_collection: bool = False
is_downwards_collection: bool = False is_downwards_collection: bool = False
is_upwards_collection: bool = False is_upwards_collection: bool = False
class Attribute(Generic[T]):
class Attribute(Generic[P]):
def __init__(self, database_object: "DatabaseObject", static_attribute: StaticAttribute) -> None: def __init__(self, database_object: "DatabaseObject", static_attribute: StaticAttribute) -> None:
self.database_object: DatabaseObject = database_object self.database_object: DatabaseObject = database_object
self.static_attribute: StaticAttribute = static_attribute self.static_attribute: StaticAttribute = static_attribute
def get(self) -> T: @property
def name(self) -> str:
return self.static_attribute.name
def get(self) -> P:
return self.database_object.__getattribute__(self.name) return self.database_object.__getattribute__(self.name)
def set(self, value: T): def set(self, value: P):
self.database_object.__setattr__(self.name, value) self.database_object.__setattr__(self.name, value)
@ -75,16 +79,15 @@ class DatabaseObject:
attribute: Attribute = Attribute(self, static_attribute) attribute: Attribute = Attribute(self, static_attribute)
self._attributes.append(attribute) self._attributes.append(attribute)
if static_attribute.is_simple: if static_attribute.is_collection:
self._simple_attribute_list.append(attribute)
else:
if static_attribute.is_collection: if static_attribute.is_collection:
self._collection_attributes.append(attribute) self._collection_attributes.append(attribute)
if static_attribute.is_upwards_collection: if static_attribute.is_upwards_collection:
self._upwards_collection_attributes.append(attribute) self._upwards_collection_attributes.append(attribute)
if static_attribute.is_downwards_collection: if static_attribute.is_downwards_collection:
self._downwards_collection_attributes.append(attribute) self._downwards_collection_attributes.append(attribute)
else:
self._simple_attribute_list.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
@ -102,6 +105,11 @@ class DatabaseObject:
for attribute in self._downwards_collection_attributes: for attribute in self._downwards_collection_attributes:
yield attribute.get() yield attribute.get()
@property
def all_collections(self) -> "Collection":
for attribute in self._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.")
@ -151,8 +159,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_STRING_ATTRIBUTES: for collection in self._collection_attributes:
getattr(self, collection).extend(getattr(other, collection)) if hasattr(self, collection.name) and hasattr(other, collection.name):
if collection.get() is not getattr(other, collection.name):
collection.get().extend(getattr(other, collection.name))
for simple_attribute, default_value in type(self).SIMPLE_STRING_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:

View File

@ -5,7 +5,7 @@ from typing import List, Optional, Dict, Tuple, Type
import pycountry import pycountry
from ..utils.enums.album import AlbumType, AlbumStatus from ..utils.enums.album import AlbumType, AlbumStatus
from .collection import Collection from .collection import Collection, CollectionHooks
from .formatted_text import FormattedText from .formatted_text import FormattedText
from .lyrics import Lyrics from .lyrics import Lyrics
from .contact import Contact from .contact import Contact
@ -86,7 +86,7 @@ class Song(MainObject):
notes: FormattedText = None, notes: FormattedText = None,
**kwargs **kwargs
) -> None: ) -> None:
MainObject.__init__(self, _id=_id, dynamic=dynamic, **kwargs) super().__init__(_id=_id, dynamic=dynamic, **kwargs)
# attributes # attributes
self.title: str = title self.title: str = title
self.unified_title: str = unified_title self.unified_title: str = unified_title
@ -102,9 +102,23 @@ class Song(MainObject):
self.source_collection: SourceCollection = SourceCollection(source_list) self.source_collection: SourceCollection = SourceCollection(source_list)
self.target_collection: Collection[Target] = Collection(data=target_list, element_type=Target) self.target_collection: Collection[Target] = Collection(data=target_list, element_type=Target)
self.lyrics_collection: Collection[Lyrics] = Collection(data=lyrics_list, element_type=Lyrics) self.lyrics_collection: Collection[Lyrics] = Collection(data=lyrics_list, element_type=Lyrics)
self.album_collection: Collection[Album] = Collection(data=album_list, element_type=Album)
# main_artist_collection = album.artist collection
self.main_artist_collection: Collection[Artist] = Collection(data=main_artist_list, element_type=Artist) self.main_artist_collection: Collection[Artist] = Collection(data=main_artist_list, element_type=Artist)
self.feature_artist_collection: Collection[Artist] = Collection(data=feature_artist_list, element_type=Artist)
# this album_collection equals no collection
self.album_collection: Collection[Album] = Collection(data=[], element_type=Album)
self.album_collection.sync_main_collection(self.main_artist_collection, "artist_collection")
self.album_collection.extend(album_list)
# self.album_collection.sync_collection("song_collection")
# self.album_collection.hooks.add_event_listener(CollectionHooks.APPEND_NEW, on_album_append)
# on feature_artist_collection append, append self to artist self
self.feature_artist_collection: Collection[Artist] = Collection(data=[], element_type=Artist)
def on_feature_artist_append(event, new_object: Artist, *args, **kwargs):
new_object.feature_song_collection.append(self, no_hook=True)
self.feature_artist_collection.hooks.add_event_listener(CollectionHooks.APPEND_NEW, on_feature_artist_append)
self.feature_artist_collection.extend(feature_artist_list)
def _build_recursive_structures(self, build_version: int, merge: bool): def _build_recursive_structures(self, build_version: int, merge: bool):
if build_version == self.build_version: if build_version == self.build_version:
@ -307,8 +321,13 @@ class Album(MainObject):
self.notes = notes or FormattedText() self.notes = notes or FormattedText()
self.source_collection: SourceCollection = SourceCollection(source_list) self.source_collection: SourceCollection = SourceCollection(source_list)
self.song_collection: Collection[Song] = Collection(data=song_list, element_type=Song)
self.artist_collection: Collection[Artist] = Collection(data=artist_list, element_type=Artist) self.artist_collection: Collection[Artist] = Collection(data=artist_list, element_type=Artist)
self.song_collection: Collection[Song] = Collection(data=[], element_type=Song)
self.song_collection.sync_main_collection(self.artist_collection, "main_artist_collection")
self.song_collection.extend(song_list)
self.label_collection: Collection[Label] = Collection(data=label_list, element_type=Label) self.label_collection: Collection[Label] = Collection(data=label_list, element_type=Label)
def _build_recursive_structures(self, build_version: int, merge: bool): def _build_recursive_structures(self, build_version: int, merge: bool):
@ -565,15 +584,28 @@ class Artist(MainObject):
self.lyrical_themes: List[str] = lyrical_themes or [] self.lyrical_themes: List[str] = lyrical_themes or []
self.general_genre = general_genre self.general_genre = general_genre
self.unformated_location: Optional[str] = unformated_location
self.source_collection: SourceCollection = SourceCollection(source_list) self.source_collection: SourceCollection = SourceCollection(source_list)
self.feature_song_collection: Collection[Song] = Collection(data=feature_song_list, element_type=Song)
self.main_album_collection: Collection[Album] = Collection(data=main_album_list, element_type=Album)
self.label_collection: Collection[Label] = Collection(data=label_list, element_type=Label)
self.contact_collection: Collection[Label] = Collection(data=contact_list, element_type=Contact) self.contact_collection: Collection[Label] = Collection(data=contact_list, element_type=Contact)
self.unformated_location: Optional[str] = unformated_location self.feature_song_collection: Collection[Song] = Collection(data=[], element_type=Song)
def on_feature_song_append(event, new_object: Song, *args, **kwargs):
new_object.feature_artist_collection.append(self, no_hook=True)
self.feature_song_collection.hooks.add_event_listener(CollectionHooks.APPEND_NEW, on_feature_song_append)
self.feature_song_collection.extend(feature_song_list)
self.main_album_collection: Collection[Album] = Collection(data=[], element_type=Album)
def on_album_append(event, new_object: Album, *args, **kwargs):
new_object.artist_collection.append(self, no_hook=True)
self.main_album_collection.hooks.add_event_listener(CollectionHooks.APPEND_NEW, on_album_append)
self.main_album_collection.extend(main_album_list)
self.label_collection: Collection[Label] = Collection(data=label_list, element_type=Label)
def on_label_append(event, new_object: Label, *args, **kwargs):
new_object.current_artist_collection.append(self, no_hook=True)
self.label_collection.hooks.add_event_listener(CollectionHooks.APPEND_NEW, on_label_append)
def _add_other_db_objects(self, object_type: Type["DatabaseObject"], object_list: List["DatabaseObject"]): def _add_other_db_objects(self, object_type: Type["DatabaseObject"], object_list: List["DatabaseObject"]):
if object_type is Song: if object_type is Song:

View File

@ -140,6 +140,9 @@ def clean_object(dirty_object: DatabaseObject) -> DatabaseObject:
Song: Collection(element_type=Song) Song: Collection(element_type=Song)
} }
if isinstance(dirty_object, Song):
return dirty_object
_clean_music_object(dirty_object, collections) _clean_music_object(dirty_object, collections)
return dirty_object return dirty_object
@ -153,7 +156,7 @@ def merge_together(old_object: DatabaseObject, new_object: DatabaseObject, do_co
new_object = clean_object(new_object) new_object = clean_object(new_object)
old_object.merge(new_object) old_object.merge(new_object)
if do_compile: if do_compile and False:
old_object.compile(merge_into=False) old_object.compile(merge_into=False)
return old_object return old_object
@ -274,7 +277,7 @@ class Page:
post_process=False post_process=False
)) ))
return merge_together(music_object, new_music_object, do_compile=post_process) return music_object.merge(new_music_object)
def fetch_object_from_source(self, source: Source, stop_at_level: int = 2, enforce_type: Type[DatabaseObject] = None, post_process: bool = True) -> Optional[DatabaseObject]: def fetch_object_from_source(self, source: Source, stop_at_level: int = 2, enforce_type: Type[DatabaseObject] = None, post_process: bool = True) -> Optional[DatabaseObject]:
obj_type = self.get_source_type(source) obj_type = self.get_source_type(source)
@ -309,9 +312,6 @@ class Page:
for sub_element in collection: for sub_element in collection:
sub_element.merge(self.fetch_details(sub_element, stop_at_level=stop_at_level-1, post_process=False)) sub_element.merge(self.fetch_details(sub_element, stop_at_level=stop_at_level-1, post_process=False))
if post_process and music_object:
return build_new_object(music_object)
return music_object return music_object
def fetch_song(self, source: Source, stop_at_level: int = 1) -> Song: def fetch_song(self, source: Source, stop_at_level: int = 1) -> Song:

View File

@ -0,0 +1 @@
from .source import SourcePages

View File

@ -0,0 +1,29 @@
from typing import List, Iterable, Dict, TypeVar, Generic, Iterator, Any, Type
from enum import Enum
from dataclasses import dataclass
from collections import defaultdict
class HookEventTypes(Enum):
pass
@dataclass
class Event:
target: Any
class Hooks:
def __init__(self, target) -> None:
self.target = target
self._callbacks: Dict[HookEventTypes, List[callable]] = defaultdict(list)
def add_event_listener(self, event_type: HookEventTypes, callback: callable):
self._callbacks[event_type].append(callback)
def trigger_event(self, event_type: HookEventTypes, *args, **kwargs):
event: Event = Event(target=self.target)
for callback in self._callbacks[event_type]:
callback(event, *args, **kwargs)