Passed
Push — master ( e71bc5...6634bf )
by Dominik
15:01
created

_generate_triggers()   F

Complexity

Conditions 16

Size

Total Lines 114

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 255.6934

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 16
c 4
b 0
f 0
dl 0
loc 114
ccs 1
cts 46
cp 0.0217
crap 255.6934
rs 2

2 Methods

Rating   Name   Duplication   Size   Complexity  
D _analyse_clause() 0 68 10
F _query_compiling() 0 105 15

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 _generate_triggers() 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
""" Trigger code for live OSMAlchemy/Overpass integration. """
29
30 1
import datetime
0 ignored issues
show
Unused Code introduced by
The import datetime seems to be unused.
Loading history...
31 1
import operator
32 1
from sqlalchemy import inspect
0 ignored issues
show
Unused Code introduced by
Unused inspect imported from sqlalchemy
Loading history...
33 1
from sqlalchemy.sql.elements import BinaryExpression, BooleanClauseList, BindParameter
34 1
from sqlalchemy.event import listens_for
35 1
from sqlalchemy.orm import Query
36 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...
37 1
from weakref import WeakSet
0 ignored issues
show
introduced by
standard import "from weakref import WeakSet" comes before "from sqlalchemy import inspect"
Loading history...
38
39 1
from .online import _get_single_element_by_id
0 ignored issues
show
Unused Code introduced by
Unused _get_single_element_by_id imported from online
Loading history...
40 1
from .util import _import_osm_xml
0 ignored issues
show
Unused Code introduced by
Unused _import_osm_xml imported from util
Loading history...
41
42 1
def _generate_triggers(osmalchemy, maxage=60*60*24):
0 ignored issues
show
Unused Code introduced by
The argument maxage seems to be unused.
Loading history...
43
    """ Generates the triggers for online functionality.
44
45
      osmalchemy - reference to the OSMAlchemy instance to be configured
46
      maxage - maximum age of objects before they are updated online, in seconds
47
    """
48
49
    _visited_queries = WeakSet()
50
51
    @listens_for(Query, "before_compile")
52
    def _query_compiling(query):
0 ignored issues
show
Unused Code introduced by
The variable _query_compiling seems to be unused.
Loading history...
53
        # Get the session associated with the query:
54
        session = query.session
0 ignored issues
show
Unused Code introduced by
The variable session seems to be unused.
Loading history...
55
56
        # Prevent recursion by skipping already-seen queries
57
        if query in _visited_queries:
58
            return
59
        else:
60
            _visited_queries.add(query)
61
62
        # Check whether this query affects our model
63
        affected_models = set([c["type"] for c in query.column_descriptions])
64
        our_models = set([osmalchemy.Node, osmalchemy.Way,  osmalchemy.Relation,
0 ignored issues
show
Coding Style introduced by
Exactly one space required after comma
Loading history...
65
                          osmalchemy.Element])
66
        if affected_models.isdisjoint(our_models):
67
            # None of our models is affected
68
            return
69
70
        # Check whether this query filters elements
71
        # Online update will only run on a specified set, not all data
72
        if query.whereclause is None:
73
            # No filters
74
            return
75
76
        # Define operator to string mapping
77
        _ops = {operator.eq: "==",
78
                operator.ne: "!=",
79
                operator.lt: "<",
80
                operator.gt: ">",
81
                operator.le: "<=",
82
                operator.ge: ">=",
83
                operator.and_: "&&",
84
                operator.or_: "||"}
85
86
        # Traverse whereclause recursively
87
        def _analyse_clause(clause):
0 ignored issues
show
best-practice introduced by
Too many return statements (8/6)
Loading history...
88
            if type(clause) is BinaryExpression:
0 ignored issues
show
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
89
                # This is something like "latitude >= 51.0"
90
                left = clause.left
91
                right = clause.right
92
                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...
93
94
                # Left part should be a column
95
                if type(left) is AnnotatedColumn:
0 ignored issues
show
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
96
                    # Get table class and field
97
                    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...
98
                    field = left
99
100
                    # Double-check this model belongs to us
101
                    if model in our_models:
102
                        # Convert model class and field to string names
103
                        left = (model.__name__, field.name)
104
                    else:
105
                        return None
106
                else:
107
                    # Right now, we cannot cope with anything but a column on the left
108
                    return None
109
110
                # Right part should be a literal value
111
                if type(right) is BindParameter:
0 ignored issues
show
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
112
                    # Extract literal value
113
                    right = right.value
114
                else:
115
                    # Right now, we cannot cope with something else here
116
                    return None
117
118
                # Look for a known operator
119
                if op in _ops.keys():
120
                    # Get string representation
121
                    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...
122
                else:
123
                    # Right now, we cannot cope with other operators
124
                    return None
125
126
                # Return polish notation tuple of this clause
127
                return (op, left, right)
128
            elif type(clause) is BooleanClauseList:
0 ignored issues
show
introduced by
Using type() instead of isinstance() for a typecheck.
Loading history...
129
                # This is an AND or OR operation
130
                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...
131
                clauses = []
132
133
                # Iterate over all the clauses in this operation
134
                for clause in clause.clauses:
135
                    # Recursively analyse clauses
136
                    res = _analyse_clause(clause)
137
                    # None is returned for unsupported clauses or operations
138
                    if res is not None:
139
                        # Append polish notation result to clauses list
140
                        clauses.append(res)
141
142
                # Look for a known operator
143
                if op in _ops.keys():
144
                    # Get string representation
145
                    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...
146
                else:
147
                    # Right now, we cannot cope with anything else
148
                    return None
149
150
                # Return polish notation tuple of this clause
151
                return (op, clauses)
152
            else:
153
                # We hit an unsupported type of clause
154
                return None
155
        tree = _analyse_clause(query.whereclause)
0 ignored issues
show
Unused Code introduced by
The variable tree seems to be unused.
Loading history...
156