refactored and cleaned up data objects

This commit is contained in:
Hellow 2023-02-25 22:16:32 +01:00
parent 819111c53a
commit 5f4912bda5
7 changed files with 148 additions and 179 deletions

View File

@ -25,6 +25,8 @@ EVEN if that means to for example keep decimal values stored in strings.
class BaseModel(Model): class BaseModel(Model):
notes: str = CharField(null=True)
class Meta: class Meta:
database = None database = None
@ -38,33 +40,41 @@ class BaseModel(Model):
return self return self
class Album(BaseModel): class ObjectModel(BaseModel):
additional_arguments: str = CharField(null=True)
notes: str = CharField(null=True)
class Album(ObjectModel):
"""A class representing an album in the music database.""" """A class representing an album in the music database."""
title: str = CharField(null=True) title: str = CharField(null=True)
label: str = CharField(null=True)
album_status: str = CharField(null=True) album_status: str = CharField(null=True)
album_type: str = CharField(null=True) album_type: str = CharField(null=True)
language: str = CharField(null=True) language: str = CharField(null=True)
date: str = CharField(null=True) date: str = CharField(null=True)
date_format: str = CharField(null=True) date_format: str = CharField(null=True)
country: str = CharField(null=True)
barcode: str = CharField(null=True) barcode: str = CharField(null=True)
albumsort: int = IntegerField(null=True)
is_split: bool = BooleanField(default=False) is_split: bool = BooleanField(default=False)
albumsort: int = IntegerField(null=True)
class Artist(BaseModel): class Artist(BaseModel):
"""A class representing an artist in the music database.""" """A class representing an artist in the music database."""
name: str = CharField() name: str = CharField(null=True)
notes: str = CharField() country: str = CharField(null=True)
formed_in_date: str = CharField(null=True)
formed_in_date_format: str = CharField(null=True)
general_genre: str = CharField(null=True)
class Song(BaseModel): class Song(BaseModel):
"""A class representing a song in the music database.""" """A class representing a song in the music database."""
name: str = CharField(null=True) title: str = CharField(null=True)
isrc: str = CharField(null=True) isrc: str = CharField(null=True)
length: int = IntegerField(null=True) length: int = IntegerField(null=True)
tracksort: int = IntegerField(null=True) tracksort: int = IntegerField(null=True)
@ -87,6 +97,12 @@ class Lyrics(BaseModel):
song = ForeignKeyField(Song, backref='lyrics') song = ForeignKeyField(Song, backref='lyrics')
class SongTarget(BaseModel):
song: ForeignKeyField = ForeignKeyField(Song, backref='song_target')
artist: ForeignKeyField = ForeignKeyField(Target, backref='song_target')
is_feature: bool = BooleanField(default=False)
class SongArtist(BaseModel): class SongArtist(BaseModel):
"""A class representing the relationship between a song and an artist.""" """A class representing the relationship between a song and an artist."""

View File

@ -0,0 +1,27 @@
from typing import List
import pycountry
from .parents import DatabaseObject
from .source import SourceAttribute, Source
from .metadata import MetadataAttribute
from .formatted_text import FormattedText
class Lyrics(DatabaseObject, SourceAttribute, MetadataAttribute):
def __init__(
self,
text: FormattedText,
language: pycountry.Languages,
_id: str = None,
dynamic: bool = False,
source_list: List[Source] = None,
**kwargs
) -> None:
DatabaseObject.__init__(_id=_id, dynamic=dynamic)
self.text: FormattedText = text
self.language: pycountry.Languages = language
if source_list is not None:
self.source_list = source_list

View File

@ -16,7 +16,7 @@ class Mapping(Enum):
# Textframes # Textframes
TITLE = "TIT2" TITLE = "TIT2"
ISRC = "TSRC" ISRC = "TSRC"
LENGTH = "TLEN" # in milliseconds LENGTH = "TLEN" # in milliseconds
DATE = "TYER" DATE = "TYER"
TRACKNUMBER = "TRCK" TRACKNUMBER = "TRCK"
TOTALTRACKS = "TRCK" # Stored in the same frame with TRACKNUMBER, separated by '/': e.g. '4/9'. TOTALTRACKS = "TRCK" # Stored in the same frame with TRACKNUMBER, separated by '/': e.g. '4/9'.
@ -193,17 +193,14 @@ class ID3Timestamp:
return "%Y" return "%Y"
return "" return ""
def get_timestamp(self) -> str: def get_timestamp(self) -> str:
time_format = self.get_time_format() time_format = self.get_time_format()
return self.date_obj.strftime(time_format) return self.date_obj.strftime(time_format)
def get_timestamp_w_format(self) -> Tuple[str, str]: def get_timestamp_w_format(self) -> Tuple[str, str]:
time_format = self.get_time_format() time_format = self.get_time_format()
return time_format, self.date_obj.strftime(time_format) return time_format, self.date_obj.strftime(time_format)
@classmethod @classmethod
def strptime(cls, time_stamp: str, format: str): def strptime(cls, time_stamp: str, format: str):
""" """

View File

@ -1,96 +1,48 @@
from typing import Optional
import uuid import uuid
from src.music_kraken.utils.shared import ( from src.music_kraken.utils.shared import (
SONG_LOGGER as logger SONG_LOGGER as LOGGER
) )
class Reference:
def __init__(self, id_: str) -> None:
self.id = id_
def __str__(self):
return f"references to an object with the id: {self.id}"
def __eq__(self, __o: object) -> bool:
if type(__o) != type(self):
return False
return self.id == __o.id
class DatabaseObject: class DatabaseObject:
empty: bool def __init__(self, _id: str = None, dynamic: bool = False, **kwargs) -> None:
if _id is None and not dynamic:
"""
generates a random UUID
https://docs.python.org/3/library/uuid.html
"""
_id = str(uuid.uuid4())
LOGGER.info(f"id for {self.__name__} isn't set. Setting to {_id}")
# The id can only be None, if the object is dynamic (self.dynamic = True)
self.id: Optional[str] = _id
def __init__(self, id_: str = None, dynamic: bool = False, empty: bool = False, **kwargs) -> None:
"""
empty means it is an placeholder.
it makes the object perform the same, it is just the same
"""
self.id_: str | None = id_
self.dynamic = dynamic self.dynamic = dynamic
self.empty = empty
def get_id(self) -> str:
"""
returns the id if it is set, else
it returns a randomly generated UUID
https://docs.python.org/3/library/uuid.html
if the object is empty, it returns None class MainObject(DatabaseObject):
if the object is dynamic, it raises an error """
""" This is the parent class for all "main" data objects:
if self.empty: - Song
return None - Album
if self.dynamic: - Artist
raise ValueError("Dynamic objects have no idea, because they are not in the database") - Label
if self.id_ is None: It has all the functionality of the "DatabaseObject" (it inherits from said class)
self.id_ = str(uuid.uuid4()) but also some added functions as well.
logger.info(f"id for {self.__str__()} isn't set. Setting to {self.id_}") """
def __init__(self, _id: str = None, dynamic: bool = False, **kwargs):
super().__init__(_id=_id, dynamic=dynamic, **kwargs)
return self.id_ self.additional_arguments: dict = kwargs
def get_reference(self) -> Reference:
return Reference(self.id)
def get_options(self) -> list: def get_options(self) -> list:
"""
makes only sense in
- artist
- song
- album
"""
return [] return []
def get_option_string(self) -> str: def get_option_string(self) -> str:
"""
makes only sense in
- artist
- song
- album
"""
return "" return ""
id = property(fget=get_id)
reference = property(fget=get_reference)
options = property(fget=get_options) options = property(fget=get_options)
options_str = property(fget=get_option_string) options_str = property(fget=get_option_string)
class SongAttribute:
def __init__(self, song=None):
# the reference to the song the lyrics belong to
self.song = song
def add_song(self, song):
self.song = song
def get_ref_song_id(self):
if self.song is None:
return None
return self.song.reference.id
def set_ref_song_id(self, song_id):
self.song_ref = Reference(song_id)
song_ref_id = property(fget=get_ref_song_id, fset=set_ref_song_id)

View File

@ -1,7 +1,6 @@
import os import os
from typing import List, Optional, Type, Dict from typing import List, Optional, Type, Dict
import pycountry import pycountry
import copy
from .metadata import ( from .metadata import (
Mapping as id3Mapping, Mapping as id3Mapping,
@ -10,12 +9,11 @@ from .metadata import (
) )
from src.music_kraken.utils.shared import ( from src.music_kraken.utils.shared import (
MUSIC_DIR, MUSIC_DIR,
DATABASE_LOGGER as logger DATABASE_LOGGER as LOGGER
) )
from .parents import ( from .parents import (
DatabaseObject, DatabaseObject,
Reference, MainObject
SongAttribute
) )
from .source import ( from .source import (
Source, Source,
@ -26,6 +24,8 @@ from .source import (
from .formatted_text import FormattedText from .formatted_text import FormattedText
from .collection import Collection from .collection import Collection
from .album import AlbumType, AlbumStatus from .album import AlbumType, AlbumStatus
from .lyrics import Lyrics
from .target import Target
""" """
All Objects dependent All Objects dependent
@ -34,77 +34,7 @@ All Objects dependent
CountryTyping = type(list(pycountry.countries)[0]) CountryTyping = type(list(pycountry.countries)[0])
class Target(DatabaseObject, SongAttribute): class Song(MainObject, SourceAttribute, MetadataAttribute):
"""
create somehow like that
```python
# I know path is pointless, and I will change that (don't worry about backwards compatibility there)
Target(file="~/Music/genre/artist/album/song.mp3", path="~/Music/genre/artist/album")
```
"""
def __init__(self, id_: str = None, file: str = None, path: str = None) -> None:
DatabaseObject.__init__(self, id_=id_)
SongAttribute.__init__(self)
self._file = file
self._path = path
def set_file(self, _file: str):
self._file = _file
def get_file(self) -> Optional[str]:
if self._file is None:
return None
return os.path.join(MUSIC_DIR, self._file)
def set_path(self, _path: str):
self._path = _path
def get_path(self) -> Optional[str]:
if self._path is None:
return None
return os.path.join(MUSIC_DIR, self._path)
def get_exists_on_disc(self) -> bool:
"""
returns True when file can be found on disc
returns False when file can't be found on disc or no filepath is set
"""
if not self.is_set():
return False
return os.path.exists(self.file)
def is_set(self) -> bool:
return not (self._file is None or self._path is None)
file = property(fget=get_file, fset=set_file)
path = property(fget=get_path, fset=set_path)
exists_on_disc = property(fget=get_exists_on_disc)
class Lyrics(DatabaseObject, SongAttribute, SourceAttribute, MetadataAttribute):
def __init__(
self,
text: str,
language: pycountry.Languages,
id_: str = None,
source_list: List[Source] = None
) -> None:
DatabaseObject.__init__(self, id_=id_)
SongAttribute.__init__(self)
self.text = text
self.language = language
if source_list is not None:
self.source_list = source_list
def get_metadata(self) -> MetadataAttribute.Metadata:
return super().get_metadata()
class Song(DatabaseObject, SourceAttribute, MetadataAttribute):
""" """
Class representing a song object, with attributes id, mb_id, title, album_name, isrc, length, Class representing a song object, with attributes id, mb_id, title, album_name, isrc, length,
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.
@ -114,14 +44,15 @@ class Song(DatabaseObject, SourceAttribute, MetadataAttribute):
def __init__( def __init__(
self, self,
id_: str = None, _id: str = None,
dynamic: bool = False,
title: str = None, title: str = None,
isrc: str = None, isrc: str = None,
length: int = None, length: int = None,
tracksort: int = None, tracksort: int = None,
genre: str = None, genre: str = None,
source_list: List[Source] = None, source_list: List[Source] = None,
target_list: Target = None, target_list: List[Target] = None,
lyrics_list: List[Lyrics] = None, lyrics_list: List[Lyrics] = None,
album_list: Type['Album'] = None, album_list: Type['Album'] = None,
main_artist_list: List[Type['Artist']] = None, main_artist_list: List[Type['Artist']] = None,
@ -131,7 +62,7 @@ class Song(DatabaseObject, SourceAttribute, MetadataAttribute):
""" """
Initializes the Song object with the following attributes: Initializes the Song object with the following attributes:
""" """
super().__init__(id_=id_, **kwargs) MainObject.__init__(self, _id=_id, dynamic=dynamic, **kwargs)
# attributes # attributes
self.title: str = title self.title: str = title
self.isrc: str = isrc self.isrc: str = isrc
@ -242,10 +173,10 @@ All objects dependent on Album
""" """
class Album(DatabaseObject, SourceAttribute, MetadataAttribute): class Album(MainObject, SourceAttribute, MetadataAttribute):
def __init__( def __init__(
self, self,
id_: str = None, _id: str = None,
title: str = None, title: str = None,
language: pycountry.Languages = None, language: pycountry.Languages = None,
date: ID3Timestamp = None, date: ID3Timestamp = None,
@ -261,7 +192,7 @@ class Album(DatabaseObject, SourceAttribute, MetadataAttribute):
label_list: List[Type['Label']] = None, label_list: List[Type['Label']] = None,
**kwargs **kwargs
) -> None: ) -> None:
DatabaseObject.__init__(self, id_=id_, dynamic=dynamic, **kwargs) MainObject.__init__(self, _id=_id, dynamic=dynamic, **kwargs)
self.title: str = title self.title: str = title
self.album_status: AlbumStatus = album_status self.album_status: AlbumStatus = album_status
@ -384,10 +315,11 @@ All objects dependent on Artist
""" """
class Artist(DatabaseObject, SourceAttribute, MetadataAttribute): class Artist(MainObject, SourceAttribute, MetadataAttribute):
def __init__( def __init__(
self, self,
id_: str = None, _id: str = None,
dynamic: bool = False,
name: str = None, name: str = None,
source_list: List[Source] = None, source_list: List[Source] = None,
feature_song_list: List[Song] = None, feature_song_list: List[Song] = None,
@ -398,8 +330,11 @@ class Artist(DatabaseObject, SourceAttribute, MetadataAttribute):
country: CountryTyping = None, country: CountryTyping = None,
formed_in: ID3Timestamp = None, formed_in: ID3Timestamp = None,
label_list: List[Type['Label']] = None, label_list: List[Type['Label']] = None,
**kwargs
): ):
DatabaseObject.__init__(self, id_=id_) MainObject.__init__(self, _id=_id, dynamic=dynamic, **kwargs)
self.name: str = name
""" """
TODO implement album type and notes TODO implement album type and notes
@ -412,11 +347,13 @@ class Artist(DatabaseObject, SourceAttribute, MetadataAttribute):
i mean do as you want but there aint no strict rule about em so good luck i mean do as you want but there aint no strict rule about em so good luck
""" """
self.notes: FormattedText = notes or FormattedText() self.notes: FormattedText = notes or FormattedText()
"""
TODO
implement in db
"""
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.name: str = name
self.feature_song_collection: Collection = Collection( self.feature_song_collection: Collection = Collection(
data=feature_song_list, data=feature_song_list,
map_attributes=["title"], map_attributes=["title"],
@ -526,17 +463,18 @@ Label
""" """
class Label(DatabaseObject, SourceAttribute, MetadataAttribute): class Label(MainObject, SourceAttribute, MetadataAttribute):
def __init__( def __init__(
self, self,
id_: str = None, _id: str = None,
dynamic: bool = False,
name: str = None, name: str = None,
album_list: List[Album] = None, album_list: List[Album] = None,
current_artist_list: List[Artist] = None, current_artist_list: List[Artist] = None,
source_list: List[Source] = None, source_list: List[Source] = None,
**kwargs **kwargs
): ):
DatabaseObject.__init__(self, id_=id_) MainObject.__init__(self, _id=_id, dynamic=dynamic, **kwargs)
self.name: str = name self.name: str = name
@ -553,7 +491,11 @@ class Label(DatabaseObject, SourceAttribute, MetadataAttribute):
) )
self.source_list = source_list or [] self.source_list = source_list or []
self.additional_attributes = kwargs
album_list = property(fget=lambda self: self.album_collection.copy()) @property
current_artist_list = property(fget=lambda self: self.current_artist_collection.copy()) def album_list(self) -> List[Album]:
return self.album_collection.copy()
@property
def current_artist_list(self) -> List[Artist]:
self.current_artist_collection.copy()

View File

@ -133,7 +133,7 @@ class SourceAttribute:
This is a class that is meant to be inherited from. This is a class that is meant to be inherited from.
it adds the source_list attribute to a class it adds the source_list attribute to a class
""" """
_source_dict: Dict[object, List[Source]] _source_dict: Dict[SourcePages, List[Source]]
source_url_map: Dict[str, Source] source_url_map: Dict[str, Source]
def __new__(cls, **kwargs): def __new__(cls, **kwargs):

View File

@ -0,0 +1,35 @@
from typing import Optional
from pathlib import Path
from ..utils import shared
from .parents import DatabaseObject
class Target(DatabaseObject):
"""
create somehow like that
```python
# I know path is pointless, and I will change that (don't worry about backwards compatibility there)
Target(file="song.mp3", path="~/Music/genre/artist/album")
```
"""
def __init__(
self,
file: str = None,
path: str = None,
_id: str = None,
dynamic: bool = False,
relative_to_music_dir: bool = False
) -> None:
super().__init__(_id=_id, dynamic=dynamic)
self._file: Path = Path(file)
self._path = path
self.is_relative_to_music_dir: bool = relative_to_music_dir
@property
def file_path(self) -> Path:
if self.is_relative_to_music_dir:
return Path(shared.MUSIC_DIR, self._path, self._file)
return Path(self._path, self._file)