From 013846a31ef9097bfe2add48aac50dda2b8468c4 Mon Sep 17 00:00:00 2001 From: Matt Armand Date: Wed, 28 Jun 2023 16:34:13 -0400 Subject: [PATCH] Type Validation now operates on the default value as well - This protects against a situation where a programmer provides a non-str default value but not a var_type, causing unexpected behavior where the type of the default is different than the type when read out of the environment - Also added a unit test covering this type-mismatch behavior --- envs/__init__.py | 16 ++++++++++------ envs/tests.py | 11 ++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/envs/__init__.py b/envs/__init__.py index 4a0b860..6bd5987 100644 --- a/envs/__init__.py +++ b/envs/__init__.py @@ -32,7 +32,7 @@ def validate_boolean(value): class Env(object): valid_types = { - 'string': None, + 'string': str, 'boolean': validate_boolean, 'list': list, 'tuple': tuple, @@ -56,16 +56,20 @@ def __call__(self, key, default=None, var_type='string', allow_none=True): if not allow_none: raise EnvsValueException('{}: Environment Variable Not Set'.format(key)) return value - return self.validate_type(value, self.valid_types[var_type], key) + return self.validate_type(value, var_type, key) - def validate_type(self, value, klass, key): - if not klass: - return value + def validate_type(self, value, var_type, key): + klass = self.valid_types[var_type] if klass in (validate_boolean, Decimal): return klass(value) if isinstance(value, klass): return value - return klass(ast.literal_eval(value)) + try: + return klass(ast.literal_eval(value)) + except ValueError as e: + raise TypeError( + f'Could not resolve value ({value}) as var_type ({var_type}) for key {key}' + ) from e env = Env() diff --git a/envs/tests.py b/envs/tests.py index 1b066df..4a0b7a8 100644 --- a/envs/tests.py +++ b/envs/tests.py @@ -114,6 +114,16 @@ def test_defaults(self): self.assertEqual(env('HELLO', 'true', var_type='boolean'), True) self.assertEqual(env('HELLO', Decimal('3.14'), var_type='decimal'), Decimal('3.14')) + def test_defaults_type_mismatch(self): + # Setting a default int with an implicit var_type of string should fail + # (otherwise default type is different than overridden type and that + # can cause problems in client code) + with self.assertRaises(TypeError): + env('HELLO', 5) + # Setting a default int with an explicit var_type of not-int should fail + with self.assertRaises(TypeError): + env('HELLO', 5, var_type='dict') + def test_without_defaults_allow_none(self): self.assertEqual(env('HELLO'), None) self.assertEqual(env('HELLO', var_type='integer'), None) @@ -198,4 +208,3 @@ def test_list_envs(): if __name__ == '__main__': unittest.main() -