lagom.environment.Env.__init__()   B
last analyzed

Complexity

Conditions 5

Size

Total Lines 45
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5.0406

Importance

Changes 0
Metric Value
cc 5
eloc 30
nop 2
dl 0
loc 45
ccs 15
cts 17
cp 0.8824
crap 5.0406
rs 8.6933
c 0
b 0
f 0
1
"""
2
This module provides code to automatically load environment variables from the container.
3
It is built on top of (and requires) pydantic.
4
5
At first one or more classes representing the required environment variables are defined.
6
All environment variables are assumed to be all uppercase and are automatically lowercased.
7
8
class MyWebEnv(Env):
9
    port: str
10
    host: str
11
12
class DBEnv(Env):
13
    db_host: str
14
    db_password: str
15
16
17
@bind_to_container(c)
18
def some_function(env: DBEnv):
19
    do_something(env.db_host, env.db_password)
20
21
"""
22
23 1
import os
24 1
from abc import ABC
25 1
from typing import ClassVar, Optional
26
27 1
from .exceptions import MissingEnvVariable, InvalidEnvironmentVariables
28
29 1
_is_pydantic_v1 = False
30
31 1
try:
32 1
    from pydantic import BaseModel, ValidationError
33
34 1
    try:
35 1
        from pydantic import v1
36
    except ImportError as _:
37
        _is_pydantic_v1 = True
38
except ImportError as error:
39
    raise ImportError(
40
        "Using Env requires pydantic to be installed. Try `pip install lagom[env]`"
41
    ) from error
42
43
44 1
class Env(ABC, BaseModel):
45
    """
46
    This class implements logic to automatically load properties from ENV
47
    variables.
48
    """
49
50 1
    PREFIX: ClassVar[Optional[str]] = None
51
52 1
    def __init__(self, **kwargs):
53
        """
54
        For normal usage no arguments should be supplied to the constructor.
55
        When this happens all required variables will be loaded from the Environment.
56
        For testing you may want to create an instance with variables explicitly set
57
        in the constructor.
58
59
        :param kwargs:
60
        """
61 1
        try:
62 1
            if len(kwargs) == 0:
63 1
                prefix = self._prefix()
64 1
                envs = os.environ.items()
65 1
                super().__init__(
66
                    **{
67
                        key.replace(prefix, "").lower(): value
68
                        for (key, value) in envs
69
                        if key.startswith(prefix)
70
                    }
71
                )
72
            else:
73
                super().__init__(**kwargs)
74 1
        except ValidationError as validation_error:
75 1
            if _is_pydantic_v1:
76
                missing_field_errors = [
77
                    e
78
                    for e in validation_error.errors()
79
                    if e["type"] == "value_error.missing"
80
                ]
81
            else:
82 1
                missing_field_errors = [
83
                    e for e in validation_error.errors() if e["type"] == "missing"
84
                ]
85 1
            if missing_field_errors:
86 1
                env_names = self._env_names_from_pydantic_errors(missing_field_errors)
87 1
                raise MissingEnvVariable(env_names) from validation_error
88 1
            other_field_errors = [
89
                e
90
                for e in validation_error.errors()
91
                if e["type"] != "value_error.missing"
92
            ]
93 1
            env_names = self._env_names_from_pydantic_errors(other_field_errors)
94 1
            raise InvalidEnvironmentVariables(
95
                env_names, str(validation_error)
96
            ) from validation_error
97
98 1
    def _env_names_from_pydantic_errors(self, missing_field_errors):
99 1
        return [
100
            f"{self._prefix()}{pyd_error['loc'][0]}".upper()
101
            for pyd_error in missing_field_errors
102
        ]
103
104 1
    def _prefix(self):
105 1
        prefix = f"{self.PREFIX}_" if self.PREFIX else ""
106
        return prefix
107