• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

datek / enfig / 18752009581

23 Oct 2025 02:35PM UTC coverage: 89.831% (-10.2%) from 100.0%
18752009581

push

github

web-flow
Support python 3.14

17 of 29 new or added lines in 1 file covered. (58.62%)

106 of 118 relevant lines covered (89.83%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.19
/enfig/base.py
1
from collections.abc import Iterator
1✔
2
from os import getenv
1✔
3
from sys import version_info
1✔
4
from typing import Any, get_args
1✔
5

6
from enfig.bool_type import _Bool
1✔
7
from enfig.errors import (
1✔
8
    ConfigAttributeError,
9
    ConfigAttributeErrorType,
10
    InstantiationForbiddenError,
11
    ValidationError,
12
)
13

14

15
class _Variable:
1✔
16
    def __init__(self, type_: type = str, default_value=...):
1✔
17
        type_args = get_args(type_)
1✔
18
        type_ = type_args[0] if type_args else type_
1✔
19
        self._type = _Bool if type_ is bool else type_
1✔
20
        self._default_value = default_value
1✔
21

22
    def __set_name__(self, owner, name):
1✔
23
        self._name = name
1✔
24

25
    def __get__(self, obj, type_=None):
1✔
26
        return self.value
1✔
27

28
    @property
1✔
29
    def default_value(self) -> Any:
1✔
30
        return self._default_value
1✔
31

32
    @property
1✔
33
    def name(self):
1✔
34
        return self._name
1✔
35

36
    @property
1✔
37
    def type(self):
1✔
38
        return self._type
1✔
39

40
    @property
1✔
41
    def value(self) -> Any:
1✔
42
        if (value := getenv(self._name)) is not None:
1✔
43
            casted_val = self._type(value)
1✔
44
            return bool(casted_val) if self._type is _Bool else casted_val
1✔
45

46
        if self._default_value is not ...:
1✔
47
            return self._default_value
1✔
48

49
        return None
1✔
50

51

52
if version_info.minor < 14:
1✔
53

NEW
54
    class _ConfigMeta(type):
×
NEW
55
        def __new__(mcs, name: str, bases: tuple, namespace: dict):
×
NEW
56
            for key, type_ in namespace.get("__annotations__", {}).items():
×
NEW
57
                namespace[key] = _Variable(type_, namespace.get(key, ...))
×
58

NEW
59
            for base in bases:
×
NEW
60
                if not isinstance(base, _ConfigMeta):
×
NEW
61
                    continue
×
62

NEW
63
                for variable in base:
×
NEW
64
                    namespace[variable.name] = variable
×
65

NEW
66
            return super().__new__(mcs, name, bases, namespace)
×
67

NEW
68
        def __iter__(cls) -> Iterator[_Variable]:
×
NEW
69
            return (
×
70
                value for value in cls.__dict__.values() if isinstance(value, _Variable)
71
            )
72

73
else:
74
    from annotationlib import Format, get_annotate_from_class_namespace  # type: ignore
1✔
75

76
    class _ConfigMeta(type):  # type: ignore
1✔
77
        def __new__(mcs, name: str, bases: tuple, namespace: dict):
1✔
78
            get_annotations = get_annotate_from_class_namespace(namespace)
1✔
79
            annotations = get_annotations(Format.VALUE) if get_annotations else {}
1✔
80
            for key, type_ in annotations.items():
1✔
81
                namespace[key] = _Variable(type_, namespace.get(key, ...))
1✔
82

83
            for base in bases:
1✔
84
                if not isinstance(base, _ConfigMeta):
1✔
85
                    continue
1✔
86

87
                for variable in base:
1✔
88
                    namespace[variable.name] = variable
1✔
89

90
            return super().__new__(mcs, name, bases, namespace)
1✔
91

92
        def __iter__(cls) -> Iterator[_Variable]:
1✔
93
            return (
1✔
94
                value for value in cls.__dict__.values() if isinstance(value, _Variable)
95
            )
96

97

98
class BaseConfig(metaclass=_ConfigMeta):
1✔
99
    def __new__(cls, *args, **kwargs):
1✔
100
        raise InstantiationForbiddenError
1✔
101

102
    @classmethod
1✔
103
    def validate(cls):
1✔
104
        errors = []
1✔
105

106
        for item in cls:
1✔
107
            try:
1✔
108
                _validate_attribute(item)
1✔
109
            except ConfigAttributeError as error:
1✔
110
                errors.append(error)
1✔
111

112
        if errors:
1✔
113
            raise ValidationError(errors)
1✔
114

115

116
def _validate_attribute(item: _Variable):
1✔
117
    try:
1✔
118
        value = item.value
1✔
119
    except ValueError:
1✔
120
        raise ConfigAttributeError(
1✔
121
            error_type=ConfigAttributeErrorType.INVALID_VALUE,
122
            attribute_name=item.name,
123
            required_type=item.type,
124
        )
125

126
    if item.default_value is ... and value is None:
1✔
127
        raise ConfigAttributeError(
1✔
128
            error_type=ConfigAttributeErrorType.NOT_SET,
129
            attribute_name=item.name,
130
            required_type=item.type,
131
        )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc