【问题标题】:how to map a postgres EARTH column in sqlalchemy如何在 sqlalchemy 中映射 postgres EARTH 列
【发布时间】:2018-06-11 04:28:23
【问题描述】:

我正在使用 sqlalchemy 和 alembic 进行迁移(和 flask-sqlalchemy)。我有一个使用 EARTH 数据类型的 postgres 表。

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

这是我的 sqlalchemy 映射:

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(???)

如何映射地球柱?

alembic 迁移能够处理它吗?

谢谢!

【问题讨论】:

  • 您对只读列是否满意,还是需要 r/w?
  • 只读是好的(至少在可预测的未来),但绝对是在 alembic 迁移创建期间工作的映射。

标签: python sqlalchemy


【解决方案1】:

在 SQLAlchemy 中使用 earthdistance extension 中的 earth 类型的列是可能的:

  • column_property,如果您只想将纬度和经度值映射到属性
  • UserDefinedType,如果您希望拥有一个支持例如创建表的完整类型

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))

为了生成具有正确_selectablecolumn,属性必须在创建后添加到类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_earthlatitudelongitude 函数来回切换的值。将UserDefinedTypecolumn_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 的范围。

【讨论】:

  • 哇,这是一个很好的答案,正是我想要的。我希望我能投票两次!谢谢。
  • 请注意column(..., _selectable=...) hack:它不适用于aliased() 等。
猜你喜欢
  • 2015-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-03
  • 2021-09-27
  • 1970-01-01
  • 2014-04-08
  • 1970-01-01
相关资源
最近更新 更多