Issues (77)

osmalchemy/model.py (2 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
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):
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)
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):
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