Completed
Push — develop ( 0ce159...3bb0e5 )
by Jace
02:41
created

Boolean   A

Complexity

Total Complexity 4

Size/Duplication

Total Lines 16
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 4
c 2
b 0
f 0
dl 0
loc 16
ccs 10
cts 10
cp 1
rs 10

1 Method

Rating   Name   Duplication   Size   Complexity  
A to_value() 0 8 4
1
"""Convertible classes for builtin immutable types."""
2
3 1
import logging
4
5 1
from .. import exceptions
6 1
from ..bases import Converter
7
8 1
log = logging.getLogger(__name__)
9
10
11 1
class Object(Converter):
12
    """Base class for immutable types."""
13
14 1
    TYPE = None  # type for inferred types (set in subclasses)
15 1
    DEFAULT = None  # default value for conversion (set in subclasses)
16
17 1
    @classmethod
18
    def create_default(cls):
19 1
        return cls.DEFAULT
20
21 1
    @classmethod
22
    def to_value(cls, obj):
23 1
        return obj
24
25 1
    @classmethod
26
    def to_data(cls, obj):
27 1
        return cls.to_value(obj)
28
29
30 1
class String(Object):
31
    """Convertible for the `str` type."""
32
33 1
    TYPE = str
34 1
    DEFAULT = ""
35
36 1
    @classmethod
37
    def to_value(cls, obj):
38 1
        if isinstance(obj, cls.TYPE):
39 1
            return obj
40 1
        elif obj is True:
41 1
            return "true"
42 1
        elif obj is False:
43 1
            return "false"
44 1
        elif obj:
45 1
            try:
46 1
                return ', '.join(str(item) for item in obj)
47 1
            except TypeError:
48 1
                return str(obj)
49
        else:
50 1
            return cls.DEFAULT
51
52 1
    @classmethod
53
    def to_data(cls, obj):
54 1
        value = cls.to_value(obj)
55 1
        return cls._optimize_for_quoting(value)
56
57 1
    @staticmethod
58
    def _optimize_for_quoting(value):
59 1
        if value == "true":
60 1
            return True
61 1
        if value == "false":
62 1
            return False
63 1
        for number_type in (int, float):
64 1
            try:
65 1
                return number_type(value)
66 1
            except (TypeError, ValueError):
67 1
                continue
68 1
        return value
69
70
71 1
class Integer(Object):
72
    """Convertible for the `int` type."""
73
74 1
    TYPE = int
75 1
    DEFAULT = 0
76
77 1
    @classmethod
78
    def to_value(cls, obj):
79 1
        if all((isinstance(obj, cls.TYPE),
80
                obj is not True,
81
                obj is not False)):
82 1
            return obj
83 1
        elif obj:
84 1
            try:
85 1
                return int(obj)
86 1
            except ValueError:
87 1
                return int(float(obj))
88
        else:
89 1
            return cls.DEFAULT
90
91
92 1
class Float(Object):
93
    """Convertible for the `float` type."""
94
95 1
    TYPE = float
96 1
    DEFAULT = 0.0
97
98 1
    @classmethod
99
    def to_value(cls, obj):
100 1
        if isinstance(obj, cls.TYPE):
101 1
            return obj
102 1
        elif obj:
103 1
            return float(obj)
104
        else:
105 1
            return cls.DEFAULT
106
107
108 1
class Boolean(Object):
109
    """Convertible for the `bool` type."""
110
111 1
    TYPE = bool
112 1
    DEFAULT = False
113
114 1
    FALSY = ('false', 'f', 'no', 'n', 'disabled', 'off', '0')
115
116 1
    @classmethod
117
    def to_value(cls, obj):
118 1
        if isinstance(obj, str) and obj.lower().strip() in cls.FALSY:
119 1
            return False
120 1
        elif obj is not None:
121 1
            return bool(obj)
122
        else:
123 1
            return cls.DEFAULT
124
125
126 1
def match(name, data, nested=False):
127
    """Determine the appropriate converter for new data."""
128 1
    nested = " nested" if nested else ""
129 1
    msg = "Determining converter for new%s: '%s' = %r"
130 1
    log.debug(msg, nested, name, repr(data))
131
132 1
    types = Object.__subclasses__()  # pylint: disable=no-member
133 1
    log.trace("Converter options: {}".format(types))
134
135 1
    for converter in types:
136 1
        if converter.TYPE and type(data) == converter.TYPE:  # pylint: disable=unidiomatic-typecheck
0 ignored issues
show
introduced by
Bad option value 'unidiomatic-typecheck'
Loading history...
137 1
            log.debug("Matched converter: %s", converter)
138 1
            log.info("New%s attribute: %s", nested, name)
139 1
            return converter
140
141 1
    if data is None or isinstance(data, (dict, list)):
142 1
        log.info("Default converter: %s", Object)
143 1
        log.warning("New%s attribute with unknown type: %s", nested, name)
144 1
        return Object
145
146 1
    msg = "No converter available for: {}".format(data)
147
    raise exceptions.FileContentError(msg)
148