mirror of
https://github.com/enpaul/kodak.git
synced 2024-11-15 02:57:00 +00:00
Update database schema for new image/alias structure
This commit is contained in:
parent
1b7856c590
commit
158db1209b
@ -4,14 +4,16 @@ from typing import Tuple
|
|||||||
import peewee
|
import peewee
|
||||||
|
|
||||||
from kodak import constants
|
from kodak import constants
|
||||||
|
from kodak import exceptions
|
||||||
from kodak.configuration import KodakConfig
|
from kodak.configuration import KodakConfig
|
||||||
from kodak.database._shared import INTERFACE as interface
|
from kodak.database._shared import INTERFACE as interface
|
||||||
from kodak.database._shared import KodakModel
|
from kodak.database._shared import KodakModel
|
||||||
|
from kodak.database.access import AccessRecord
|
||||||
|
from kodak.database.alias import AliasRecord
|
||||||
from kodak.database.image import ImageRecord
|
from kodak.database.image import ImageRecord
|
||||||
from kodak.database.thumbnail import ThumbnailRecord
|
|
||||||
|
|
||||||
|
|
||||||
MODELS: Tuple[KodakModel, ...] = (ImageRecord, ThumbnailRecord)
|
MODELS: Tuple[KodakModel, ...] = (ImageRecord, AliasRecord, AccessRecord)
|
||||||
|
|
||||||
|
|
||||||
def initialize(config: KodakConfig):
|
def initialize(config: KodakConfig):
|
||||||
@ -26,13 +28,13 @@ def initialize(config: KodakConfig):
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
if config.database.backend == constants.SupportedDatabaseBackend.SQLITE:
|
if config.database.backend == constants.DatabaseBackend.SQLITE:
|
||||||
logger.debug("Using SQLite database backend")
|
logger.debug("Using SQLite database backend")
|
||||||
logger.debug(f"Applying SQLite pragmas: {config.database.sqlite.pragmas}")
|
logger.debug(f"Applying SQLite pragmas: {config.database.sqlite.pragmas}")
|
||||||
database = peewee.SqliteDatabase(
|
database = peewee.SqliteDatabase(
|
||||||
config.database.sqlite.path, pragmas=config.database.sqlite.pragmas
|
config.database.sqlite.path, pragmas=config.database.sqlite.pragmas
|
||||||
)
|
)
|
||||||
elif config.database.backend == constants.SupportedDatabaseBackend.MARIADB:
|
elif config.database.backend == constants.DatabaseBackend.MARIADB:
|
||||||
logger.debug("Using MariaDB database backend")
|
logger.debug("Using MariaDB database backend")
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Configuring MariaDB:"
|
"Configuring MariaDB:"
|
||||||
@ -48,7 +50,7 @@ def initialize(config: KodakConfig):
|
|||||||
charset="utf8mb4",
|
charset="utf8mb4",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise exceptions.ConfigurationError(
|
||||||
f"Invalid storage backend in configuration: {config.database.backend}"
|
f"Invalid storage backend in configuration: {config.database.backend}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
import enum
|
||||||
|
import hashlib
|
||||||
import uuid
|
import uuid
|
||||||
|
from typing import NamedTuple
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
import peewee
|
import peewee
|
||||||
|
|
||||||
@ -7,7 +11,82 @@ import peewee
|
|||||||
INTERFACE = peewee.DatabaseProxy()
|
INTERFACE = peewee.DatabaseProxy()
|
||||||
|
|
||||||
|
|
||||||
|
class Checksum(NamedTuple):
|
||||||
|
"""Checksum data container
|
||||||
|
|
||||||
|
:param algorithm: Hashing algorithm, must be supported by Python's hashlib
|
||||||
|
:param digest: Hex digest of the hash
|
||||||
|
"""
|
||||||
|
|
||||||
|
algorithm: str
|
||||||
|
digest: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_hash(cls, data: hashlib._hashlib.HASH): # pylint: disable=protected-access
|
||||||
|
"""Construct from a hashlib object"""
|
||||||
|
return cls(algorithm=data.name, digest=data.hexdigest())
|
||||||
|
|
||||||
|
|
||||||
|
class EnumField(peewee.CharField):
|
||||||
|
"""Custom field for storing enums"""
|
||||||
|
|
||||||
|
def __init__(self, enumeration: Type[enum.Enum], *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.enumeration = enumeration
|
||||||
|
|
||||||
|
def db_value(self, value: enum.Enum) -> str:
|
||||||
|
"""Convert the enum value to the database string
|
||||||
|
|
||||||
|
:param value: Enum item to store the name of
|
||||||
|
:raises peewee.IntegrityError: When the item passed to ``value`` is not in the field's enum
|
||||||
|
:returns: The name of the enum item passed to ``value``
|
||||||
|
"""
|
||||||
|
if not isinstance(value, self.enumeration):
|
||||||
|
raise peewee.IntegrityError(
|
||||||
|
f"Enum {self.enumeration.__name__} has no value '{value}'"
|
||||||
|
)
|
||||||
|
return value.name
|
||||||
|
|
||||||
|
def python_value(self, value: str) -> enum.Enum:
|
||||||
|
"""Convert the stored string to the corresponding enum
|
||||||
|
|
||||||
|
:param value: Name of the item from the field's enum to return
|
||||||
|
:raises peewee.IntegrityError: When the name passed to ``value`` does not correspond to an
|
||||||
|
item in the field's enum
|
||||||
|
:returns: The enum item with the name passed to ``value``
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.enumeration[value]
|
||||||
|
except KeyError:
|
||||||
|
raise peewee.InterfaceError(
|
||||||
|
f"Enum {self.enumeration.__name__} has no value with name '{value}'"
|
||||||
|
) from None
|
||||||
|
|
||||||
|
|
||||||
|
class ChecksumField(peewee.CharField):
|
||||||
|
"""Field for storing checksum hashes in the database
|
||||||
|
|
||||||
|
.. note:: The reason for implementing this is to protect against future changes to the hashing
|
||||||
|
algorithm. Just storing the digest means that if the hashing algorithm is ever
|
||||||
|
changed (for performance, etc) then any existing records will be invalidated. By
|
||||||
|
storing the hashing algorithm with the digest we can protect against that possibility.
|
||||||
|
A custom container needs to be implemented because the builtin hashlib has no way to
|
||||||
|
recreate a hash object from the algorithm+digest without the original data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def db_value(self, value: Checksum) -> str:
|
||||||
|
"""Serialize the checkstum to a database string"""
|
||||||
|
return f"{value.algorithm}:{value.digest}"
|
||||||
|
|
||||||
|
def python_value(self, value: str) -> Checksum:
|
||||||
|
"""Deserailize a string to a checksum container"""
|
||||||
|
alg, _, digest = value.partition(":")
|
||||||
|
return Checksum(algorithm=alg, digest=digest)
|
||||||
|
|
||||||
|
|
||||||
class KodakModel(peewee.Model):
|
class KodakModel(peewee.Model):
|
||||||
|
"""Base model for defining common fields and attaching database"""
|
||||||
|
|
||||||
class Meta: # pylint: disable=too-few-public-methods,missing-class-docstring
|
class Meta: # pylint: disable=too-few-public-methods,missing-class-docstring
|
||||||
database = INTERFACE
|
database = INTERFACE
|
||||||
|
|
||||||
|
9
kodak/database/access.py
Normal file
9
kodak/database/access.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import peewee
|
||||||
|
|
||||||
|
from kodak.database._shared import KodakModel
|
||||||
|
|
||||||
|
|
||||||
|
class AccessRecord(KodakModel):
|
||||||
|
"""Model for access keys when operating in private mode"""
|
||||||
|
|
||||||
|
password = peewee.CharField(null=False)
|
13
kodak/database/alias.py
Normal file
13
kodak/database/alias.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import peewee
|
||||||
|
|
||||||
|
from kodak.database._shared import ChecksumField
|
||||||
|
from kodak.database._shared import KodakModel
|
||||||
|
from kodak.database.image import ImageRecord
|
||||||
|
|
||||||
|
|
||||||
|
class AliasRecord(KodakModel):
|
||||||
|
"""Model for manipulated image records"""
|
||||||
|
|
||||||
|
parent = peewee.ForeignKeyField(ImageRecord, null=False)
|
||||||
|
name = peewee.CharField(null=False)
|
||||||
|
checksum = ChecksumField(null=False)
|
@ -1,30 +1,15 @@
|
|||||||
import json
|
|
||||||
import uuid
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import peewee
|
import peewee
|
||||||
|
|
||||||
|
from kodak import constants
|
||||||
|
from kodak.database._shared import ChecksumField
|
||||||
|
from kodak.database._shared import EnumField
|
||||||
from kodak.database._shared import KodakModel
|
from kodak.database._shared import KodakModel
|
||||||
|
|
||||||
|
|
||||||
class ImageRecord(KodakModel):
|
class ImageRecord(KodakModel):
|
||||||
"""Database record for"""
|
"""Model for source images"""
|
||||||
|
|
||||||
width = peewee.IntegerField(null=False)
|
name = peewee.Charfield(null=False)
|
||||||
height = peewee.IntegerField(null=False)
|
format = EnumField(constants.ImageFormat, null=False)
|
||||||
format = peewee.CharField(null=False)
|
|
||||||
deleted = peewee.BooleanField(null=False, default=False)
|
deleted = peewee.BooleanField(null=False, default=False)
|
||||||
public = peewee.BooleanField(null=False, default=False)
|
checksum = ChecksumField(null=False)
|
||||||
owner = peewee.UUIDField(null=False)
|
|
||||||
sha256 = peewee.CharField(null=False)
|
|
||||||
_readable = peewee.CharField(null=False, default="[]")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def readable(self) -> List[uuid.UUID]:
|
|
||||||
"""List of UUIDs corresponding to accounts that can read the file"""
|
|
||||||
return [uuid.UUID(item) for item in json.loads(self._readable)]
|
|
||||||
|
|
||||||
@readable.setter
|
|
||||||
def readable(self, value: List[uuid.UUID]):
|
|
||||||
"""Update the list of UUIDs for accounts that can read the file"""
|
|
||||||
self._readable = json.dumps([str(item) for item in value])
|
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import peewee
|
|
||||||
|
|
||||||
from kodak.database._shared import KodakModel
|
|
||||||
from kodak.database.image import ImageRecord
|
|
||||||
|
|
||||||
|
|
||||||
class ThumbnailRecord(KodakModel):
|
|
||||||
|
|
||||||
parent = peewee.ForeignKeyField(ImageRecord)
|
|
||||||
width = peewee.IntegerField(null=False)
|
|
||||||
height = peewee.IntegerField(null=False)
|
|
Loading…
Reference in New Issue
Block a user