Test Failed
Push — master ( cb04cf...da4639 )
by Dominik
02:03
created

_where_to_tree()   D

Complexity

Conditions 10

Size

Total Lines 74

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
c 1
b 0
f 0
dl 0
loc 74
rs 4.0449

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 _where_to_tree() 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
# Copyright (c) 2016 Eike Tim Jesinghaus <[email protected]>
6
#
7
# Permission is hereby granted, free of charge, to any person obtaining a copy
8
# of this software and associated documentation files (the "Software"), to deal
9
# in the Software without restriction, including without limitation the rights
10
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
# copies of the Software, and to permit persons to whom the Software is
12
# furnished to do so, subject to the following conditions:
13
#
14
# The above copyright notice and this permission notice shall be included in all
15
# copies or substantial portions of the Software.
16
#
17
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
# SOFTWARE.
24
#
25
# Alternatively, you are free to use OSMAlchemy under Simplified BSD, The
26
# MirOS Licence, GPL-2+, LGPL-2.1+, AGPL-3+ or the same terms as Python
27
# itself.
28
29
""" Utility code for OSMAlchemy's overpass code. """
30
31
import operator
32
import overpass
33
from sqlalchemy.sql.elements import BinaryExpression, BooleanClauseList, BindParameter
34
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
def _generate_overpass_api(endpoint=None):
37
    """ Create and initialise the Overpass API object.
38
39
    Passing the endpoint argument will override the default
40
    endpoint URL.
41
    """
42
43
    # Create API object with default settings
44
    api = overpass.API()
45
46
    # Change endpoint if desired
47
    if endpoint is not None:
48
        api.endpoint = endpoint
49
50
    return api
51
52
def _get_single_element_by_id(api, type, id, recurse_down=True):
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...
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 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...
53
    """ Retrieves a single OpenStreetMap element by its id.
54
55
      api - an initialised Overpass API object
56
      type - the element type to query, one of node, way or relation
57
      id - the id of the element to retrieve
58
      recurse_down - whether to get child nodes of ways and relations
59
    """
60
61
    # Construct query
62
    q = "%s(%d);%s" % (type, id, "(._;>;);" if recurse_down else "")
0 ignored issues
show
Coding Style Naming introduced by
The name q 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...
63
64
    # Run query
65
    r = api.Get(q, responseformat="xml")
0 ignored issues
show
Coding Style Naming introduced by
The name r 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...
66
67
    # Return data
68
    return r
69
70
# Define operator to string mapping
71
_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...
72
        operator.ne: "!=",
73
        operator.lt: "<",
74
        operator.gt: ">",
75
        operator.le: "<=",
76
        operator.ge: ">=",
77
        operator.and_: "&&",
78
        operator.or_: "||"}
79
80
def _where_to_tree(clause, target):
0 ignored issues
show
best-practice introduced by
Too many return statements (8/6)
Loading history...
81
    """ Transform an SQLAlchemy whereclause to an expression tree.
82
83
    This function analyses a Query.whereclause object and turns it
84
    into a more general data structure.
85
    """
86
87
    if type(clause) is BinaryExpression:
0 ignored issues
show
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
88
        # This is something like "latitude >= 51.0"
89
        left = clause.left
90
        right = clause.right
91
        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...
92
93
        # Left part should be a column
94
        if type(left) is AnnotatedColumn:
0 ignored issues
show
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
95
            # Get table class and field
96
            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...
97
            field = left
98
99
            # Only use if we are looking for this model
100
            if model is target:
101
                # Store field name
102
                left = field.name
103
            else:
104
                return None
105
        else:
106
            # Right now, we cannot cope with anything but a column on the left
107
            return None
108
109
        # Right part should be a literal value
110
        if type(right) is BindParameter:
0 ignored issues
show
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
111
            # Extract literal value
112
            right = right.value
113
        else:
114
            # Right now, we cannot cope with something else here
115
            return None
116
117
        # Look for a known operator
118
        if op in _ops.keys():
119
            # Get string representation
120
            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...
121
        else:
122
            # Right now, we cannot cope with other operators
123
            return None
124
125
        # Return polish notation tuple of this clause
126
        return (op, left, right)
127
    elif type(clause) is BooleanClauseList:
0 ignored issues
show
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
128
        # This is an AND or OR operation
129
        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...
130
        clauses = []
131
132
        # Iterate over all the clauses in this operation
133
        for clause in clause.clauses:
134
            # Recursively analyse clauses
135
            res = _analyse_clause(clause, target)
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable '_analyse_clause'
Loading history...
136
            # None is returned for unsupported clauses or operations
137
            if res is not None:
138
                # Append polish notation result to clauses list
139
                clauses.append(res)
140
141
        # Look for a known operator
142
        if op in _ops.keys():
143
            # Get string representation
144
            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...
145
        else:
146
            # Right now, we cannot cope with anything else
147
            return None
148
149
        # Return polish notation tuple of this clause
150
        return (op, clauses)
151
    else:
152
        # We hit an unsupported type of clause
153
        return None
154
155
def _trees_to_overpassql(tree_dict):
156
    def _tree_to_overpassql_recursive(tree, type_, op):
0 ignored issues
show
Coding Style Naming introduced by
The name op 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...
157
        result = ""
158
159
        # Test if we got a tree or an atom
160
        if isinstance(tree[1], list):
161
            # Tree
162
163
            # Store operation of subtree
164
            op = tree[0]
165
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
166
            # Empty bounding box
167
            bbox = [None, None, None, None]
168
169
            # List of genrated set names
170
            set_names = []
171
172
            for t in tree[1]:
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...
173
                # Check if element is a tree or an atom
174
                if isinstance(t[1], list):
175
                    # Recurse into inner tree
176
                    result_inner_tree = _tree_to_overpassql_recursive(tree[1], op)
0 ignored issues
show
Bug introduced by
It seems like a value for argument op is missing in the function call.
Loading history...
177
                    result += "%s" % result_inner_tree[1]
178
                    set_names.append(result_inner_tree[0])
179
                else:
180
                    # Parse atom
181
                    if t[1] == "latitude" and t[0] == ">":
182 View Code Duplication
                        if bbox[0] is None:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
183
                            bbox[0] = float(t[2])
184
                        elif op == "&&" and bbox[0] <= t[2]:
185
                            bbox[0] = float(t[2])
186
                        elif op == "||" and bbox[0] >= t[2]:
187
                            bbox[0] = float(t[2])
188
                    elif t[1] == "latitude" and t[0] == "<":
189 View Code Duplication
                        if bbox[2] is None:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
190
                            bbox[2] = float(t[2])
191
                        elif op == "&&" and bbox[2] >= t[2]:
192
                            bbox[2] = float(t[2])
193
                        elif op == "||" and bbox[2] <= t[2]:
194
                            bbox[2] = float(t[2])
195
                    elif t[1] == "longitude" and t[0] == ">":
196 View Code Duplication
                        if bbox[1] is None:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
197
                            bbox[1] = float(t[2])
198
                        elif op == "&&" and bbox[1] <= t[2]:
199
                            bbox[1] = float(t[2])
200
                        elif op == "||" and bbox[1] >= t[2]:
201
                            bbox[1] = float(t[2])
202
                    elif t[1] == "longitude" and t[0] == "<":
203 View Code Duplication
                        if bbox[3] is None:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
204
                            bbox[3] = float(t[2])
205
                        elif op == "&&" and bbox[3] >= t[2]:
206
                            bbox[3] = float(t[2])
207
                        elif op == "||" and bbox[3] <= t[2]:
208
                            bbox[3] = float(t[2])
209
                    elif t[1] == "id" and t[0] == "==":
210
                        idquery = "%s(%i)" % (type_, t[2])
211
                        set_name = "s%i" % id(idquery)
212
                        result += "%s->.%s;" % (idquery, set_name)
213
                        set_names.append(set_name)
214
                    elif t[1] == "id":
215
                        # We got an id query, but not with equality comparison
216
                        raise ValueError("id can only be queried with equality")
217
                    else:
218
                        tagquery = "%s[\"%s\"=\"%s\"]" % (type_, t[1], t[2])
219
                        set_name = "s%i" % id(tagquery)
220
                        result += "%s->.%s;" % (tagquery, set_name)
221
                        set_names.append(set_name)
222
223
            if bbox != [None, None, None, None]:
224
                if bbox[0] is None:
225
                    bbox[0] = -90.0
226
                if bbox[1] is None:
227
                    bbox[1] = -180.0
228
                if bbox[2] is None:
229
                    bbox[2] = 90.0
230
                if bbox[3] is None:
231
                    bbox[3] = 180.0
232
                bboxquery = "%s(%s,%s,%s,%s)" % (type_, bbox[0], bbox[1], bbox[2], bbox[3])
233
                set_name = "s%i" % id(bboxquery)
234
                result += "%s->.%s;" % (bboxquery, set_name)
235
                set_names.append(set_name)
236
237
            if op == "&&":
238
                result += "%s." % type_
239
                result += ".".join(set_names)
240
            elif op == "||":
241
                result += "("
242
                for s in set_names:
0 ignored issues
show
Coding Style Naming introduced by
The name s 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...
243
                    result += ".%s;" % s
244
                result += ")"
245
        else:
246
            if tree[1] == "latitude" and tree[0] == ">":
247
                result = "%s(%s,-180.0,90.0,180.0)" % (type_, tree[2])
248
            elif tree[1] == "latitude" and tree[0] == "<":
249
                result = "%s(-90.0,-180.0,%s,180.0)" % (type_, tree[2])
250
            elif tree[1] == "longitude" and tree[0] == ">":
251
                result = "%s(-90.0,%s,-90.0,180.0)" % (type_, tree[2])
252
            elif tree[1] == "longitude" and tree[0] == "<":
253
                result = "%s(-90.0,-180.0,-90.0,%s)" % (type_, tree[2])
254
            elif tree[1] == "id" and tree[0] == "==":
255
                result = "%s(%i)" % (type_, tree[2])
256
            elif tree[1] == "id":
257
                # We got an id query, but not with equality comparison
258
                raise ValueError("id can only be queried with equality")
259
            else:
260
                result = "%s[\"%s\"=\"%s\"]" % (type_, tree[1], tree[2])
261
262
        set_name = id(result)
263
        result += "->.s%i;" % set_name
264
        return (set_name, result)
265
266
    results = []
267
    for type_ in tree_dict.keys():
268
        results.append(_tree_to_overpassql_recursive(tree_dict[type_], type_[3:].lower(), None))
269
270
    result = ""
271
    set_names = "("
272
    for r in results:
0 ignored issues
show
Coding Style Naming introduced by
The name r 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...
273
        result += r[1]
274
        set_names += ".s%s; " % r[0]
275
    set_names = "%s);" % set_names.strip(" ")
276
    result += set_names
277
278
    return result
279