_generate_triggers()   F
last analyzed

Complexity

Conditions 17

Size

Total Lines 129

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 306

Importance

Changes 15
Bugs 0 Features 0
Metric Value
cc 17
c 15
b 0
f 0
dl 0
loc 129
ccs 0
cts 22
cp 0
crap 306
rs 2

2 Methods

Rating   Name   Duplication   Size   Complexity  
F _query_compiling() 0 81 12
B _instance_loading() 0 39 4

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
# 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 1
29
""" Trigger code for live OSMAlchemy/Overpass integration. """
30 1
31 1
import datetime
32 1
from hashlib import md5
33 1
from sqlalchemy import inspect
0 ignored issues
show
Unused Code introduced by
Unused inspect imported from sqlalchemy
Loading history...
34 1
from sqlalchemy.event import listens_for
35
from sqlalchemy.orm import Query
36 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...
37 1
38
from .util.db import _import_osm_xml
39 1
from .util.online import (_get_elements_by_query, _where_to_tree,
40
                          _trees_to_overpassql, _normalise_overpassql,
41
                          _get_single_element_by_id)
42
43
def _generate_triggers(oa):
0 ignored issues
show
Coding Style Naming introduced by
The name oa 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...
44
    """ Generates the triggers for online functionality.
45
46
      oa - reference to the OSMAlchemy instance to be configured
47
    """
48
49
    _visited_queries = WeakSet()
50
51
    @listens_for(oa.node, "load")
52
    @listens_for(oa.way, "load")
53
    @listens_for(oa.relation, "load")
54
    def _instance_loading(target, context, _=None):
0 ignored issues
show
Unused Code introduced by
The variable _instance_loading seems to be unused.
Loading history...
55
        # Get query session
56
        session = context.session
57
58
        # Skip if the session was in a trigger before
59
        # Prevents recursion in import code
60
        if hasattr(session, "_osmalchemy_in_trigger"):
61
            return
62
63
        # Determine OSM element type and id
64
        type_ = target.__class__.__name__[3:].lower()
65
        id_ = target.id
66
67
68
        # Guard against broken objects without an id
69
        if id_ is None:
70
            return
71
72
        oa.logger.debug("Loading instance of type %s with id %i." % (type_, id_))
73
74
        # Check whether object needs to be refreshed
75
        updated = target.osmalchemy_updated
76
        timediff = datetime.datetime.now() - updated
77
        if timediff.total_seconds() < oa.maxage:
78
            oa.logger.debug("Instance is only %i seconds old, not refreshing online." % timediff.total_seconds())
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (113/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
79
            return
80
81
        oa.logger.debug("Refreshing instance online.")
82
83
        # Get object by id as XML
84
        xml = _get_single_element_by_id(oa.overpass, type_, id_)
85
86
        # Import data
87
        session._osmalchemy_in_trigger = True
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _osmalchemy_in_trigger 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...
88
        _import_osm_xml(oa, xml, session=session)
89
        del session._osmalchemy_in_trigger
90
91
    @listens_for(Query, "before_compile")
92
    def _query_compiling(query):
0 ignored issues
show
Unused Code introduced by
The variable _query_compiling seems to be unused.
Loading history...
93
        # Get the session associated with the query:
94
        session = query.session
95
96
        # Skip if the session was in a trigger before
97
        # Prevents recursion in import code
98
        if hasattr(session, "_osmalchemy_in_trigger"):
99
            return
100
101
        # Prevent recursion by skipping already-seen queries
102
        if query in _visited_queries:
103
            return
104
        else:
105
            _visited_queries.add(query)
106
107
        oa.logger.debug("Analysing new ORM query.")
108
109
        # Check whether this query affects our model
110
        affected_models = set([c["type"] for c in query.column_descriptions])
111
        our_models = set([oa.node, oa.way,  oa.relation,
0 ignored issues
show
Coding Style introduced by
Exactly one space required after comma
Loading history...
112
                          oa.element])
113
        if affected_models.isdisjoint(our_models):
114
            # None of our models is affected
115
            oa.logger.debug("None of our models are affected.")
116
            return
117
118
        # Check whether this query filters elements
119
        # Online update will only run on a specified set, not all data
120
        if query.whereclause is None:
121
            # No filters
122
            oa.logger.debug("No filters found in query.")
123
            return
124
125
        oa.logger.debug("Building query tree from ORM query structure.")
126
127
        # Analyse where clause looking for all looked-up fields
128
        trees = {}
129
        for target in our_models.intersection(affected_models):
130
            # Build expression trees first
131
            tree = _where_to_tree(query.whereclause, target)
132
            if not tree is None:
133
                trees[target.__name__] = tree
134
135
        # Bail out if no relevant trees were built
136
        if not trees:
137
            oa.logger.debug("No relevant query trees built.")
138
            return
139
140
        # Compile to OverpassQL
141
        oql = _trees_to_overpassql(trees)
142
        oa.logger.debug("Compiled OverpassQL: %s" % oql)
143
144
        # Look up query in cache
145
        hashed_oql = md5(_normalise_overpassql(oql).encode()).hexdigest()
146
        cached_query = session.query(oa.cached_query).filter_by(oql_hash=hashed_oql).scalar()
147
        # Check age if cached query was found
148
        if cached_query:
149
            timediff = datetime.datetime.now() - cached_query.oql_queried
150
            if timediff.total_seconds() < oa.maxage:
151
                # Return and do nothing if query was run recently
152
                oa.logger.debug("Query was run online only %i seconds ago, not running." % timediff.total_seconds())
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (116/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
153
                return
154
155
        # Run query online
156
        oa.logger.debug("Running Overpass query.")
157
        xml = _get_elements_by_query(oa.overpass, oql)
158
159
        # Import data
160
        session._osmalchemy_in_trigger = True
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _osmalchemy_in_trigger 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...
161
        _import_osm_xml(oa, xml, session=session)
162
        del session._osmalchemy_in_trigger
163
164
        # Store or update query time
165
        if not cached_query:
166
            cached_query = oa.cached_query()
167
            cached_query.oql_hash = hashed_oql
168
        cached_query.oql_queried = datetime.datetime.now()
169
        session.add(cached_query)
170
        session.commit()
171
        oa.logger.debug("Query cached.")
172