goblin.properties   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 224
Duplicated Lines 14.29 %

Importance

Changes 0
Metric Value
eloc 147
dl 32
loc 224
rs 8.48
c 0
b 0
f 0
wmc 49

31 Methods

Rating   Name   Duplication   Size   Complexity  
A Property.getdb_name() 0 2 1
A IdPropertyDescriptor.__set__() 0 5 2
A Boolean.to_db() 2 2 1
A String.to_db() 2 2 1
A Integer.to_ogm() 0 2 1
A IdPropertyDescriptor.__get__() 0 5 2
A Boolean.to_ogm() 2 2 1
A Property.setgetdb_name() 0 2 1
A Boolean.validate() 7 7 2
A Generic.validate() 0 2 1
A PropertyDescriptor.__init__() 0 5 1
A Float.validate() 0 7 2
A PropertyDescriptor.__set__() 0 3 1
A Property.__init__() 0 14 3
A IdProperty.serializer() 0 3 1
A String.validate() 7 7 3
A Integer.to_db() 0 2 1
A PropertyDescriptor.__delete__() 0 5 2
A Float.to_db() 0 2 1
A IdProperty.__init__() 0 7 3
A String.to_ogm() 2 2 1
A IdPropertyDescriptor.__init__() 0 5 1
A Property.db_name_factory() 0 3 1
A Generic.to_db() 0 2 1
A Property.default() 0 3 1
A IdProperty.data_type() 0 3 1
A PropertyDescriptor.__get__() 0 4 2
A Float.to_ogm() 0 2 1
A Property.data_type() 0 3 1
A Integer.validate() 0 9 4
A Generic.to_ogm() 0 2 1

2 Functions

Rating   Name   Duplication   Size   Complexity  
A noop_factory() 0 2 1
A default_id_serializer() 0 4 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like goblin.properties often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""Classes to handle properties and data type definitions"""
2
3
import logging
4
from typing import Any
5
6
from gremlin_python.statics import long # type: ignore
7
8
from goblin import abc, exception
9
10
logger = logging.getLogger(__name__)
11
12
13
def noop_factory(x, y):
14
    return None
15
16
17
class PropertyDescriptor:
18
    """
19
    Descriptor that validates user property input and gets/sets properties
20
    as instance attributes. Not instantiated by user.
21
    """
22
23
    def __init__(self, name, prop):
24
        self._prop_name = name
25
        self._name = '_' + name
26
        self._data_type = prop.data_type
27
        self._default = prop.default
28
29
    def __get__(self, obj, objtype):
30
        if obj is None:
31
            return getattr(objtype.__mapping__, self._prop_name)
32
        return getattr(obj, self._name, self._default)
33
34
    def __set__(self, obj, val):
35
        val = self._data_type.validate(val)
36
        setattr(obj, self._name, val)
37
38
    def __delete__(self, obj):
39
        # hmmm what is the best approach here
40
        attr = getattr(obj, self._name, None)
41
        if attr:
42
            del attr
43
44
45
class Property(abc.BaseProperty):
46
    """
47
    API class used to define properties. Replaced with
48
    :py:class:`PropertyDescriptor` by :py:class:`goblin.element.ElementMeta`.
49
50
    :param goblin.abc.DataType data_type: Str or class of data type
51
    :param str db_name: User defined custom name for property in db
52
    :param default: Default value for this property.
53
    """
54
55
    __descriptor__ = PropertyDescriptor
56
57
    def __init__(self,
58
                 data_type,
59
                 *,
60
                 db_name=None,
61
                 default=None,
62
                 db_name_factory=None):
63
        if not db_name_factory:
64
            db_name_factory = noop_factory  # noop
65
        if isinstance(data_type, type):
66
            data_type = data_type()
67
        self._db_name_factory = db_name_factory
68
        self._data_type = data_type
69
        self._db_name = db_name
70
        self._default = default
71
72
    @property
73
    def data_type(self):
74
        return self._data_type
75
76
    def getdb_name(self):
77
        return self._db_name
78
79
    def setgetdb_name(self, val):
80
        self._db_name = val
81
82
    db_name = property(getdb_name, setgetdb_name)
83
84
    @property
85
    def db_name_factory(self):
86
        return self._db_name_factory
87
88
    @property
89
    def default(self):
90
        return self._default
91
92
93
class IdPropertyDescriptor:
94
    def __init__(self, name, prop):
95
        assert name == 'id', 'ID properties must be named "id"'
96
        self._data_type = prop.data_type
97
        self._name = '_' + name
98
        self._serializer = prop.serializer
99
100
    def __get__(self, obj, objtype=None):
101
        if obj is None:
102
            raise exception.ElementError(
103
                "Only instantiated elements have ID property")
104
        return obj._id
105
106
    def __set__(self, obj, val):
107
        if self._serializer:
108
            val = self._serializer(val)
109
        val = self._data_type.validate(val)
110
        setattr(obj, self._name, val)
111
112
113
def default_id_serializer(val):
114
    if isinstance(val, int):
115
        val = long(val)
116
    return val
117
118
119
class IdProperty(abc.BaseProperty):
120
121
    __descriptor__ = IdPropertyDescriptor
122
123
    def __init__(self, data_type, *, serializer=None):
124
        if not serializer:
125
            serializer = default_id_serializer
126
        if isinstance(data_type, type):
127
            data_type = data_type()
128
        self._data_type = data_type
129
        self._serializer = serializer
130
131
    @property
132
    def data_type(self):
133
        return self._data_type
134
135
    @property
136
    def serializer(self):
137
        return self._serializer
138
139
140
# Data types
141
class Generic(abc.DataType):
142
    def validate(self, val):
143
        return super().validate(val)
144
145
    def to_db(self, val=None):
146
        return super().to_db(val=val)
147
148
    def to_ogm(self, val):
149
        return super().to_ogm(val)
150
151
152 View Code Duplication
class String(abc.DataType):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
153
    """Simple string datatype"""
154
155
    def validate(self, val):
156
        if val is not None:
157
            try:
158
                return str(val)
159
            except ValueError as e:
160
                raise exception.ValidationError(
161
                    'Not a valid string: {}'.format(val)) from e
162
163
    def to_db(self, val=None):
164
        return super().to_db(val=val)
165
166
    def to_ogm(self, val):
167
        return super().to_ogm(val)
168
169
170
class Integer(abc.DataType):
171
    """Simple integer datatype"""
172
173
    def validate(self, val):
174
        if val is not None:
175
            try:
176
                if isinstance(val, long):
177
                    return long(val)
178
                return int(val)
179
            except (ValueError, TypeError) as e:
180
                raise exception.ValidationError(
181
                    'Not a valid integer: {}'.format(val)) from e
182
183
    def to_db(self, val=None):
184
        return super().to_db(val=val)
185
186
    def to_ogm(self, val):
187
        return super().to_ogm(val)
188
189
190
class Float(abc.DataType):
191
    """Simple float datatype"""
192
193
    def validate(self, val):
194
        try:
195
            val = float(val)
196
        except ValueError:
197
            raise exception.ValidationError(
198
                "Not a valid float: {}".format(val)) from e
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable e does not seem to be defined.
Loading history...
199
        return val
200
201
    def to_db(self, val=None):
202
        return super().to_db(val=val)
203
204
    def to_ogm(self, val):
205
        return super().to_ogm(val)
206
207
208 View Code Duplication
class Boolean(abc.DataType):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
209
    """Simple boolean datatype"""
210
211
    def validate(self, val: Any):
212
        try:
213
            val = bool(val)
214
        except ValueError:
215
            raise exception.ValidationError(
216
                "Not a valid boolean: {val}".format(val)) from e # type: ignore
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable e does not seem to be defined.
Loading history...
217
        return val
218
219
    def to_db(self, val=None):
220
        return super().to_db(val=val)
221
222
    def to_ogm(self, val):
223
        return super().to_ogm(val)
224