Passed
Push — master ( 171368...772522 )
by Steve
02:53
created

lagom.environment.Env.__init__()   B

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 1
import os
23 1
from abc import ABC
24 1
from typing import ClassVar, Optional
25
26 1
from .exceptions import MissingEnvVariable, InvalidEnvironmentVariables
27
28 1
_is_pydantic_v1 = False
29
30 1
try:
31 1
    from pydantic import BaseModel, ValidationError
32
33 1
    try:
34 1
        from pydantic import v1
35
    except ImportError as _:
36
        _is_pydantic_v1 = True
37
except ImportError as error:
38
    raise ImportError(
39
        "Using Env requires pydantic to be installed. Try `pip install lagom[env]`"
40
    ) from error
41
42
43 1
class Env(ABC, BaseModel):
44
    """
45
    This class implements logic to automatically load properties from ENV
46
    variables.
47
    """
48
49 1
    PREFIX: ClassVar[Optional[str]] = None
50
51 1
    def __init__(self, **kwargs):
52
        """
53
        For normal usage no arguments should be supplied to the constructor.
54
        When this happens all required variables will be loaded from the Environment.
55
        For testing you may want to create an instance with variables explicitly set
56
        in the constructor.
57
58
        :param kwargs:
59
        """
60 1
        try:
61 1
            if len(kwargs) == 0:
62 1
                prefix = self._prefix()
63 1
                envs = os.environ.items()
64 1
                super().__init__(
65
                    **{
66
                        key.replace(prefix, "").lower(): value
67
                        for (key, value) in envs
68
                        if key.startswith(prefix)
69
                    }
70
                )
71
            else:
72
                super().__init__(**kwargs)
73 1
        except ValidationError as validation_error:
74 1
            if _is_pydantic_v1:
75
                missing_field_errors = [
76
                    e
77
                    for e in validation_error.errors()
78
                    if e["type"] == "value_error.missing"
79
                ]
80
            else:
81 1
                missing_field_errors = [
82
                    e for e in validation_error.errors() if e["type"] == "missing"
83
                ]
84 1
            if missing_field_errors:
85 1
                env_names = self._env_names_from_pydantic_errors(missing_field_errors)
86 1
                raise MissingEnvVariable(env_names) from validation_error
87 1
            other_field_errors = [
88
                e
89
                for e in validation_error.errors()
90
                if e["type"] != "value_error.missing"
91
            ]
92 1
            env_names = self._env_names_from_pydantic_errors(other_field_errors)
93 1
            raise InvalidEnvironmentVariables(
94
                env_names, str(validation_error)
95
            ) from validation_error
96
97 1
    def _env_names_from_pydantic_errors(self, missing_field_errors):
98 1
        return [
99
            f"{self._prefix()}{pyd_error['loc'][0]}".upper()
100
            for pyd_error in missing_field_errors
101
        ]
102
103 1
    def _prefix(self):
104 1
        prefix = f"{self.PREFIX}_" if self.PREFIX else ""
105
        return prefix
106