Passed
Push — master ( 7cc42f...cb04cf )
by Dominik
29:15 queued 12:34
created

_analyse_clause()   D

Complexity

Conditions 10

Size

Total Lines 68

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 100.6196

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 10
c 3
b 0
f 0
dl 0
loc 68
ccs 1
cts 31
cp 0.0323
crap 100.6196
rs 4.3373

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like _analyse_clause() 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
# ~*~ coding: utf-8 ~*~
2
#-
3
# OSMAlchemy - OpenStreetMap to SQLAlchemy bridge
4
# Copyright (c) 2016 Dominik George <[email protected]>
5
#
6
# Permission is hereby granted, free of charge, to any person obtaining a copy
7
# of this software and associated documentation files (the "Software"), to deal
8
# in the Software without restriction, including without limitation the rights
9
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
# copies of the Software, and to permit persons to whom the Software is
11
# furnished to do so, subject to the following conditions:
12
#
13
# The above copyright notice and this permission notice shall be included in all
14
# copies or substantial portions of the Software.
15
#
16
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
# SOFTWARE.
23
#
24
# Alternatively, you are free to use OSMAlchemy under Simplified BSD, The
25
# MirOS Licence, GPL-2+, LGPL-2.1+, AGPL-3+ or the same terms as Python
26
# itself.
27
28 1
""" Utility code for OSMAlchemy. """
29
30 1
import dateutil.parser
31 1
import operator
0 ignored issues
show
introduced by
standard import "import operator" comes before "import dateutil.parser"
Loading history...
32 1
import xml.dom.minidom as minidom
0 ignored issues
show
introduced by
standard import "import xml.dom.minidom as minidom" comes before "import dateutil.parser"
Loading history...
33 1
from sqlalchemy.sql.elements import BinaryExpression, BooleanClauseList, BindParameter
34 1
from sqlalchemy.sql.annotation import AnnotatedColumn
0 ignored issues
show
Bug introduced by
The name AnnotatedColumn does not seem to exist in module sqlalchemy.sql.annotation.
Loading history...
35
36 1
def _import_osm_dom(osma, session, dom):
37
    """ Import a DOM tree from OSM XML into an OSMAlchemy model.
38
39
    Not called directly; used by _import_osm_xml and _import_osm_file.
40
    """
41
42 1
    def _dom_attrs_to_any(e, element):
0 ignored issues
show
Coding Style Naming introduced by
The name e does not conform to the argument naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
43 1
        if "version" in e.attributes.keys():
44 1
            element.version = int(e.attributes["version"].value)
45 1
        if "changeset" in e.attributes.keys():
46 1
            element.changeset = int(e.attributes["changeset"].value)
47 1
        if "user" in e.attributes.keys():
48 1
            element.user = e.attributes["user"].value
49 1
        if "uid" in e.attributes.keys():
50 1
            element.uid = int(e.attributes["uid"].value)
51 1
        if "visible" in e.attributes.keys():
52 1
            element.visible = True if e.attributes["visible"].value == "true" else False
53 1
        if "timestamp" in e.attributes.keys():
54 1
            element.timestamp = dateutil.parser.parse(e.attributes["timestamp"].value)
55
56 1
    def _dom_tags_to_any(e, element):
0 ignored issues
show
Coding Style Naming introduced by
The name e does not conform to the argument naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
57
        # Target dictionary
58 1
        tags = {}
59
60
        # Iterate over all <tag /> nodes in the DOM element
61 1
        for t in e.getElementsByTagName("tag"):
0 ignored issues
show
Coding Style Naming introduced by
The name t does not conform to the variable naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
62
            # Append data to tags
63 1
            tags[t.attributes["k"].value] = t.attributes["v"].value
64
65
        # Store tags dictionary in object
66 1
        element.tags = tags
67
68 1
    def _dom_to_node(e):
0 ignored issues
show
Coding Style Naming introduced by
The name e does not conform to the argument naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
69 1
        with session.no_autoflush:
70
            # Get mandatory node id
71 1
            id = int(e.attributes["id"].value)
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
Coding Style Naming introduced by
The name id does not conform to the variable naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
72
73
            # Find object in database and create if non-existent
74 1
            node = session.query(osma.node).filter_by(id=id).scalar()
75 1
            if node is None:
76 1
                node = osma.node(id=id)
77
78
            # Store mandatory latitude and longitude
79 1
            node.latitude = e.attributes["lat"].value
80 1
            node.longitude = e.attributes["lon"].value
81
82
            # Store other attributes and tags
83 1
            _dom_attrs_to_any(e, node)
84 1
            _dom_tags_to_any(e, node)
85
86
        # Add to session
87 1
        session.add(node)
88 1
        session.commit()
89
90 1
    def _dom_to_way(e):
0 ignored issues
show
Coding Style Naming introduced by
The name e does not conform to the argument naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
91 1
        with session.no_autoflush:
92
            # Get mandatory way id
93 1
            id = int(e.attributes["id"].value)
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
Coding Style Naming introduced by
The name id does not conform to the variable naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
94
95
            # Find object in database and create if non-existent
96 1
            way = session.query(osma.way).filter_by(id=id).scalar()
97 1
            if way is None:
98 1
                way = osma.way(id=id)
99
100
            # Find all related nodes
101 1
            for n in e.getElementsByTagName("nd"):
0 ignored issues
show
Coding Style Naming introduced by
The name n does not conform to the variable naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
102
                # Get node id and find object
103 1
                ref = int(n.attributes["ref"].value)
104 1
                node = session.query(osma.node).filter_by(id=ref).one()
105
                # Append to nodes in way
106 1
                way.nodes.append(node)
107
108
            # Store other attributes and tags
109 1
            _dom_attrs_to_any(e, way)
110 1
            _dom_tags_to_any(e, way)
111
112
        # Add to session
113 1
        session.add(way)
114 1
        session.commit()
115
116 1
    def _dom_to_relation(e):
0 ignored issues
show
Coding Style Naming introduced by
The name e does not conform to the argument naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
117 1
        with session.no_autoflush:
118
            # Get mandatory way id
119 1
            id = int(e.attributes["id"].value)
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
Coding Style Naming introduced by
The name id does not conform to the variable naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
120
121
            # Find object in database and create if non-existent
122 1
            relation = session.query(osma.relation).filter_by(id=id).scalar()
123 1
            if relation is None:
124 1
                relation = osma.relation(id=id)
125
126
            # Find all members
127 1
            for m in e.getElementsByTagName("member"):
0 ignored issues
show
Coding Style Naming introduced by
The name m does not conform to the variable naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
128
                # Get member attributes
129 1
                ref = int(m.attributes["ref"].value)
130 1
                type = m.attributes["type"].value
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in type.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
131
132 1
                if "role" in m.attributes.keys():
133 1
                    role = m.attributes["role"].value
134
                else:
135
                    role = ""
136 1
                element = session.query(osma.element).filter_by(id=ref, type=type).scalar()
137 1
                if element is None:
138
                    # We do not know the member yet, create a stub
139 1
                    if type == "node":
140 1
                        element = osma.node(id=ref)
141 1
                    elif type == "way":
142 1
                        element = osma.way(id=ref)
143 1
                    elif type == "relation":
144 1
                        element = osma.relation(id=ref)
145
                    # We need to commit here because element could be repeated
146 1
                    session.add(element)
147 1
                    session.commit()
148
                # Append to members
149 1
                relation.members.append((element, role))
150
151
            # Store other attributes and tags
152 1
            _dom_attrs_to_any(e, relation)
153 1
            _dom_tags_to_any(e, relation)
154
155
        # Add to session
156 1
        session.add(relation)
157 1
        session.commit()
158
159
    # Get root element
160 1
    osm = dom.documentElement
161
162
    # Iterate over children to find nodes, ways and relations
163 1
    for e in osm.childNodes:
0 ignored issues
show
Coding Style Naming introduced by
The name e does not conform to the variable naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
164
        # Determine element type
165 1
        if e.nodeName == "node":
166 1
            _dom_to_node(e)
167 1
        elif e.nodeName == "way":
168 1
            _dom_to_way(e)
169 1
        elif e.nodeName == "relation":
170 1
            _dom_to_relation(e)
171
172
        # Rmove children
173 1
        e.unlink()
174
175 1
def _import_osm_xml(osma, session, xml):
176
    """ Import a string in OSM XML format into an OSMAlchemy model.
177
178
      osma - reference to the OSMAlchemy model instance
179
      session - an SQLAlchemy session
180
      xml - string containing the XML data
181
    """
182
183
    # Parse string into DOM structure
184
    dom = minidom.parseString(xml)
185
186
    return _import_osm_dom(osma, session, dom)
187
188 1
def _import_osm_file(osma, session, file):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in file.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
189
    """ Import a file in OSM XML format into an OSMAlchemy model.
190
191
      osma - reference to the OSMAlchemy model instance
192
      session - an SQLAlchemy session
193
      path - path to the file to import or open file object
194
    """
195
196
    # Parse document into DOM structure
197 1
    dom = minidom.parse(file)
198
199 1
    return _import_osm_dom(osma, session, dom)
200
201
# Define operator to string mapping
202 1
_ops = {operator.eq: "==",
0 ignored issues
show
Coding Style Naming introduced by
The name _ops does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
203
        operator.ne: "!=",
204
        operator.lt: "<",
205
        operator.gt: ">",
206
        operator.le: "<=",
207
        operator.ge: ">=",
208
        operator.and_: "&&",
209
        operator.or_: "||"}
210
211 1
def _analyse_clause(clause, target):
0 ignored issues
show
best-practice introduced by
Too many return statements (8/6)
Loading history...
212
    if type(clause) is BinaryExpression:
0 ignored issues
show
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
213
        # This is something like "latitude >= 51.0"
214
        left = clause.left
215
        right = clause.right
216
        op = clause.operator
0 ignored issues
show
Coding Style Naming introduced by
The name op does not conform to the variable naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
217
218
        # Left part should be a column
219
        if type(left) is AnnotatedColumn:
0 ignored issues
show
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
220
            # Get table class and field
221
            model = left._annotations["parentmapper"].class_
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _annotations was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
222
            field = left
223
224
            # Only use if we are looking for this model
225
            if model is target:
226
                # Store field name
227
                left = field.name
228
            else:
229
                return None
230
        else:
231
            # Right now, we cannot cope with anything but a column on the left
232
            return None
233
234
        # Right part should be a literal value
235
        if type(right) is BindParameter:
0 ignored issues
show
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
236
            # Extract literal value
237
            right = right.value
238
        else:
239
            # Right now, we cannot cope with something else here
240
            return None
241
242
        # Look for a known operator
243
        if op in _ops.keys():
244
            # Get string representation
245
            op = _ops[op]
0 ignored issues
show
Coding Style Naming introduced by
The name op does not conform to the variable naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
246
        else:
247
            # Right now, we cannot cope with other operators
248
            return None
249
250
        # Return polish notation tuple of this clause
251
        return (op, left, right)
252
    elif type(clause) is BooleanClauseList:
0 ignored issues
show
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
253
        # This is an AND or OR operation
254
        op = clause.operator
0 ignored issues
show
Coding Style Naming introduced by
The name op does not conform to the variable naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
255
        clauses = []
256
257
        # Iterate over all the clauses in this operation
258
        for clause in clause.clauses:
259
            # Recursively analyse clauses
260
            res = _analyse_clause(clause, target)
261
            # None is returned for unsupported clauses or operations
262
            if res is not None:
263
                # Append polish notation result to clauses list
264
                clauses.append(res)
265
266
        # Look for a known operator
267
        if op in _ops.keys():
268
            # Get string representation
269
            op = _ops[op]
0 ignored issues
show
Coding Style Naming introduced by
The name op does not conform to the variable naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
270
        else:
271
            # Right now, we cannot cope with anything else
272
            return None
273
274
        # Return polish notation tuple of this clause
275
        return (op, clauses)
276
    else:
277
        # We hit an unsupported type of clause
278
        return None
279