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

thombashi / humanreadable / 14816919190

04 May 2025 02:39AM UTC coverage: 88.798% (-0.4%) from 89.216%
14816919190

push

github

thombashi
Remove _typing file that is no longer needed

Signed-off-by: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>

65 of 84 branches covered (77.38%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 5 files covered. (100.0%)

18 existing lines in 3 files now uncovered.

371 of 407 relevant lines covered (91.15%)

0.91 hits per line

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

89.16
/humanreadable/_base.py
1
"""
2
.. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
3
"""
4

5
import abc
1✔
6
import re
1✔
7
from decimal import Decimal
1✔
8
from re import Pattern
1✔
9
from typing import Final, Optional, Union, cast
1✔
10

11
from typepy import RealNumber, String
1✔
12

13
from ._types import SupportsUnit, TextUnitsMap
1✔
14
from .error import ParameterError, UnitNotFoundError
1✔
15

16

17
_RE_NUMBER: Final[Pattern] = re.compile(r"^[-\+]?[0-9\.]+$")
1✔
18

19

20
def _get_unit_msg(text_units: TextUnitsMap) -> str:
1✔
21
    return ", ".join([", ".join(values) for values in text_units.values()])
1✔
22

23

24
class HumanReadableValue(metaclass=abc.ABCMeta):
1✔
25
    @property
26
    @abc.abstractmethod
27
    def _text_units(self) -> TextUnitsMap:  # pragma: no cover
28
        pass
29

30
    @property
31
    @abc.abstractmethod
32
    def _units(self) -> list[SupportsUnit]:  # pragma: no cover
33
        pass
34

35
    @abc.abstractmethod
36
    def get_as(self, unit: Union[str, SupportsUnit]) -> float:  # pragma: no cover
37
        pass
38

39
    def __init__(
1✔
40
        self, readable_value: str, default_unit: Union[str, SupportsUnit, None] = None
41
    ) -> None:
42
        self._default_unit = self._normalize_unit(default_unit)
1✔
43
        self._number, self._from_unit = self.__preprocess(readable_value)
1✔
44

45
    def __repr__(self) -> str:
1✔
46
        items = [str(self._number)]
1✔
47
        if self._from_unit.name:
1!
48
            items.append(self._from_unit.name)
1✔
49

50
        return " ".join(items)
1✔
51

52
    def _normalize_unit(self, unit: Union[str, SupportsUnit, None]) -> Optional[SupportsUnit]:
1✔
53
        if unit is None:
1✔
54
            return None
1✔
55

56
        for u in self._text_units:
1!
57
            if u.regexp.search(cast(str, unit)):
1✔
58
                return u
1✔
59

UNCOV
60
        raise ValueError(f"unit not found: {unit}")
×
61

62
    def __split_unit(self, readable_value: str) -> tuple[str, SupportsUnit]:
1✔
63
        if RealNumber(readable_value).is_type():
1✔
64
            if self._default_unit is None:
1✔
65
                raise UnitNotFoundError(
1✔
66
                    "unit not found",
67
                    value=readable_value,
68
                    available_units=_get_unit_msg(self._text_units),
69
                )
70

71
            return (readable_value, self._default_unit)
1✔
72

73
        if not String(readable_value).is_type():
1✔
74
            raise TypeError("readable_value must be a string")
1✔
75

76
        for unit in self._units:
1✔
77
            try:
1✔
78
                if unit.regexp.search(readable_value):
1✔
79
                    number = unit.regexp.split(readable_value)[0]
1✔
80
                    if not RealNumber(number).is_type():
1✔
81
                        continue
1✔
82

83
                    return (number, unit)
1✔
UNCOV
84
            except TypeError:
×
UNCOV
85
                continue
×
86

87
        raise UnitNotFoundError(
1✔
88
            "unit not found", value=readable_value, available_units=_get_unit_msg(self._text_units)
89
        )
90

91
    def __preprocess(self, readable_value: str) -> tuple[Decimal, SupportsUnit]:
1✔
92
        if readable_value is None:
1✔
93
            raise TypeError("readable_value must be a string")
1✔
94

95
        number_str, from_unit = self.__split_unit(readable_value)
1✔
96
        number = self.__to_number(number_str)
1✔
97

98
        if from_unit is None:
1!
UNCOV
99
            raise UnitNotFoundError(
×
100
                "unit not found",
101
                value=readable_value,
102
                available_units=_get_unit_msg(self._text_units),
103
            )
104

105
        return (number, from_unit)
1✔
106

107
    def __to_number(self, number_str: str) -> Decimal:
1✔
108
        match = _RE_NUMBER.search(number_str)
1✔
109
        if not match:
1!
UNCOV
110
            raise ParameterError(
×
111
                "human-readable value should only include a number", value=number_str
112
            )
113

114
        return Decimal(match.group())
1✔
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