Issues (77)

osmalchemy/model.py (6 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
""" Simple representation of OpenStreetMap's conceptual data model.
29
(cf. http://wiki.openstreetmap.org/wiki/Elements)
30
31
This implementation of the model assumes that only current data is used,
32
not historic data.
33
"""
34
35 1
import datetime
36 1
from sqlalchemy import (Column, ForeignKey, Integer, BigInteger, Numeric, String, Unicode,
37
                        DateTime, Boolean, UniqueConstraint)
38 1
from sqlalchemy.ext.declarative import declarative_base
0 ignored issues
show
Unused declarative_base imported from sqlalchemy.ext.declarative
Loading history...
39 1
from sqlalchemy.ext.associationproxy import association_proxy
40 1
from sqlalchemy.ext.orderinglist import ordering_list
41 1
from sqlalchemy.orm import relationship, backref
42 1
from sqlalchemy.orm.collections import attribute_mapped_collection
43
44 1
def _generate_model(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...
45
    """ Generates the data model.
46
47
    The model classes are generated dynamically to allow passing in a
48
    declarative base and a prefix.
49
    """
50
51 1
    class OSMTag(oa.base):
52
        """ An OSM tag element.
53
54
        Simple key/value pair.
55
        """
56
57
        # Name of the table in the database, prefix provided by user
58 1
        __tablename__ = oa.prefix + "tags"
59
60
        # The internal ID of the element, only for structural use
61 1
        tag_id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True)
62
63
        # Key/value pair
64 1
        key = Column(Unicode(256))
65 1
        value = Column(Unicode(256))
66
67 1
        def __init__(self, key="", value="", **kwargs):
68
            """ Initialisation with two main positional arguments.
69
70
            Shorthand for OSMTag(key, value)
71
            """
72
73 1
            self.key = key
74 1
            self.value = value
75
76
            # Pass rest on to default constructor
77 1
            oa.base.__init__(self, **kwargs)
78
79 1
    class OSMElement(oa.base):
80
        """ Base class for all the conceptual OSM elements. """
81
82
        # Name of the table in the database, prefix provided by user
83 1
        __tablename__ = oa.prefix + "elements"
84
85
        # The internal ID of the element, only for structural use
86 1
        element_id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True)
87
88
        # Track element modification for OSMAlchemy caching
89 1
        osmalchemy_updated = Column(DateTime, default=datetime.datetime.now,
90
                                    onupdate=datetime.datetime.now)
91
92
        # The type of the element, used by SQLAlchemy for polymorphism
93 1
        type = Column(String(256))
94
95
        # ID of the element in OSM, not to be confused with the primary key element_id
96 1
        id = Column(BigInteger)
0 ignored issues
show
Coding Style Naming introduced by
The name id does not conform to the class attribute naming conventions (([A-Za-z_][A-Za-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...
97
98
        # Tags belonging to the element
99
        # Accessed as a dictionary like {'name': 'value', 'name2': 'value2',…}
100
        # Uses proxying across several tables to OSMTag
101 1
        tags = association_proxy(oa.prefix+"elements_tags", "tag_value",
102
                                 creator=lambda k, v: OSMElementsTags(tag=OSMTag(key=k, value=v)))
103
104
        # Metadata shared by all element types
105 1
        version = Column(Integer)
106 1
        changeset = Column(BigInteger)
107 1
        user = Column(Unicode(256))
108 1
        uid = Column(BigInteger)
109 1
        visible = Column(Boolean)
110 1
        timestamp = Column(DateTime)
111
112
        # OSM ids are unique per type
113 1
        _u_type_id = UniqueConstraint("type", "id")
114
115
        # Configure polymorphism
116 1
        __mapper_args__ = {
117
            'polymorphic_identity': 'element',
118
            'polymorphic_on': type,
119
            'with_polymorphic': '*'
120
        }
121
122 1
    class OSMElementsTags(oa.base):
123
        """ Secondary mapping table for elements and tags """
124
125
        # Name of the table in the database, prefix provided by user
126 1
        __tablename__ = oa.prefix + "elements_tags"
127
128
        # Internal ID of the mapping, only for structural use
129 1
        map_id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True)
130
131
        # Foreign key columns for the element and tag of the mapping
132 1
        element_id = Column(BigInteger().with_variant(Integer, "sqlite"),
133
                            ForeignKey(oa.prefix + 'elements.element_id'))
134 1
        tag_id = Column(BigInteger().with_variant(Integer, "sqlite"),
135
                        ForeignKey(oa.prefix + 'tags.tag_id'))
136
137
        # Relationship with all the tags mapped to the element
138
        # The backref is the counter-part to the tags association proxy
139
        # in OSMElement to form the dictionary
140 1
        element = relationship(OSMElement, foreign_keys=[element_id],
141
                               backref=backref(oa.prefix+"elements_tags",
142
                                               collection_class=attribute_mapped_collection(
143
                                                   "tag_key"
144
                                               ), cascade="all, delete-orphan"))
145
146
        # Relationship to the tag object and short-hand for its key and value
147 1
        # for use in the association proxy
148 1
        tag = relationship(OSMTag, foreign_keys=[tag_id])
149 1
        tag_key = association_proxy("tag", "key")
150
        tag_value = association_proxy("tag", "value")
151 1
152
    class OSMNode(OSMElement):
153
        """ An OSM node element.
154
155
        A node hast a latitude and longitude, which are non-optional,
156
        and a list of zero or more tags.
157
        """
158
159 1
        # Name of the table in the database, prefix provided by user
160
        __tablename__ = oa.prefix + "nodes"
161
162
        # The internal ID of the element, only for structural use
163 1
        # Synchronised with the id of the parent table OSMElement through polymorphism
164
        element_id = Column(BigInteger().with_variant(Integer, "sqlite"),
165
                            ForeignKey(oa.prefix + 'elements.element_id'), primary_key=True)
166
167
        # Track element modification for OSMAlchemy caching
168 1
        osmalchemy_updated = Column(DateTime, default=datetime.datetime.now,
169 1
                                    onupdate=datetime.datetime.now)
170
171
        # Geographical coordinates of the node
172 1
        latitude = Column(Numeric(precision="9,7", asdecimal=False))
173
        longitude = Column(Numeric(precision="10,7", asdecimal=False))
174
175
        # Configure polymorphism with OSMElement
176 1
        __mapper_args__ = {
177
            'polymorphic_identity': 'node',
178
        }
179
180
        def __init__(self, latitude=None, longitude=None, **kwargs):
181
            """ Initialisation with two main positional arguments.
182 1
183 1
            Shorthand for OSMNode(lat, lon).
184
            """
185
186 1
            self.latitude = latitude
187
            self.longitude = longitude
188 1
189
            # Pass rest on to default constructor
190
            OSMElement.__init__(self, **kwargs)
0 ignored issues
show
The __init__ of the immediate ancestor is not called instead this calls
the method of another ancestor OSMElement.

It is usually advisable to call the __init__ method of the immediate ancestor instead of another ancestor in the inheritance chain:

class NonDirectParent:
    def __init__(self):
        pass

class DirectParent(NonDirectParent):
    def __init__(self):
        NonDirectParent.__init__(self)

class YourClass(DirectParent):
    def __init__(self):
        NonDirectParent.__init__(self) # Bad
        DirectParent.__init__(self)    # Good
Loading history...
The Class OSMElement does not seem to have a member named __init__.

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...
191
192 1
    class OSMWaysNodes(oa.base):
193
        """ Secondary mapping table for ways and nodes """
194
195 1
        # Name of the table in the database, prefix provided by user
196
        __tablename__ = oa.prefix + "ways_nodes"
197
198 1
        # Internal ID of the mapping, only for structural use
199
        map_id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True)
200 1
201
        # Foreign key columns for the connected way and node
202
        way_id = Column(BigInteger().with_variant(Integer, "sqlite"),
203 1
                        ForeignKey(oa.prefix + 'ways.element_id'))
204
        node_id = Column(BigInteger().with_variant(Integer, "sqlite"),
205
                         ForeignKey(oa.prefix + 'nodes.element_id'))
206 1
        # Relationships for proxy access
207
        node = relationship(OSMNode, foreign_keys=[node_id])
208 1
209
        # Index of the node in the way to maintain ordered list, structural use only
210
        position = Column(Integer)
211
212
    class OSMWay(OSMElement):
213
        """ An OSM way element (also area).
214
215
        Contains a list of two or more nodes and a list of zero or more
216 1
        tags.
217
        """
218
219
        # Name of the table in the database, prefix provided by user
220 1
        __tablename__ = oa.prefix + "ways"
221
222
        # The internal ID of the element, only for structural use
223
        # Synchronised with the id of the parent table OSMElement through polymorphism
224
        element_id = Column(BigInteger().with_variant(Integer, "sqlite"),
225
                            ForeignKey(oa.prefix + 'elements.element_id'), primary_key=True)
226 1
227
        # Track element modification for OSMAlchemy caching
228 1
        osmalchemy_updated = Column(DateTime, default=datetime.datetime.now,
229
                                    onupdate=datetime.datetime.now)
230
231
        # Relationship with all nodes in the way
232 1
        # Uses association proxy and a collection class to maintain an ordered list,
233
        # synchronised with the position field of OSMWaysNodes
234
        _nodes = relationship(OSMWaysNodes, order_by="OSMWaysNodes.position",
235
                              collection_class=ordering_list("position"))
236 1
        nodes = association_proxy("_nodes", "node",
237
                                  creator=lambda _n: OSMWaysNodes(node=_n))
238
239
        # Configure polymorphism with OSMElement
240 1
        __mapper_args__ = {
241
            'polymorphic_identity': 'way',
242
        }
243 1
244
    class OSMRelationsElements(oa.base):
245
        """ Secondary mapping table for relation members """
246 1
247
        # Name of the table in the database, prefix provided by user
248 1
        __tablename__ = oa.prefix + "relations_elements"
249
250
        # Internal ID of the mapping, only for structural use
251 1
        map_id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True)
252
253
        # Foreign ley columns for the relation and other element of the mapping
254 1
        relation_id = Column(BigInteger().with_variant(Integer, "sqlite"),
255
                             ForeignKey(oa.prefix + 'relations.element_id'))
256
        element_id = Column(BigInteger().with_variant(Integer, "sqlite"),
257 1
                            ForeignKey(oa.prefix + 'elements.element_id'))
258
        # Relationships for proxy access
259
        element = relationship(OSMElement, foreign_keys=[element_id])
260 1
261
        # Role of the element in the relationship
262 1
        role = Column(Unicode(256))
263
264 1
        # Index of element in the relationship to maintain ordered list, structural use only
265
        position = Column(Integer)
266
267
        # Produce (element, role) tuple for proxy access in OSMRelation
268
        @property
269
        def role_tuple(self):
0 ignored issues
show
This method 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...
270
            return (self.element, self.role)
271
272 1
    class OSMRelation(OSMElement):
273
        """ An OSM relation element.
274
275
        Contains zero or more members (ways, nodes or other relations)
276 1
        with associated, optional roles and zero or more tags.
277
        """
278
279
        # Name of the table in the database, prefix provided by user
280 1
        __tablename__ = oa.prefix + "relations"
281
282
        # The internal ID of the element, only for structural use
283
        # Synchronised with the id of the parent table OSMElement through polymorphism
284 1
        element_id = Column(BigInteger().with_variant(Integer, "sqlite"),
285
                            ForeignKey(oa.prefix + 'elements.element_id'), primary_key=True)
286
287
        # Track element modification for OSMAlchemy caching
288
        osmalchemy_updated = Column(DateTime, default=datetime.datetime.now,
289 1
                                    onupdate=datetime.datetime.now)
290
291
        # Relationship to the members of the relationship, proxied across OSMRelationsElements
292
        _members = relationship(OSMRelationsElements,
293
                                order_by="OSMRelationsElements.position",
294 1
                                collection_class=ordering_list("position"))
295
        # Accessed as a list like [(element, "role"), (element2, "role2")]
296
        members = association_proxy("_members", "role_tuple",
297
                                    creator=lambda _m: OSMRelationsElements(element=_m[0],
298
                                                                            role=_m[1]))
299
300
        # Configure polymorphism with OSMElement
301
        __mapper_args__ = {
302
            'polymorphic_identity': 'relation',
303
        }
304
305
    class OSMCachedQuery(oa.base):
306
        """ Table for caching queries run by the OSMAlchemy triggers. """
307
308
        __tablename__ = oa.prefix + "cached_queries"
309
310
        # The internal ID of the element, only for structural use
311
        query_id = Column(BigInteger().with_variant(Integer, "sqlite"), primary_key=True)
312
313
        # Hash sum of the query to identify it
314
        oql_hash = Column(Unicode(32), unique=True)
315
        # Last time this query was run
316
        oql_queried = Column(DateTime, default=datetime.datetime.now,
317
                             onupdate=datetime.datetime.now)
318
319
    # Return the relevant generated objects
320
    return (OSMNode, OSMWay, OSMRelation, OSMElement, OSMCachedQuery)
321