1
0
mirror of https://github.com/enpaul/kodak.git synced 2024-11-23 15:07:13 +00:00

Reimplement manip configuration settings

Add support for future color changes
Once and for all settle how crop/scaling will work
Add proper exceptions
This commit is contained in:
Ethan Paul 2021-10-29 01:32:04 -04:00
parent 67abbd5374
commit dff79571ba
No known key found for this signature in database
GPG Key ID: D0E2CBF1245E92BF
3 changed files with 119 additions and 69 deletions

View File

@ -1,3 +1,4 @@
import enum
import json import json
import os import os
from dataclasses import dataclass from dataclasses import dataclass
@ -5,18 +6,40 @@ from dataclasses import field
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from typing import Dict from typing import Dict
from typing import NamedTuple
from typing import Optional from typing import Optional
from typing import Sequence from typing import Set
from typing import Union from typing import Union
from kodak import constants from kodak import constants
from kodak import exceptions
class DimensionConfig(NamedTuple): def _get_int(var: str, default: Optional[int]) -> Optional[int]:
strategy: constants.DimensionStrategy return int(os.environ[var]) if var in os.environ else default
anchor: constants.Anchor
value: int
def _get_float(var: str, default: Optional[float]) -> Optional[float]:
return float(os.environ[var]) if var in os.environ else default
def _get_enum_by_name(
var: str, enumeration: enum.Enum, default: enum.Enum
) -> enum.Enum:
return enumeration[os.environ[var].upper()] if var in os.environ else default
def _get_enum_by_value(
var: str, enumeration: enum.Enum, default: enum.Enum
) -> enum.Enum:
return enumeration(os.environ[var].lower()) if var in os.environ else default
def _get_path(var: str, default: Union[str, Path]) -> Path:
return Path(os.environ.get(var, default)).expanduser().resolve()
def _get_bool(var: str, default: bool) -> bool:
return os.getenv(var, str(default)).lower() == "true"
@dataclass @dataclass
@ -29,9 +52,9 @@ class DatabaseSqliteConfig:
@classmethod @classmethod
def from_env(cls): def from_env(cls):
return cls( return cls(
path=Path(os.environ.get("KODAK_DB_SQLITE_PATH", cls.path)), path=_get_path("KODAK_DATABASE_SQLITE_PATH", cls.path),
pragmas=json.loads(os.environ["KODAK_DB_SQLITE_PRAGMAS"]) pragmas=json.loads(os.environ["KODAK_DATABASE_SQLITE_PRAGMAS"])
if "KODAK_DB_SQLITE_PRAGMAS" in os.environ if "KODAK_DATABASE_SQLITE_PRAGMAS" in os.environ
else constants.DEFAULT_SQLITE_PRAGMAS, else constants.DEFAULT_SQLITE_PRAGMAS,
) )
@ -43,16 +66,16 @@ class DatabaseMariaConfig:
username: str = "root" username: str = "root"
password: Optional[str] = None password: Optional[str] = None
port: int = 3306 port: int = 3306
schema: str = "fresnel" schema: str = "kodak"
@classmethod @classmethod
def from_env(cls): def from_env(cls):
return cls( return cls(
hostname=os.getenv("KODAK_DB_MARIA_HOSTNAME", cls.hostname), hostname=os.getenv("KODAK_DATABASE_MARIADB_HOSTNAME", cls.hostname),
username=os.getenv("KODAK_DB_MARIA_USERNAME", cls.username), username=os.getenv("KODAK_DATABASE_MARIADB_USERNAME", cls.username),
password=os.environ.get("KODAK_DB_MARIA_PASSWORD", cls.password), password=os.environ.get("KODAK_DATABASE_MARIADB_PASSWORD", cls.password),
port=int(os.environ.get("KODAK_DB_MARIA_PORT", cls.port)), port=_get_int("KODAK_DATABASE_MARIADB_PORT", cls.port),
schema=os.getenv("KODAK_DB_MARIA_SCHEMA", cls.schema), schema=os.getenv("KODAK_DATABASE_MARIADB_SCHEMA", cls.schema),
) )
@ -66,56 +89,86 @@ class DatabaseConfig:
@classmethod @classmethod
def from_env(cls): def from_env(cls):
return cls( return cls(
backend=constants.DatabaseBackend[os.environ["KODAK_DB_BACKEND"].upper()] backend=_get_enum_by_name(
if "KODAK_DB_BACKEND" in os.environ "KODAK_DATABASE_BACKEND", constants.DatabaseBackend, cls.backend
else cls.backend )
)
@dataclass
class ManipCropConfig:
horizontal: Optional[int] = None
vertical: Optional[int] = None
anchor: constants.CropAnchor = constants.CropAnchor.C
@classmethod
def from_env(cls, key: str):
return cls(
anchor=_get_enum_by_value(
f"KODAK_MANIP_{key}_CROP_ANCHOR", constants.CropAnchor, cls.anchor
),
horizontal=_get_int(f"KODAK_MANIP_{key}_CROP_HORIZONTAL", cls.horizontal),
vertical=_get_int(f"KODAK_MANIP_{key}_CROP_VERTICAL", cls.vertical),
)
@dataclass
class ManipScaleConfig:
horizontal: Optional[Union[int, float]] = None
vertical: Optional[Union[int, float]] = None
strategy: constants.ScaleStrategy = constants.ScaleStrategy.ABSOLUTE
@classmethod
def from_env(cls, key: str):
strategy = _get_enum_by_name(
f"KODAK_MANIP_{key}_SCALE_STRATEGY", constants.ScaleStrategy, cls.strategy
)
if strategy == constants.ScaleStrategy.ABSOLUTE:
parser = _get_int
elif strategy == constants.ScaleStrategy.RELATIVE:
parser = _get_float
else:
raise RuntimeError("This path should not be possible")
return cls(
strategy=strategy,
vertical=parser(f"KODAK_MANIP_{key}_SCALE_VERTICAL", cls.vertical),
horizontal=parser(f"KODAK_MANIP_{key}_SCALE_HORIZONTAL", cls.horizontal),
) )
@dataclass @dataclass
class ManipConfig: class ManipConfig:
name: str name: str
strategy: constants.DimensionStrategy = constants.DimensionStrategy.SCALE crop: ManipCropConfig = field(default_factory=ManipCropConfig.from_env)
anchor: constants.Anchor = constants.Anchor.C scale: ManipScaleConfig = field(default_factory=ManipScaleConfig.from_env)
formats: Sequence[constants.ImageFormat] = ( formats: Set[constants.ImageFormat] = field(
constants.ImageFormat.JPEG, default_factory=lambda: constants.DEFAULT_SUPPORTED_FORMATS
constants.ImageFormat.PNG,
) )
horizontal: Optional[Union[int, float]] = None black_and_white: bool = False
vertical: Optional[Union[int, float]] = None # TODO: Implement support for these settings
# brightness: int = 0
# contrast: int = 0
# sepia: bool = False
@classmethod @classmethod
def from_env(cls, key: str): def from_env(cls, key: str):
strategy = (
constants.DimensionStrategy[
os.environ[f"KODAK_MANIP_{key}_STRATEGY"].upper()
]
if f"KODAK_MANIP_{key}_STRATEGY" in os.environ
else cls.strategy
)
dimension_conversion = (
float if strategy == constants.DimensionStrategy.RELATIVE else int
)
return cls( return cls(
name=os.getenv(f"KODAK_MANIP_{key}_NAME", key.lower()), name=os.getenv(f"KODAK_MANIP_{key}_NAME", key.lower()),
strategy=strategy, crop=ManipCropConfig.from_env(key),
anchor=constants.Anchor(os.environ[f"KODAK_MANIP_{key}_ANCHOR"].lower()) scale=ManipScaleConfig.from_env(key),
if f"KODAK_MANIP_{key}_ANCHOR" in os.environ formats=set(
else cls.anchor, [
formats=[ constants.ImageFormat[item.strip().upper()]
constants.ImageFormat[item.upper()]
for item in os.environ[f"KODAK_MANIP_{key}_FORMATS"].split(",") for item in os.environ[f"KODAK_MANIP_{key}_FORMATS"].split(",")
] ]
)
if f"KODAK_MANIP_{key}_FORMATS" in os.environ if f"KODAK_MANIP_{key}_FORMATS" in os.environ
else cls.formats, else constants.DEFAULT_SUPPORTED_FORMATS,
horizontal=dimension_conversion(os.environ[f"KODAK_MANIP_{key}_HORIZONTAL"]) black_and_white=_get_bool(
if f"KODAK_MANIP_{key}_HORIZONTAL" in os.environ f"KODAK_MANIP_{key}_BLACK_AND_WHITE", cls.black_and_white
else cls.horizontal, ),
vertical=dimension_conversion(os.environ[f"KODAK_MANIP_{key}_VERTICAL"])
if f"KODAK_MANIP_{key}_VERTICAL" in os.environ
else cls.vertical,
) )
@ -138,17 +191,10 @@ class KodakConfig:
] ]
) )
return cls( return cls(
sourcedir=Path(os.environ.get("KODAK_SOURCEDIR", cls.sourcedir)) sourcedir=_get_path("KODAK_SOURCEDIR", cls.sourcedir),
.expanduser() manipdir=_get_path("KODAK_MANIPDIR", cls.manipdir),
.resolve(), expose_source=_get_bool("KODAK_EXPOSE_SOURCE", cls.expose_source),
manipdir=Path(os.environ.get("KODAK_MANIPDIR", cls.manipdir)) private=_get_bool("KODAK_PRIVATE", cls.private),
.expanduser()
.resolve(),
expose_source=os.getenv(
"KODAK_EXPOSE_SOURCE", str(cls.expose_source)
).lower()
== "true",
private=os.getenv("KODAK_PRIVATE", str(cls.private)).lower() == "true",
manips={name.lower(): ManipConfig.from_env(name) for name in manip_names}, manips={name.lower(): ManipConfig.from_env(name) for name in manip_names},
) )
@ -157,4 +203,4 @@ def load() -> KodakConfig:
try: try:
return KodakConfig.from_env() return KodakConfig.from_env()
except (ValueError, TypeError, IndexError, KeyError) as err: except (ValueError, TypeError, IndexError, KeyError) as err:
raise RuntimeError(err) raise exceptions.ConfigurationError(f"Failed to load configuration: {err}")

View File

@ -8,19 +8,17 @@ class DatabaseBackend(enum.Enum):
SQLITE = peewee.SqliteDatabase SQLITE = peewee.SqliteDatabase
class DimensionStrategy(enum.Enum): class ScaleStrategy(enum.Enum):
CROP = enum.auto()
SCALE = enum.auto()
RELATIVE = enum.auto() RELATIVE = enum.auto()
ABSOLUTE = enum.auto()
class ImageFormat(enum.Enum): class ImageFormat(enum.Enum):
JPEG = enum.auto() JPEG = enum.auto()
PNG = enum.auto() PNG = enum.auto()
GIF = enum.auto()
class Anchor(enum.Enum): class CropAnchor(enum.Enum):
TL = "top-left" TL = "top-left"
TC = "top-center" TC = "top-center"
TR = "top-center" TR = "top-center"
@ -39,3 +37,5 @@ DEFAULT_SQLITE_PRAGMAS = {
"ignore_check_constraints": 0, "ignore_check_constraints": 0,
"synchronous": 0, "synchronous": 0,
} }
DEFAULT_SUPPORTED_FORMATS = {ImageFormat.JPEG, ImageFormat.PNG}

View File

@ -37,3 +37,7 @@ class ServerError(KodakException):
class ImageFileRemovedError(ServerError): class ImageFileRemovedError(ServerError):
"""Image file removed from server""" """Image file removed from server"""
class ConfigurationError(ServerError):
"""Failed to load the application configuration"""