diff --git a/.pylintrc b/.pylintrc index dcd96f3..9d3bb39 100644 --- a/.pylintrc +++ b/.pylintrc @@ -17,6 +17,8 @@ disable=logging-fstring-interpolation ,typecheck ,wrong-import-order ,wrong-import-position + ,missing-module-docstring + ,fixme [REPORTS] diff --git a/kodak/configuration.py b/kodak/configuration.py index 6307a2b..1e74a9e 100644 --- a/kodak/configuration.py +++ b/kodak/configuration.py @@ -44,13 +44,20 @@ def _get_bool(var: str, default: bool) -> bool: @dataclass class DatabaseSqliteConfig: - path: Path = Path.cwd() / "fresnel.db" + """SQLite database backend configuration options + + :param path: Path to the SQLite database file + :param pragmas: Mapping of SQLite pragmas to apply to the database connection + """ + + path: Path = Path.cwd() / "kodak.db" pragmas: Dict[str, Any] = field( default_factory=lambda: constants.DEFAULT_SQLITE_PRAGMAS ) @classmethod def from_env(cls): + """Build dataclass from environment""" return cls( path=_get_path("KODAK_DATABASE_SQLITE_PATH", cls.path), pragmas=json.loads(os.environ["KODAK_DATABASE_SQLITE_PRAGMAS"]) @@ -61,6 +68,14 @@ class DatabaseSqliteConfig: @dataclass class DatabaseMariaConfig: + """MariaDB database backend configuration options + + :param hostname: Hostname or IP address of the host running the database server + :param username: Username of the account to use for connecting to the database server + :param password: Password for the account to use for connecting to the database server + :param port: Port on the host that the database server is listening on + :param schema: Database schema that the application should use + """ hostname: str = "localhost" username: str = "root" @@ -70,6 +85,7 @@ class DatabaseMariaConfig: @classmethod def from_env(cls): + """Build dataclass from environment""" return cls( hostname=os.getenv("KODAK_DATABASE_MARIADB_HOSTNAME", cls.hostname), username=os.getenv("KODAK_DATABASE_MARIADB_USERNAME", cls.username), @@ -81,6 +97,12 @@ class DatabaseMariaConfig: @dataclass class DatabaseConfig: + """Database backend configuration + + :param backend: Enum selecting the backend to use for storing data + :param sqlite: Container of SQLite settings + :param mariadb: Container of MariaDB settings + """ backend: constants.DatabaseBackend = constants.DatabaseBackend.SQLITE sqlite: DatabaseSqliteConfig = field(default_factory=DatabaseSqliteConfig.from_env) @@ -88,6 +110,7 @@ class DatabaseConfig: @classmethod def from_env(cls): + """Build dataclass from environment""" return cls( backend=_get_enum_by_name( "KODAK_DATABASE_BACKEND", constants.DatabaseBackend, cls.backend @@ -97,12 +120,20 @@ class DatabaseConfig: @dataclass class ManipCropConfig: + """Settings for cropping an image + + :param horizontal: Size the image should be cropped to (in pixels) in the horizontal direction + :param vertical: Size the image should be cropped to (in pixels) in the vertical direction + :param anchor: Image location anchor that cropping should be done relative to + """ + horizontal: Optional[int] = None vertical: Optional[int] = None anchor: constants.CropAnchor = constants.CropAnchor.C @classmethod def from_env(cls, key: str): + """Build dataclass from environment""" return cls( anchor=_get_enum_by_value( f"KODAK_MANIP_{key}_CROP_ANCHOR", constants.CropAnchor, cls.anchor @@ -114,12 +145,28 @@ class ManipCropConfig: @dataclass class ManipScaleConfig: + """Settings for scaling an image + + :param horizontal: Horizontal scaling dimension. If ``strategy`` is ``ABSOLUTE`` then this is + the pixel measurement that the horizontal dimension will be scaled up or + down to; if ``strategy`` is ``RELATIVE`` then this is a percentage modifier + that will be applied to the image's existing horizontal dimension. + :param vertical: Vertical scaling dimension. If ``strategy`` is ``ABSOLUTE`` then this is the + pixel measurement that the vertical dimension will be scaled up or down to; + if ``strategy`` is ``RELATIVE`` then this is a percentage modifier that will + be applied to the image's existing vertical dimension. + :param strategy: Strategy to use for scaling the image. Use ``ABSOLUTE`` to scale to an + absolute pixel measurement and use ``RELATIVE`` to scale relative to the + existing dimensions. + """ + 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): + """Build dataclass from environment""" strategy = _get_enum_by_name( f"KODAK_MANIP_{key}_SCALE_STRATEGY", constants.ScaleStrategy, cls.strategy ) @@ -140,6 +187,15 @@ class ManipScaleConfig: @dataclass class ManipConfig: + """Image manipulation configuration settings + + :param name: Name of the manipulation that will be accessed in the URL + :param crop: Contaienr of settings for cropping an image + :param scale: Container of settings for scaling an image + :param formats: Set of image formats that the source can be dynamically converted into + :param black_and_white: Whether the image should be converted to black and white + """ + name: str crop: ManipCropConfig = field(default_factory=ManipCropConfig.from_env) scale: ManipScaleConfig = field(default_factory=ManipScaleConfig.from_env) @@ -147,6 +203,7 @@ class ManipConfig: default_factory=lambda: constants.DEFAULT_SUPPORTED_FORMATS ) black_and_white: bool = False + # TODO: Implement support for these settings # brightness: int = 0 # contrast: int = 0 @@ -154,15 +211,14 @@ class ManipConfig: @classmethod def from_env(cls, key: str): + """Build dataclass from environment""" return cls( name=os.getenv(f"KODAK_MANIP_{key}_NAME", key.lower()), crop=ManipCropConfig.from_env(key), scale=ManipScaleConfig.from_env(key), formats=set( - [ - constants.ImageFormat[item.strip().upper()] - for item in os.environ[f"KODAK_MANIP_{key}_FORMATS"].split(",") - ] + constants.ImageFormat[item.strip().upper()] + for item in os.environ[f"KODAK_MANIP_{key}_FORMATS"].split(",") ) if f"KODAK_MANIP_{key}_FORMATS" in os.environ else constants.DEFAULT_SUPPORTED_FORMATS, @@ -174,32 +230,46 @@ class ManipConfig: @dataclass class KodakConfig: + """Global application configuration settings + + :param database: Container of database backend settings + :param manips: Mapping of manipulation config names to image manipulation configurations + :param source_dir: Path to where source images should be loaded from + :param content_dir: Path to where the application should store generated images + :param expose_source: Whether the original image should be exposed to clients + :param private: Whether authentication is required for accessing the server + """ + database: DatabaseConfig = field(default_factory=DatabaseConfig.from_env) - sourcedir: Path = Path.cwd() / "images" - manipdir: Path = Path.cwd() / "images" + manips: Dict[str, ManipConfig] = field(default_factory=dict) + source_dir: Path = Path.cwd() / "images" + content_dir: Path = Path.cwd() / "images" expose_source: bool = False private: bool = False - manips: Dict[str, ManipConfig] = field(default_factory=dict) @classmethod def from_env(cls): + """Build dataclass from environment""" manip_names = set( - [ - key.replace("KODAK_MANIP_", "").partition("_")[0] - for key in os.environ.keys() - if key.startswith("KODAK_MANIP_") - ] + key.replace("KODAK_MANIP_", "").partition("_")[0] + for key in os.environ + if key.startswith("KODAK_MANIP_") ) + manips = [ManipConfig.from_env(name) for name in manip_names] return cls( - sourcedir=_get_path("KODAK_SOURCEDIR", cls.sourcedir), - manipdir=_get_path("KODAK_MANIPDIR", cls.manipdir), + source_dir=_get_path("KODAK_SOURCE_DIR", cls.source_dir), + content_dir=_get_path("KODAK_CONTENT_DIR", cls.content_dir), expose_source=_get_bool("KODAK_EXPOSE_SOURCE", cls.expose_source), private=_get_bool("KODAK_PRIVATE", cls.private), - manips={name.lower(): ManipConfig.from_env(name) for name in manip_names}, + manips={item.name: item for item in manips}, ) def load() -> KodakConfig: + """Load the application configuration from environment variables + + :returns: Populated environment configuration + """ try: return KodakConfig.from_env() except (ValueError, TypeError, IndexError, KeyError) as err: diff --git a/kodak/constants.py b/kodak/constants.py index 2b7652a..91b2243 100644 --- a/kodak/constants.py +++ b/kodak/constants.py @@ -4,21 +4,22 @@ import peewee class DatabaseBackend(enum.Enum): + """Enum of supported database backends""" + MARIADB = peewee.MySQLDatabase SQLITE = peewee.SqliteDatabase class ScaleStrategy(enum.Enum): + """Available strategies for scaling images""" + RELATIVE = enum.auto() ABSOLUTE = enum.auto() -class ImageFormat(enum.Enum): - JPEG = enum.auto() - PNG = enum.auto() - - class CropAnchor(enum.Enum): + """Anchor locations for cropping images""" + TL = "top-left" TC = "top-center" TR = "top-center" @@ -30,6 +31,13 @@ class CropAnchor(enum.Enum): BR = "bottom-right" +class ImageFormat(enum.Enum): + """Supported image conversion formats""" + + JPEG = enum.auto() + PNG = enum.auto() + + DEFAULT_SQLITE_PRAGMAS = { "journal_mode": "wal", "cache_size": -1 * 64000,