Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion kirovy/settings/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
SECRET_KEY = get_env_var("SECRET_KEY", validation_callback=secret_key_validator)

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = get_env_var("DEBUG", False, validation_callback=not_allowed_on_prod)
DEBUG = get_env_var("DEBUG", default=False, validation_callback=not_allowed_on_prod, value_type=bool)

ALLOWED_HOSTS = get_env_var("ALLOWED_HOSTS", "localhost,mapdb-nginx").split(",")

Expand Down
3 changes: 2 additions & 1 deletion kirovy/typing/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Any types specific to our application should live in this package.
"""

import uuid

# import typing for re-export. This avoids having two different typing imports.
Expand All @@ -10,7 +11,7 @@
# arg[0]: The key of the env var for the exception.
# arg[1]: The value we fetched from the env var
# No return, raise an error.
SettingsValidationCallback = Callable[[str, Any], NoReturn]
SettingsValidationCallback = Callable[[str, Any], None]


FileExtension = str
Expand Down
49 changes: 34 additions & 15 deletions kirovy/utils/settings_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,29 @@
"""

import os
from typing import Any, Optional, NoReturn
from collections.abc import Callable

from distutils.util import strtobool
from typing import Any, Type

from kirovy import exceptions
from kirovy.typing import SettingsValidationCallback
from kirovy.settings import settings_constants

MINIMUM_SECRET_KEY_LENGTH = 32
_NOT_SET = object()


def _unvalidated_env_var(_: str, __: Any) -> None:
return


def get_env_var(
key: str,
default: Optional[Any] = None,
validation_callback: Optional[SettingsValidationCallback] = None,
default: Any | None = _NOT_SET,
validation_callback: SettingsValidationCallback = _unvalidated_env_var,
*,
value_type: Type[Callable[[object], Any]] = str,
) -> Any:
"""Get an env var and validate it.

Expand All @@ -25,16 +35,24 @@ def get_env_var(

Do not provide defaults for e.g. passwords.

:param str key:
:param key:
The env var key to search for.
:param Optional[Any] default:
:param default:
The default value. Use to make an env var not raise an error if
no env var is found. Never use for secrets.
If you use with ``validation_callback`` then make sure your default value will
pass your validation check.
:param Optional[SettingsValidationCallback] validation_callback:
:param validation_callback:
A function to call on a value to make sure it's valid.
Raises an exception if invalid.
:param value_type:
Convert the string from ``os.environ`` to this type. The type must be callable.
No validation is performed on the environment string before attempting to cast,
so you're responsible for handling cast errors.

.. note::

If you provide ``bool`` then we will use ``distutils.util.strtobool``.
:return Any:
The env var value

Expand All @@ -45,28 +63,29 @@ def get_env_var(

"""

value: Optional[Any] = os.environ.get(key)

if value is None:
value = default
value: str | None = os.environ.get(key)

if value is None:
if value is None and default is _NOT_SET:
raise exceptions.ConfigurationException(key, "Env var is required and cannot be None.")

if validation_callback is not None:
validation_callback(key, value)
if value_type == bool:
value_type = strtobool

value = value_type(value) if value is not None else default

validation_callback(key, value)

return value


def secret_key_validator(key: str, value: str) -> NoReturn:
def secret_key_validator(key: str, value: str) -> None:
"""Validate the secret key.

:param str key:
env var key.
:param str value:
The value found.
:return NoReturn:
:return:

:raises exceptions.ConfigurationException:
"""
Expand Down
23 changes: 22 additions & 1 deletion tests/test_settings_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from kirovy import exceptions
from kirovy import exceptions, typing as t
from kirovy.utils import settings_utils


Expand Down Expand Up @@ -59,3 +59,24 @@ def test_run_environment_valid(run_environment: str, expect_error: bool):
assert e
else:
settings_utils.get_env_var("meh", run_environment, settings_utils.run_environment_valid)


@pytest.mark.parametrize(
"value,expected,value_type",
[
("1", 1, int),
("1.1", 1.1, float),
("1", "1", str),
("1", True, bool),
("true", True, bool),
("0", False, bool),
("false", False, bool),
],
)
def test_get_env_var_value(mocker, value: str, expected: t.Any, value_type: t.Type[t.Any]):
"""Test the strings can be properly cast using the environment loader.

Necessary because ``environ.get`` always returns ``str | None``.
"""
mocker.patch.dict(os.environ, {"test_get_env_var_value": value})
assert settings_utils.get_env_var("test_get_env_var_value", value_type=value_type) == expected