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

oir / startle / 21548344480

31 Jan 2026 05:42PM UTC coverage: 98.823%. Remained the same
21548344480

Pull #130

github

web-flow
Merge a62856d22 into c58d589ef
Pull Request #130: Support `Any`, update typing docs

250 of 250 branches covered (100.0%)

Branch coverage included in aggregate %.

1177 of 1194 relevant lines covered (98.58%)

0.99 hits per line

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

98.59
startle/_value_parser.py
1
"""
2
String-to-type conversion functions.
3
"""
4

5
import typing
1✔
6
from collections.abc import Callable
1✔
7
from enum import Enum
1✔
8
from inspect import isclass
1✔
9
from pathlib import Path
1✔
10
from typing import Any, Literal, cast
1✔
11

12
from ._type_utils import strip_optional
1✔
13
from .error import ParserValueError
1✔
14

15

16
def _to_str(value: str) -> str:
1✔
17
    return value
1✔
18

19

20
def _to_int(value: str) -> int:
1✔
21
    try:
1✔
22
        return int(value)
1✔
23
    except ValueError as err:
1✔
24
        raise ParserValueError(f"Cannot parse integer from `{value}`!") from err
1✔
25

26

27
def _to_float(value: str) -> float:
1✔
28
    try:
1✔
29
        return float(value)
1✔
30
    except ValueError as err:
1✔
31
        raise ParserValueError(f"Cannot parse float from `{value}`!") from err
1✔
32

33

34
def _to_bool(value: str) -> bool:
1✔
35
    if value.lower() in {"true", "t", "yes", "y", "1"}:
1✔
36
        return True
1✔
37
    if value.lower() in {"false", "f", "no", "n", "0"}:
1✔
38
        return False
1✔
39
    raise ParserValueError(f"Cannot parse boolean from `{value}`!")
1✔
40

41

42
def _to_path(value: str) -> Path:
1✔
43
    return Path(value)  # can this raise?
1✔
44

45

46
def _to_enum(value: str, enum_type: type) -> Enum:
1✔
47
    try:
1✔
48
        # for StringEnum and (str, Enum) types, use enum value
49
        # otherwise use the name of the member
50
        member_type: type = getattr(enum_type, "_member_type_", object)
1✔
51
        if member_type is str or (member_type is object and issubclass(enum_type, str)):
1✔
52
            return cast(Enum, enum_type(value))
1✔
53
        try:
1✔
54
            enum_type_ = cast(type[Enum], enum_type)
1✔
55
            return enum_type_[value.upper().replace("-", "_")]
1✔
56
        except KeyError as err:
1✔
57
            raise ParserValueError(
1✔
58
                f"Cannot parse enum {enum_type.__name__} from `{value}`!"
59
            ) from err
60
    except ValueError as err:
1✔
61
        raise ParserValueError(
1✔
62
            f"Cannot parse enum {enum_type.__name__} from `{value}`!"
63
        ) from err
64

65

66
PARSERS: dict[Any, Callable[[str], Any]] = {
1✔
67
    str: _to_str,
68
    Any: _to_str,
69
    int: _to_int,
70
    float: _to_float,
71
    bool: _to_bool,
72
    Path: _to_path,
73
}
74

75

76
def _get_parser(type_: Any) -> Callable[[str], Any] | None:
1✔
77
    """
78
    Get the parser function for a given type.
79
    """
80

81
    # if type is Optional[T], convert to T
82
    type_ = strip_optional(type_)
1✔
83

84
    if typing.get_origin(type_) is Literal:
1✔
85
        type_args = typing.get_args(type_)
1✔
86
        if all(isinstance(arg, str) for arg in type_args):
1✔
87

88
            def parser(value: str) -> str:
1✔
89
                if value in type_args:
1✔
90
                    return value
1✔
91
                raise ParserValueError(
1✔
92
                    f"Cannot parse literal {type_args} from `{value}`!"
93
                )
94

95
            return parser
1✔
96

97
    # check if type_ is an Enum
98
    if isclass(type_) and issubclass(type_, Enum):
1✔
99
        return lambda value: _to_enum(value, type_)
1✔
100

101
    if fp := PARSERS.get(type_):
1✔
102
        return fp
1✔
103

104
    return None
1✔
105

106

107
def parse(value: str, type_: Any) -> Any:
1✔
108
    """
109
    Parse or convert a string value to a given type.
110
    """
111
    if parser := _get_parser(type_):
1✔
112
        return parser(value)
1✔
113

114
    # otherwise it is unsupported
115
    raise ParserValueError(f"Unsupported type {type_.__module__}.{type_.__qualname__}!")
×
116

117

118
def is_parsable(type_: Any) -> bool:
1✔
119
    """
120
    Check if a type is parsable (supported).
121
    """
122
    return _get_parser(type_) is not None
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