feat: build
This commit is contained in:
		
							
								
								
									
										0
									
								
								music_kraken/database/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								music_kraken/database/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										197
									
								
								music_kraken/database/data_models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								music_kraken/database/data_models.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,197 @@
 | 
			
		||||
from typing import List, Union, Type, Optional
 | 
			
		||||
from peewee import (
 | 
			
		||||
    SqliteDatabase,
 | 
			
		||||
    PostgresqlDatabase,
 | 
			
		||||
    MySQLDatabase,
 | 
			
		||||
    Model,
 | 
			
		||||
    CharField,
 | 
			
		||||
    IntegerField,
 | 
			
		||||
    BooleanField,
 | 
			
		||||
    ForeignKeyField,
 | 
			
		||||
    TextField
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
**IMPORTANT**:
 | 
			
		||||
 | 
			
		||||
never delete, modify the datatype or add constrains to ANY existing collumns,
 | 
			
		||||
between the versions, that gets pushed out to the users.
 | 
			
		||||
Else my function can't update legacy databases, to new databases, 
 | 
			
		||||
while keeping the data of the old ones.
 | 
			
		||||
 | 
			
		||||
EVEN if that means to for example keep decimal values stored in strings.
 | 
			
		||||
(not in my codebase though.)
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseModel(Model):
 | 
			
		||||
    notes: str = CharField(null=True)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        database = None
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def Use(cls, database: Union[SqliteDatabase, PostgresqlDatabase, MySQLDatabase]) -> Model:
 | 
			
		||||
        cls._meta.database = database
 | 
			
		||||
        return cls
 | 
			
		||||
 | 
			
		||||
    def use(self, database: Union[SqliteDatabase, PostgresqlDatabase, MySQLDatabase]) -> Model:
 | 
			
		||||
        self._meta.database = database
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
class ObjectModel(BaseModel):
 | 
			
		||||
    id: str = CharField(primary_key=True)
 | 
			
		||||
 | 
			
		||||
class MainModel(BaseModel):
 | 
			
		||||
    additional_arguments: str = CharField(null=True)
 | 
			
		||||
    notes: str = CharField(null=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Song(MainModel):
 | 
			
		||||
    """A class representing a song in the music database."""
 | 
			
		||||
 | 
			
		||||
    title: str = CharField(null=True)
 | 
			
		||||
    isrc: str = CharField(null=True)
 | 
			
		||||
    length: int = IntegerField(null=True)
 | 
			
		||||
    tracksort: int = IntegerField(null=True)
 | 
			
		||||
    genre: str = CharField(null=True)
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
class Album(MainModel):
 | 
			
		||||
    """A class representing an album in the music database."""
 | 
			
		||||
 | 
			
		||||
    title: str = CharField(null=True)
 | 
			
		||||
    album_status: str = CharField(null=True)
 | 
			
		||||
    album_type: str = CharField(null=True)
 | 
			
		||||
    language: str = CharField(null=True)
 | 
			
		||||
    date_string: str = CharField(null=True)
 | 
			
		||||
    date_format: str = CharField(null=True)
 | 
			
		||||
    barcode: str = CharField(null=True)
 | 
			
		||||
    albumsort: int = IntegerField(null=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Artist(MainModel):
 | 
			
		||||
    """A class representing an artist in the music database."""
 | 
			
		||||
 | 
			
		||||
    name: str = CharField(null=True)
 | 
			
		||||
    country: str = CharField(null=True)
 | 
			
		||||
    formed_in_date: str = CharField(null=True)
 | 
			
		||||
    formed_in_format: str = CharField(null=True)
 | 
			
		||||
    general_genre: str = CharField(null=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Label(MainModel):
 | 
			
		||||
    name: str = CharField(null=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Target(ObjectModel):
 | 
			
		||||
    """A class representing a target of a song in the music database."""
 | 
			
		||||
 | 
			
		||||
    file: str = CharField()
 | 
			
		||||
    path: str = CharField()
 | 
			
		||||
    song = ForeignKeyField(Song, backref='targets')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Lyrics(ObjectModel):
 | 
			
		||||
    """A class representing lyrics of a song in the music database."""
 | 
			
		||||
 | 
			
		||||
    text: str = TextField()
 | 
			
		||||
    language: str = CharField()
 | 
			
		||||
    song = ForeignKeyField(Song, backref='lyrics')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Source(BaseModel):
 | 
			
		||||
    """A class representing a source of a song in the music database."""
 | 
			
		||||
    ContentTypes = Union[Song, Album, Artist, Lyrics]
 | 
			
		||||
 | 
			
		||||
    page: str = CharField()
 | 
			
		||||
    url: str = CharField()
 | 
			
		||||
 | 
			
		||||
    content_type: str = CharField()
 | 
			
		||||
    content_id: int = CharField()
 | 
			
		||||
    # content: ForeignKeyField = ForeignKeyField('self', backref='content_items', null=True)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def content_object(self) -> Union[Song, Album, Artist]:
 | 
			
		||||
        """Get the content associated with the source as an object."""
 | 
			
		||||
        if self.content_type == 'Song':
 | 
			
		||||
            return Song.get(Song.id == self.content_id)
 | 
			
		||||
        if self.content_type == 'Album':
 | 
			
		||||
            return Album.get(Album.id == self.content_id)
 | 
			
		||||
        if self.content_type == 'Artist':
 | 
			
		||||
            return Artist.get(Artist.id == self.content_id)
 | 
			
		||||
        if self.content_type == 'Label':
 | 
			
		||||
            return Label.get(Label.id == self.content_id)
 | 
			
		||||
        if self.content_type == 'Lyrics':
 | 
			
		||||
            return Lyrics.get(Lyrics.id == self.content_id)
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    @content_object.setter
 | 
			
		||||
    def content_object(self, value: Union[Song, Album, Artist]) -> None:
 | 
			
		||||
        """Set the content associated with the source as an object."""
 | 
			
		||||
        self.content_type = value.__class__.__name__
 | 
			
		||||
        self.content_id = value.id
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SongArtist(BaseModel):
 | 
			
		||||
    """A class representing the relationship between a song and an artist."""
 | 
			
		||||
 | 
			
		||||
    song: ForeignKeyField = ForeignKeyField(Song, backref='song_artists')
 | 
			
		||||
    artist: ForeignKeyField = ForeignKeyField(Artist, backref='song_artists')
 | 
			
		||||
    is_feature: bool = BooleanField(default=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ArtistAlbum(BaseModel):
 | 
			
		||||
    """A class representing the relationship between an album and an artist."""
 | 
			
		||||
 | 
			
		||||
    album: ForeignKeyField = ForeignKeyField(Album, backref='album_artists')
 | 
			
		||||
    artist: ForeignKeyField = ForeignKeyField(Artist, backref='album_artists')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AlbumSong(BaseModel):
 | 
			
		||||
    """A class representing the relationship between an album and an song."""
 | 
			
		||||
    album: ForeignKeyField = ForeignKeyField(Album, backref='album_artists')
 | 
			
		||||
    song: ForeignKeyField = ForeignKeyField(Song, backref='album_artists')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LabelAlbum(BaseModel):
 | 
			
		||||
    label: ForeignKeyField = ForeignKeyField(Label, backref='label_album')
 | 
			
		||||
    album: ForeignKeyField = ForeignKeyField(Album, backref='label_album')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LabelArtist(BaseModel):
 | 
			
		||||
    label: ForeignKeyField = ForeignKeyField(Label, backref='label_artist')
 | 
			
		||||
    artist: ForeignKeyField = ForeignKeyField(Artist, backref='label_artists')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ALL_MODELS = [
 | 
			
		||||
    Song,
 | 
			
		||||
    Album,
 | 
			
		||||
    Artist,
 | 
			
		||||
    Source,
 | 
			
		||||
    Lyrics,
 | 
			
		||||
    ArtistAlbum,
 | 
			
		||||
    Target,
 | 
			
		||||
    SongArtist
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    database_1 = SqliteDatabase(":memory:")
 | 
			
		||||
    database_1.create_tables([Song.Use(database_1)])
 | 
			
		||||
    database_2 = SqliteDatabase(":memory:")
 | 
			
		||||
    database_2.create_tables([Song.Use(database_2)])
 | 
			
		||||
 | 
			
		||||
    # creating songs, adding it to db_2 if i is even, else to db_1
 | 
			
		||||
    for i in range(100):
 | 
			
		||||
        song = Song(name=str(i) + "hs")
 | 
			
		||||
 | 
			
		||||
        db_to_use = database_2 if i % 2 == 0 else database_1
 | 
			
		||||
        song.use(db_to_use).save()
 | 
			
		||||
 | 
			
		||||
    print("database 1")
 | 
			
		||||
    for song in Song.Use(database_1).select():
 | 
			
		||||
        print(song.name)
 | 
			
		||||
 | 
			
		||||
    print("database 2")
 | 
			
		||||
    for song in Song.Use(database_1).select():
 | 
			
		||||
        print(song.name)
 | 
			
		||||
							
								
								
									
										188
									
								
								music_kraken/database/database.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								music_kraken/database/database.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,188 @@
 | 
			
		||||
# Standard library
 | 
			
		||||
from typing import Optional, Union, List
 | 
			
		||||
from enum import Enum
 | 
			
		||||
from playhouse.migrate import *
 | 
			
		||||
 | 
			
		||||
# third party modules
 | 
			
		||||
from peewee import (
 | 
			
		||||
    SqliteDatabase,
 | 
			
		||||
    MySQLDatabase,
 | 
			
		||||
    PostgresqlDatabase,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# own modules
 | 
			
		||||
from . import (
 | 
			
		||||
    data_models,
 | 
			
		||||
    write
 | 
			
		||||
)
 | 
			
		||||
from .. import objects
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DatabaseType(Enum):
 | 
			
		||||
    SQLITE = "sqlite"
 | 
			
		||||
    POSTGRESQL = "postgresql"
 | 
			
		||||
    MYSQL = "mysql"
 | 
			
		||||
 | 
			
		||||
class Database:
 | 
			
		||||
    database: Union[SqliteDatabase, PostgresqlDatabase, MySQLDatabase]
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
            self,
 | 
			
		||||
            db_type: DatabaseType,
 | 
			
		||||
            db_name: str,
 | 
			
		||||
            db_user: Optional[str] = None,
 | 
			
		||||
            db_password: Optional[str] = None,
 | 
			
		||||
            db_host: Optional[str] = None,
 | 
			
		||||
            db_port: Optional[int] = None
 | 
			
		||||
    ):
 | 
			
		||||
        self.db_type = db_type
 | 
			
		||||
        self.db_name = db_name
 | 
			
		||||
        self.db_user = db_user
 | 
			
		||||
        self.db_password = db_password
 | 
			
		||||
        self.db_host = db_host
 | 
			
		||||
        self.db_port = db_port
 | 
			
		||||
 | 
			
		||||
        self.initialize_database()
 | 
			
		||||
 | 
			
		||||
    def create_database(self) -> Union[SqliteDatabase, PostgresqlDatabase, MySQLDatabase]:
 | 
			
		||||
        """Create a database instance based on the configured database type and parameters.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            The created database instance, or None if an invalid database type was specified.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        # SQLITE
 | 
			
		||||
        if self.db_type == DatabaseType.SQLITE:
 | 
			
		||||
            return SqliteDatabase(self.db_name)
 | 
			
		||||
 | 
			
		||||
        # POSTGRES
 | 
			
		||||
        if self.db_type == DatabaseType.POSTGRESQL:
 | 
			
		||||
            return PostgresqlDatabase(
 | 
			
		||||
                self.db_name,
 | 
			
		||||
                user=self.db_user,
 | 
			
		||||
                password=self.db_password,
 | 
			
		||||
                host=self.db_host,
 | 
			
		||||
                port=self.db_port,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        # MYSQL
 | 
			
		||||
        if self.db_type == DatabaseType.MYSQL:
 | 
			
		||||
            return MySQLDatabase(
 | 
			
		||||
                self.db_name,
 | 
			
		||||
                user=self.db_user,
 | 
			
		||||
                password=self.db_password,
 | 
			
		||||
                host=self.db_host,
 | 
			
		||||
                port=self.db_port,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        raise ValueError("Invalid database type specified.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def migrator(self) -> SchemaMigrator:
 | 
			
		||||
        if self.db_type == DatabaseType.SQLITE:
 | 
			
		||||
            return SqliteMigrator(self.database)
 | 
			
		||||
        
 | 
			
		||||
        if self.db_type == DatabaseType.MYSQL:
 | 
			
		||||
            return MySQLMigrator(self.database)
 | 
			
		||||
    
 | 
			
		||||
        if self.db_type == DatabaseType.POSTGRESQL:
 | 
			
		||||
            return PostgresqlMigrator(self.database)
 | 
			
		||||
 | 
			
		||||
    def initialize_database(self):
 | 
			
		||||
        """
 | 
			
		||||
        Connect to the database
 | 
			
		||||
        initialize the previously defined databases
 | 
			
		||||
        create tables if they don't exist.
 | 
			
		||||
        """
 | 
			
		||||
        self.database = self.create_database()
 | 
			
		||||
        self.database.connect()
 | 
			
		||||
        
 | 
			
		||||
        migrator = self.migrator
 | 
			
		||||
        
 | 
			
		||||
        for model in data_models.ALL_MODELS:
 | 
			
		||||
            model = model.Use(self.database)
 | 
			
		||||
            
 | 
			
		||||
            if self.database.table_exists(model):
 | 
			
		||||
                migration_operations = [
 | 
			
		||||
                    migrator.add_column(
 | 
			
		||||
                        "some field", field[0], field[1]
 | 
			
		||||
                    )
 | 
			
		||||
                    for field in model._meta.fields.items()
 | 
			
		||||
                ]
 | 
			
		||||
                
 | 
			
		||||
                migrate(*migration_operations)
 | 
			
		||||
            else:
 | 
			
		||||
                self.database.create_tables([model], safe=True)
 | 
			
		||||
 | 
			
		||||
        #self.database.create_tables([model.Use(self.database) for model in data_models.ALL_MODELS], safe=True)
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        upgrade old databases. 
 | 
			
		||||
        If a collumn has been added in a new version this adds it to old Tables, 
 | 
			
		||||
        without deleting the data in legacy databases
 | 
			
		||||
        """
 | 
			
		||||
        
 | 
			
		||||
        for model in data_models.ALL_MODELS:
 | 
			
		||||
            model = model.Use(self.database)
 | 
			
		||||
            
 | 
			
		||||
            
 | 
			
		||||
            
 | 
			
		||||
            print(model._meta.fields)
 | 
			
		||||
 | 
			
		||||
    def push(self, database_object: objects.DatabaseObject):
 | 
			
		||||
        """
 | 
			
		||||
        Adds a new music object to the database using the corresponding method from the `write` session.
 | 
			
		||||
        When possible, rather use the `push_many` function.
 | 
			
		||||
        This gets even more important, when using a remote database server.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            database_object (objects.MusicObject): The music object to add to the database.
 | 
			
		||||
 | 
			
		||||
        Returns:
 | 
			
		||||
            The newly added music object.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        with write.WritingSession(self.database) as writing_session:
 | 
			
		||||
            if isinstance(database_object, objects.Song):
 | 
			
		||||
                return writing_session.add_song(database_object)
 | 
			
		||||
            
 | 
			
		||||
            if isinstance(database_object, objects.Album):
 | 
			
		||||
                return writing_session.add_album(database_object)
 | 
			
		||||
            
 | 
			
		||||
            if isinstance(database_object, objects.Artist):
 | 
			
		||||
                return writing_session.add_artist(database_object)
 | 
			
		||||
 | 
			
		||||
    def push_many(self, database_objects: List[objects.DatabaseObject]) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Adds a list of MusicObject instances to the database.
 | 
			
		||||
        This function sends only needs one querry for each type of table added.
 | 
			
		||||
        Beware that if you have for example an object like this:
 | 
			
		||||
        - Album
 | 
			
		||||
        - Song
 | 
			
		||||
        - Song
 | 
			
		||||
        you already have 3 different Tables.
 | 
			
		||||
    
 | 
			
		||||
        Unlike the function `push`, this function doesn't return the added database objects.
 | 
			
		||||
 | 
			
		||||
        Args:
 | 
			
		||||
            database_objects: List of MusicObject instances to be added to the database.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        with write.WritingSession(self.database) as writing_session:
 | 
			
		||||
            for obj in database_objects:
 | 
			
		||||
                if isinstance(obj, objects.Song):
 | 
			
		||||
                    writing_session.add_song(obj)
 | 
			
		||||
                    continue
 | 
			
		||||
                
 | 
			
		||||
                if isinstance(obj, objects.Album):
 | 
			
		||||
                    writing_session.add_album(obj)
 | 
			
		||||
                    continue
 | 
			
		||||
                
 | 
			
		||||
                if isinstance(obj, objects.Artist):
 | 
			
		||||
                    writing_session.add_artist(obj)
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def __del__(self):
 | 
			
		||||
        self.database.close()
 | 
			
		||||
		Reference in New Issue
	
	Block a user