Skip to content

Commit fa60885

Browse files
authored
fix debug env var loading (#27)
- Fix debug being set to `"0"` being treated as `True` due to a lack of casting
1 parent a9a6b62 commit fa60885

File tree

4 files changed

+59
-18
lines changed

4 files changed

+59
-18
lines changed

kirovy/settings/_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
SECRET_KEY = get_env_var("SECRET_KEY", validation_callback=secret_key_validator)
3232

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

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

kirovy/typing/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Any types specific to our application should live in this package.
33
"""
4+
45
import uuid
56

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

1516

1617
FileExtension = str

kirovy/utils/settings_utils.py

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,29 @@
44
"""
55

66
import os
7-
from typing import Any, Optional, NoReturn
7+
from collections.abc import Callable
8+
9+
from distutils.util import strtobool
10+
from typing import Any, Type
811

912
from kirovy import exceptions
1013
from kirovy.typing import SettingsValidationCallback
1114
from kirovy.settings import settings_constants
1215

1316
MINIMUM_SECRET_KEY_LENGTH = 32
17+
_NOT_SET = object()
18+
19+
20+
def _unvalidated_env_var(_: str, __: Any) -> None:
21+
return
1422

1523

1624
def get_env_var(
1725
key: str,
18-
default: Optional[Any] = None,
19-
validation_callback: Optional[SettingsValidationCallback] = None,
26+
default: Any | None = _NOT_SET,
27+
validation_callback: SettingsValidationCallback = _unvalidated_env_var,
28+
*,
29+
value_type: Type[Callable[[object], Any]] = str,
2030
) -> Any:
2131
"""Get an env var and validate it.
2232
@@ -25,16 +35,24 @@ def get_env_var(
2535
2636
Do not provide defaults for e.g. passwords.
2737
28-
:param str key:
38+
:param key:
2939
The env var key to search for.
30-
:param Optional[Any] default:
40+
:param default:
3141
The default value. Use to make an env var not raise an error if
3242
no env var is found. Never use for secrets.
3343
If you use with ``validation_callback`` then make sure your default value will
3444
pass your validation check.
35-
:param Optional[SettingsValidationCallback] validation_callback:
45+
:param validation_callback:
3646
A function to call on a value to make sure it's valid.
3747
Raises an exception if invalid.
48+
:param value_type:
49+
Convert the string from ``os.environ`` to this type. The type must be callable.
50+
No validation is performed on the environment string before attempting to cast,
51+
so you're responsible for handling cast errors.
52+
53+
.. note::
54+
55+
If you provide ``bool`` then we will use ``distutils.util.strtobool``.
3856
:return Any:
3957
The env var value
4058
@@ -45,28 +63,29 @@ def get_env_var(
4563
4664
"""
4765

48-
value: Optional[Any] = os.environ.get(key)
49-
50-
if value is None:
51-
value = default
66+
value: str | None = os.environ.get(key)
5267

53-
if value is None:
68+
if value is None and default is _NOT_SET:
5469
raise exceptions.ConfigurationException(key, "Env var is required and cannot be None.")
5570

56-
if validation_callback is not None:
57-
validation_callback(key, value)
71+
if value_type == bool:
72+
value_type = strtobool
73+
74+
value = value_type(value) if value is not None else default
75+
76+
validation_callback(key, value)
5877

5978
return value
6079

6180

62-
def secret_key_validator(key: str, value: str) -> NoReturn:
81+
def secret_key_validator(key: str, value: str) -> None:
6382
"""Validate the secret key.
6483
6584
:param str key:
6685
env var key.
6786
:param str value:
6887
The value found.
69-
:return NoReturn:
88+
:return:
7089
7190
:raises exceptions.ConfigurationException:
7291
"""

tests/test_settings_utils.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from kirovy import exceptions
5+
from kirovy import exceptions, typing as t
66
from kirovy.utils import settings_utils
77

88

@@ -59,3 +59,24 @@ def test_run_environment_valid(run_environment: str, expect_error: bool):
5959
assert e
6060
else:
6161
settings_utils.get_env_var("meh", run_environment, settings_utils.run_environment_valid)
62+
63+
64+
@pytest.mark.parametrize(
65+
"value,expected,value_type",
66+
[
67+
("1", 1, int),
68+
("1.1", 1.1, float),
69+
("1", "1", str),
70+
("1", True, bool),
71+
("true", True, bool),
72+
("0", False, bool),
73+
("false", False, bool),
74+
],
75+
)
76+
def test_get_env_var_value(mocker, value: str, expected: t.Any, value_type: t.Type[t.Any]):
77+
"""Test the strings can be properly cast using the environment loader.
78+
79+
Necessary because ``environ.get`` always returns ``str | None``.
80+
"""
81+
mocker.patch.dict(os.environ, {"test_get_env_var_value": value})
82+
assert settings_utils.get_env_var("test_get_env_var_value", value_type=value_type) == expected

0 commit comments

Comments
 (0)