简体   繁体   中英

how to map a postgres EARTH column in sqlalchemy

I'm using sqlalchemy and also alembic for migrations (and flask-sqlalchemy). I have a postgres table that uses the EARTH data type.

CREATE TABLE things
(
  id INTEGER PRIMARY KEY NOT NULL,
  name TEXT,
  earth_location EARTH
)

Here is my sqlalchemy mapping:

class Thing(db.Model):
    __tablename__ = 'things'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.UnicodeText, nullable=False)
    earth_location = db.Column(???)

How do I map the earth column?

Will alembic migration be able to handle it?

Thanks!

Using a column of type earth from the earthdistance extension with SQLAlchemy is possible with:

  • column_property , if you just wish to map the latitude and longitude values to attributes
  • UserDefinedType , if you wish to have a full blown type that supports for example table creation

A column_property is somewhat straightforward to implement, but is limited to being read-only and requires some unorthodox syntax, if a declarative column is not available:

from sqlalchemy import Float, column, func
from sqlalchemy.orm import column_property

class Thing(db.Model):
    __tablename__ = 'things'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.UnicodeText, nullable=False)

_earth_location = column('earth_location', _selectable=Thing.__table__)

Thing.earth_location_latitude = column_property(func.latitude(
        _earth_location, type_=Float))
Thing.earth_location_longitude = column_property(func.longitude(
        _earth_location, type_=Float))

In order to produce a column with the right _selectable the attributes have to be added to the class Thing after it has been created, because the declaratively created Table is not available during creation in the class body. This allows for queries like

session.query(Thing.earth_location_longitude).scalar()

to work as expected. Without the _selectable the issued SQL would be:

SELECT longitude(loc)

Unfortunately this leaves the mapped table completely oblivious of the underlying column things.earth_location and so Alembic will also be none the wiser. Table creation in Alembic would have to be done by executing a raw SQL string.

A UserDefinedType has the advantage of being able to support table creation. Raw earth values are pretty useless in python context, so some back and forth with ll_to_earth , latitude and longitude functions is required. Combining a UserDefinedType with a column_property could perhaps provide a "best of both worlds" solution:

from sqlalchemy.types import UserDefinedType
from sqlalchemy.orm import deferred


class EARTH(UserDefinedType):
    def get_col_spec(self, **kw):
        return 'EARTH'


class Thing(db.Model)
    __tablename__ = 'things'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.UnicodeText, nullable=False)
    # defer loading, as the raw value is pretty useless in python
    earth_location = deferred(db.Column(EARTH))
    # A deferred column, aka column_property cannot be adapted, extract the
    # real column. A bit of an ugly hack.
    latitude = column_property(func.latitude(*earth_location.columns,
                                             type_=Float))
    longitude = column_property(func.longitude(*earth_location.columns,
                                               type_=Float))

Checking table creation:

In [21]: t = Table('test', meta, Column('loc', EARTH))

In [22]: print(CreateTable(t))                        

CREATE TABLE test (
        loc earth
)

Adding a new Thing :

>>> latitude = 65.012089
>>> longitude = 25.465077
>>> t = Thing(name='some name',
              earth_location=func.ll_to_earth(latitude, longitude))
>>> session.add(t)
>>> session.commit()

Note that a bound function call to ll_to_earth is provided as value.

A more sophisticated custom type supporting accessing lat and lon as attributes etc. would be entirely possible, but perhaps out of scope of this q/a.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM