refactored and cleaned up data objects
This commit is contained in:
parent
819111c53a
commit
5f4912bda5
@ -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."""
|
||||
|
||||
|
27
src/music_kraken/objects/lyrics.py
Normal file
27
src/music_kraken/objects/lyrics.py
Normal 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
|
@ -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):
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
35
src/music_kraken/objects/target.py
Normal file
35
src/music_kraken/objects/target.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user