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.