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):
notes: str = CharField(null=True)
class Meta:
database = None
@ -38,33 +40,41 @@ class BaseModel(Model):
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."""
title: str = CharField(null=True)
label: str = CharField(null=True)
album_status: str = CharField(null=True)
album_type: str = CharField(null=True)
language: str = CharField(null=True)
date: str = CharField(null=True)
date_format: str = CharField(null=True)
country: str = CharField(null=True)
barcode: str = CharField(null=True)
albumsort: int = IntegerField(null=True)
is_split: bool = BooleanField(default=False)
albumsort: int = IntegerField(null=True)
class Artist(BaseModel):
"""A class representing an artist in the music database."""
name: str = CharField()
notes: str = CharField()
name: str = CharField(null=True)
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):
"""A class representing a song in the music database."""
name: str = CharField(null=True)
title: str = CharField(null=True)
isrc: str = CharField(null=True)
length: int = IntegerField(null=True)
tracksort: int = IntegerField(null=True)
@ -87,6 +97,12 @@ class Lyrics(BaseModel):
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):
"""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
TITLE = "TIT2"
ISRC = "TSRC"
LENGTH = "TLEN" # in milliseconds
LENGTH = "TLEN" # in milliseconds
DATE = "TYER"
TRACKNUMBER = "TRCK"
TOTALTRACKS = "TRCK" # Stored in the same frame with TRACKNUMBER, separated by '/': e.g. '4/9'.
@ -193,17 +193,14 @@ class ID3Timestamp:
return "%Y"
return ""
def get_timestamp(self) -> str:
time_format = self.get_time_format()
return self.date_obj.strftime(time_format)
def get_timestamp_w_format(self) -> Tuple[str, str]:
time_format = self.get_time_format()
return time_format, self.date_obj.strftime(time_format)
@classmethod
def strptime(cls, time_stamp: str, format: str):
"""

View File

@ -1,96 +1,48 @@
from typing import Optional
import uuid
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:
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.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
if the object is dynamic, it raises an error
"""
if self.empty:
return None
if self.dynamic:
raise ValueError("Dynamic objects have no idea, because they are not in the database")
class MainObject(DatabaseObject):
"""
This is the parent class for all "main" data objects:
- Song
- Album
- Artist
- Label
if self.id_ is None:
self.id_ = str(uuid.uuid4())
logger.info(f"id for {self.__str__()} isn't set. Setting to {self.id_}")
It has all the functionality of the "DatabaseObject" (it inherits from said class)
but also some added functions as well.
"""
def __init__(self, _id: str = None, dynamic: bool = False, **kwargs):
super().__init__(_id=_id, dynamic=dynamic, **kwargs)
return self.id_
def get_reference(self) -> Reference:
return Reference(self.id)
self.additional_arguments: dict = kwargs
def get_options(self) -> list:
"""
makes only sense in
- artist
- song
- album
"""
return []
def get_option_string(self) -> str:
"""
makes only sense in
- artist
- song
- album
"""
return ""
id = property(fget=get_id)
reference = property(fget=get_reference)
options = property(fget=get_options)
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
from typing import List, Optional, Type, Dict
import pycountry
import copy
from .metadata import (
Mapping as id3Mapping,
@ -10,12 +9,11 @@ from .metadata import (
)
from src.music_kraken.utils.shared import (
MUSIC_DIR,
DATABASE_LOGGER as logger
DATABASE_LOGGER as LOGGER
)
from .parents import (
DatabaseObject,
Reference,
SongAttribute
MainObject
)
from .source import (
Source,
@ -26,6 +24,8 @@ from .source import (
from .formatted_text import FormattedText
from .collection import Collection
from .album import AlbumType, AlbumStatus
from .lyrics import Lyrics
from .target import Target
"""
All Objects dependent
@ -34,77 +34,7 @@ All Objects dependent
CountryTyping = type(list(pycountry.countries)[0])
class Target(DatabaseObject, SongAttribute):
"""
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 Song(MainObject, SourceAttribute, MetadataAttribute):
"""
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.
@ -114,14 +44,15 @@ class Song(DatabaseObject, SourceAttribute, MetadataAttribute):
def __init__(
self,
id_: str = None,
_id: str = None,
dynamic: bool = False,
title: str = None,
isrc: str = None,
length: int = None,
tracksort: int = None,
genre: str = None,
source_list: List[Source] = None,
target_list: Target = None,
target_list: List[Target] = None,
lyrics_list: List[Lyrics] = None,
album_list: Type['Album'] = 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:
"""
super().__init__(id_=id_, **kwargs)
MainObject.__init__(self, _id=_id, dynamic=dynamic, **kwargs)
# attributes
self.title: str = title
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__(
self,
id_: str = None,
_id: str = None,
title: str = None,
language: pycountry.Languages = None,
date: ID3Timestamp = None,
@ -261,7 +192,7 @@ class Album(DatabaseObject, SourceAttribute, MetadataAttribute):
label_list: List[Type['Label']] = None,
**kwargs
) -> None:
DatabaseObject.__init__(self, id_=id_, dynamic=dynamic, **kwargs)
MainObject.__init__(self, _id=_id, dynamic=dynamic, **kwargs)
self.title: str = title
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__(
self,
id_: str = None,
_id: str = None,
dynamic: bool = False,
name: str = None,
source_list: List[Source] = None,
feature_song_list: List[Song] = None,
@ -398,8 +330,11 @@ class Artist(DatabaseObject, SourceAttribute, MetadataAttribute):
country: CountryTyping = None,
formed_in: ID3Timestamp = 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
@ -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
"""
self.notes: FormattedText = notes or FormattedText()
"""
TODO
implement in db
"""
self.lyrical_themes: List[str] = lyrical_themes or []
self.general_genre = general_genre
self.name: str = name
self.feature_song_collection: Collection = Collection(
data=feature_song_list,
map_attributes=["title"],
@ -526,17 +463,18 @@ Label
"""
class Label(DatabaseObject, SourceAttribute, MetadataAttribute):
class Label(MainObject, SourceAttribute, MetadataAttribute):
def __init__(
self,
id_: str = None,
_id: str = None,
dynamic: bool = False,
name: str = None,
album_list: List[Album] = None,
current_artist_list: List[Artist] = None,
source_list: List[Source] = None,
**kwargs
):
DatabaseObject.__init__(self, id_=id_)
MainObject.__init__(self, _id=_id, dynamic=dynamic, **kwargs)
self.name: str = name
@ -553,7 +491,11 @@ class Label(DatabaseObject, SourceAttribute, MetadataAttribute):
)
self.source_list = source_list or []
self.additional_attributes = kwargs
album_list = property(fget=lambda self: self.album_collection.copy())
current_artist_list = property(fget=lambda self: self.current_artist_collection.copy())
@property
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.
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]
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)