在 SQLAlchemy 中使用 earthdistance extension 中的 earth 类型的列是可能的:
column_property 实现起来有点简单,但仅限于只读,并且如果声明性列不可用,则需要一些非正统的语法:
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))
为了生成具有正确_selectable的column,属性必须在创建后添加到类Thing,因为以声明方式创建的Table不可用在类体内创建期间。这允许像
这样的查询
session.query(Thing.earth_location_longitude).scalar()
按预期工作。如果没有 _selectable,发出的 SQL 将是:
SELECT longitude(loc)
不幸的是,这使得映射表完全忘记了基础列things.earth_location,因此 Alembic 也不会更明智。在 Alembic 中创建表必须通过执行原始 SQL 字符串来完成。
UserDefinedType 的优点是能够支持表创建。原始的earth 值在 python 上下文中非常无用,因此需要一些与ll_to_earth、latitude 和longitude 函数来回切换的值。将UserDefinedType 与column_property 结合起来或许可以提供“两全其美”的解决方案:
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))
检查表创建:
In [21]: t = Table('test', meta, Column('loc', EARTH))
In [22]: print(CreateTable(t))
CREATE TABLE test (
loc earth
)
添加一个新的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()
请注意,对ll_to_earth 的绑定函数调用是作为值提供的。
支持访问 lat 和 lon 作为属性等的更复杂的自定义类型是完全可能的,但可能超出此 q/a 的范围。