Issues (77)

osmalchemy/osmalchemy.py (9 issues)

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
""" Module that holds the main OSMAlchemy class.
29
30
The classe encapsulates the model and accompanying logic.
31
"""
32
33 1
from sqlalchemy.engine import Engine
34 1
from sqlalchemy.ext.declarative import declarative_base
35 1
from sqlalchemy.orm import sessionmaker, scoped_session
36 1
try:
37 1
    from flask_sqlalchemy import SQLAlchemy as FlaskSQLAlchemy
38
except ImportError:
39
    # non-fatal, Flask-SQLAlchemy support is optional
40
    # Create stub to avoid bad code later on
41
    class FlaskSQLAlchemy(object):
0 ignored issues
show
This class should have a docstring.

The coding style of this project requires that you add a docstring to this code element. Below, you find an example for methods:

class SomeClass:
    def some_method(self):
        """Do x and return foo."""

If you would like to know more about docstrings, we recommend to read PEP-257: Docstring Conventions.

Loading history...
42
        pass
43
44 1
from .model import _generate_model
45 1
from .util.db import _import_osm_file
46 1
from .util.online import _generate_overpass_api
47 1
from .triggers import _generate_triggers
48
49 1
class OSMAlchemy(object):
0 ignored issues
show
Too many instance attributes (12/7)
Loading history...
50
    """ Wrapper class for the OSMAlchemy model and logic
51
52
    This class holds all the SQLAlchemy classes and logic that make up
53
    OSMAlchemy. It is contained in a separate class because it is a
54
    template that can be modified as needed by users, e.g. by using a
55
    different table prefix or a different declarative base.
56
    """
57
58 1
    def __init__(self, sa, prefix="osm_", overpass=None, maxage=60*60*24):
59
        """ Initialise the table definitions in the wrapper object
60
61
        This function generates the OSM element classes as SQLAlchemy table
62
        declaratives.
63
64
        Positional arguments:
65
66
          sa - reference to SQLAlchemy stuff; can be either of…
67
                 …an Engine instance, or…
68
                 …a tuple of (Engine, Base), or…
69
                 …a tuple of (Engine, Base, ScopedSession), or…
70
                 …a Flask-SQLAlchemy instance.
71
          prefix - optional; prefix for table names, defaults to "osm_"
72
          overpass - optional; API endpoint URL for Overpass API. Can be…
73
                      …None to disable loading data from Overpass (the default), or…
74
                      …True to enable the default endpoint URL, or…
75
                      …a string with a custom endpoint URL.
76
          maxage - optional; the maximum age after which elements are refreshed from
77
                   Overpass, in seconds, defaults to 86400s (1d)
78
        """
79
80
        # Store logger or create mock
81 1
        import logging
82 1
        self.logger = logging.getLogger('osmalchemy')
83 1
        self.logger.addHandler(logging.NullHandler())
84
85
        # Create fields for SQLAlchemy stuff
86 1
        self.base = None
87
        self.engine = None
88 1
        self.session = None
89 1
90 1
        # Inspect sa argument
91 1
        if isinstance(sa, tuple):
92 1
            # Got tuple of (Engine, Base) or (Engine, Base, ScopedSession)
93
            self.engine = sa[0]
94 1
            self.base = sa[1]
95 1
            if len(sa) == 3:
96 1
                self.session = sa[2]
97 1
            else:
98
                self.session = scoped_session(sessionmaker(bind=self.engine))
99 1
            self.logger.debug("Called with (engine, base, session) tuple.")
100 1
        elif isinstance(sa, Engine):
101 1
            # Got a plain engine, so derive the rest from it
102
            self.engine = sa
103
            self.base = declarative_base(bind=self.engine)
104
            self.session = scoped_session(sessionmaker(bind=self.engine))
105
            self.logger.debug("Called with a plain SQLAlchemy engine.")
106
        elif isinstance(sa, FlaskSQLAlchemy):
107 1
            # Got a Flask-SQLAlchemy instance, extract everything from it
108
            self.engine = sa.engine
109
            self.base = sa.Model
110 1
            self.session = sa.session
111
            self.logger.debug("Called with a Flask-SQLAlchemy wrapper.")
112
        else:
113
            # Something was passed, but none of the expected argument types
114
            raise TypeError("Invalid argument passed to sa parameter.")
115
116
        # Store prefix
117
        self.prefix = prefix
118
119
        # Store maxage
120
        self.maxage = maxage
121
122 1
        # Store API endpoint for Overpass
123
        if not overpass is None:
124
            if overpass is True:
125 1
                # Use default endpoint URL from overpass module
126
                self.overpass = _generate_overpass_api()
127
                self.logger.debug("Overpass API enabled with default endpoint.")
128
            elif isinstance(overpass, str):
129 1
                # Pass given argument as custom URL
130
                self.overpass = _generate_overpass_api(overpass)
131
                self.logger.debug("Overpass API enabled with endpoint %s." % overpass)
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
132 1
            else:
133
                # We got something unknown passed, bail out
134
                raise TypeError("Invalid argument passed to overpass parameter.")
135
        else:
136
            # Do not use overpass
137
            self.overpass = None
138
139 1
        # Generate model and store as instance members
140
        self.node, self.way, self.relation, self.element, self.cached_query = _generate_model(self)
141
        self.logger.debug("Generated OSMAlchemy model with prefix %s." % prefix)
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
142
143
        # Add triggers if online functionality is enabled
144
        if not self.overpass is None:
145
            _generate_triggers(self)
146
            self.logger.debug("Triggers generated and activated.")
147
148
    def import_osm_file(self, path):
149
        """ Import data from an OSM XML file into this model.
150
151
          path - path to the file to import
152
        """
153
154
        # Call utility funtion with own reference and session
155
        _import_osm_file(self, path)
156
157
    def create_api(self, api_manager):
158
        """ Create Flask-Restless API endpoints. """
159
160
        def _expand_tags(obj):
161
            # Type name to object mapping
162
            _types = {
163
                      "node": self.node,
0 ignored issues
show
Wrong hanging indentation (remove 6 spaces).
Loading history...
164
                      "way": self.way,
0 ignored issues
show
Wrong hanging indentation (remove 6 spaces).
Loading history...
165
                      "relation": self.relation
0 ignored issues
show
Wrong hanging indentation (remove 6 spaces).
Loading history...
166
                     }
0 ignored issues
show
Wrong hanging indentation.
Loading history...
167
168
            # Get tags dictionary from ORM object
169
            instance = self.session.query(_types[obj["type"]]).get(obj["element_id"])
0 ignored issues
show
The Instance of scoped_session does not seem to have a member named query.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
170
171
            # Fill a tag dictionary
172
            res = {}
173
            for key in obj["tags"]:
174
                res[key] = instance.tags[key]
175
176
            # Replace tags list with generated dictionary
177
            obj["tags"] = res
178
179
        def _cleanup(obj):
180
            # Remove unnecessary entries from dict
181
            del obj["osm_elements_tags"]
182
            del obj["type"]
183
184
        def _post_get(result, **_):
185
            # Post-processor for GET
186
            # Work-around for strange bug in Flask-Restless preventing detection
187
            #  of dictionary-like association proxies
188
            if "objects" in result:
189
                # This is a GET_MANY call
190
                for obj in result["objects"]:
191
                    _expand_tags(obj)
192
                    _cleanup(obj)
193
            else:
194
                # This is a GET_SINGLE call
195
                _expand_tags(result)
196
                _cleanup(result)
197
198
        # Define post-processors for all collections
199
        postprocessors = {"GET_SINGLE": [_post_get], "GET_MANY": [_post_get]}
200
201
        # Define collections for all object types
202
        api_manager.create_api(self.node, postprocessors=postprocessors)
203
        api_manager.create_api(self.way, postprocessors=postprocessors)
204
        api_manager.create_api(self.relation, postprocessors=postprocessors)
205