21_intl/util.py

"""Utilities."""

from hashlib import blake2b
import json
import os
from pathlib import Path
import secrets


DIGEST_SIZE = 16

HTTP_200_OK = 200
HTTP_400_BAD_REQUEST = 400
HTTP_403_NOT_AUTHORIZED = 403
HTTP_500_INTERNAL = 500

NUM_RANDOM_BITS = 30
SECRET_VAR = "SECRET"

TRANSLATIONS_VAR = "TXLATE"
TranslationsTable = None


class AppException(Exception):
    """Root of exception hierarchy."""

    def __init__(self, msg):
        self._msg = msg

    def __str__(self):
        return self._msg


class ModelException(AppException):
    pass


class SecurityException(AppException):
    pass


class ViewException(AppException):
    pass


class Translation:
    def __init__(self, lookup, key_lang="en"):
        if isinstance(lookup, (str, Path)):
            with open(lookup, "r") as reader:
                lookup = json.loads(reader.read())

        self._lookup = lookup
        self._key_lang = key_lang

    def translator(self, lang):
        def func(key):
            if key not in self._lookup:
                return f"{key}??"
            if lang == self._key_lang:
                return key
            if lang not in self._lookup[key]:
                return f"{key}/{lang}??"
            return self._lookup[key][lang]
        return func


def dict_factory(cursor, row):
    """Convert row to dictionary."""
    fields = [column[0] for column in cursor.description]
    return {key: value for key, value in zip(fields, row)}


def encrypt_password(secret, plain):
    """Encrypt a password."""
    combined = f"{secret}:{plain}"
    h = blake2b(bytes(combined, "utf-8"), digest_size=DIGEST_SIZE)
    return h.hexdigest()


def get_secret():
    """Get the secret value from an environment variable."""
    secret = os.getenv(SECRET_VAR, None)
    if secret is None:
        raise SecurityException(f"secret not set: use {SECRET_VAR} environment variable")
    return secret


def get_translations(key_lang="en"):
    """Load the translation table."""
    global TranslationsTable
    if TranslationsTable is None:
        path = os.getenv(TRANSLATIONS_VAR, None)
        if path is None:
            raise ViewException(f"no translation table path defined (use {TRANSLATIONS_VAR})")
        TranslationsTable = Translation(path, key_lang)
    return TranslationsTable


def make_secret():
    """Make a secret value."""
    return str(secrets.randbits(NUM_RANDOM_BITS))