【问题标题】:Geometry('POINT') column being returned as str objectGeometry('POINT') 列作为 str 对象返回
【发布时间】:2018-06-26 04:49:39
【问题描述】:

我有一个具有以下列的 sqlalchemy 模型对象:

gps = Column(Geometry('POINT'))

我已经在模型类中实现了一个 to_dict 函数,为此我需要解构 gps 对象来给我 lat 和 long。这在另一个模型中成功地为我工作。但由于某种原因,在有问题的类中,以下代码导致属性错误('str' object has no attribute 'data'):

point = wkb.loads(bytes(self.gps.data))

我像这样存储 gps 数据:

gps = Point(longitude, latitude).wkt

这是来自 postgresql 的表描述:

   Column    |            Type             |                     Modifiers                     | Storage | Stats target | Description 
-------------+-----------------------------+---------------------------------------------------+---------+--------------+-------------
 id          | integer                     | not null default nextval('pins_id_seq'::regclass) | plain   |              | 
 gps         | geometry(Point)             |                                                   | main    |              | 

只要 Pin 对象像这样创建,我就会调用 as dict 方法:

gps = Point(
        float(data['longitude']),
        float(data['latitude'])
    ).wkt
pin = Pin(gps=gps)
# Commit pin to disk 
# otherwise fields will 
# not return properly
with transaction.manager:
    self.dbsession.add(pin)
    transaction.commit()
    print (pin.as_dict())

让我发疯的是,某些代码确实适用于其他模型。任何见解将不胜感激。

编辑:根据 Ilja 的评论,我了解到问题在于该对象没有被写入磁盘,显然 Geometry 列将被视为字符串,直到发生这种情况。但是即使现在我也遇到了同样的错误。基本上,在这一点上,transaction.commit() 函数并没有做我认为应该做的事情......

与此相关的是会话对象的配置。由于所有这些都在 Pyramid Web 框架下,因此我使用默认会话配置,如 here 所述(您可以跳过前几段,直到他们开始讨论 /models/__init__.py 文件。如果需要,请按 Ctrl + F)。

如果我遗漏了一些重要的细节,请在下面复制有问题的类:

from geoalchemy2 import Geometry
from sqlalchemy import (
    Column,
    Integer,
)
from shapely import wkb

from .meta import Base


class Pin(Base):
    __tablename__ = 'pins'
    id = Column(Integer, primary_key=True)
    gps = Column(Geometry('POINT'))

    def as_dict(self):
        toret = {}
        point = wkb.loads(bytes(self.gps.data))
        lat = point.x
        lon = point.y
        toret['gps'] = {'lon': lon, 'lat': lat}
        return toret

【问题讨论】:

  • @IljaEverilä 感谢您的指点。我已根据您的建议更新了问题。
  • @IljaEverilä 我正在使用 Pyramid 框架中 request 对象包含的默认会话。会话被传递给充当模型接口的类。我使用了 transaction.manager,因为刚刚运行 self.dbsession.commit() 返回了一个错误,说只有事务管理器可以提交。
  • @IljaEverilä 我的配置(默认 Pyramid 配置如图所示 [link] (docs.pylonsproject.org/projects/pyramid/en/latest/tutorials/…)。它没有明确设置 expire_on_commit。我重写了该代码以将其设置为 false,但我仍然有同样的问题。有什么方法可以让我保持会话打开并让对象适当地返回属性(特别是几何)?

标签: python sqlalchemy pyramid geoalchemy2


【解决方案1】:

一开始我以为是这个原因

Traceback (most recent call last):
  ...
  File "/.../pyramid_test/views/default.py", line 28, in my_view
    print(pin.as_dict())
  File "/.../pyramid_test/models/pin.py", line 18, in as_dict
    point = wkb.loads(bytes(self.gps.data))
AttributeError: 'str' object has no attribute 'data'

zope.sqlalchemy 在提交时关闭会话,但使实例未过期,但事实并非如此。这是因为前段时间使用 Pyramid,当时全局 transaction 仍会影响请求期间正在进行的事务,但现在默认值似乎是 explicit transaction manager

实际的问题是transaction.commit()对当前会话正在进行的事务没有影响。添加一些日志记录将使这一点变得清晰:

with transaction.manager:
    self.dbsession.add(pin)
    transaction.commit()
    print("Called transaction.commit()")
    insp = inspect(pin)
    print(insp.transient,
          insp.pending,
          insp.persistent,
          insp.detached,
          insp.deleted,
          insp.session)

导致大约:

 % env/bin/pserve development.ini   
2018-01-19 14:36:25,113 INFO  [shapely.speedups._speedups:219][MainThread] Numpy was not imported, continuing without requires()
Starting server in PID 1081.
Serving on http://localhost:6543
...
Called transaction.commit()
False True False False False <sqlalchemy.orm.session.Session object at 0x7f958169d0f0>
...
2018-01-19 14:36:28,855 INFO  [sqlalchemy.engine.base.Engine:682][waitress] BEGIN (implicit)
2018-01-19 14:36:28,856 INFO  [sqlalchemy.engine.base.Engine:1151][waitress] INSERT INTO pins (gps) VALUES (ST_GeomFromEWKT(%(gps)s)) RETURNING pins.id
2018-01-19 14:36:28,856 INFO  [sqlalchemy.engine.base.Engine:1154][waitress] {'gps': 'POINT (1 1)'}
2018-01-19 14:36:28,881 INFO  [sqlalchemy.engine.base.Engine:722][waitress] COMMIT

可以看出,没有发生提交,实例仍在pending state 中,因此其gps 属性包含来自分配的文本值。如果您希望按照自己的方式处理序列化,您可以先flush 更改数据库,然后expire 实例属性:

gps = Point(
        float(data['longitude']),
        float(data['latitude'])
    ).wkt
pin = Pin(gps=gps)
self.dbsession.add(pin)
self.dbsession.flush()
self.dbsession.expire(pin, ['gps'])  # expire the gps attr
print(pin.as_dict())  # SQLAlchemy will fetch the value from the DB

另一方面,您也可以避免在应用程序中处理 (E)WKB 表示并直接使用例如column_property() 访问器从 DB 请求坐标:

class Pin(Base):
    __tablename__ = 'pins'
    id = Column(Integer, primary_key=True)
    gps = Column(Geometry('POINT'))
    gps_x = column_property(gps.ST_X())
    gps_y = column_property(gps.ST_Y())

    def as_dict(self):
        toret = {}
        toret['gps'] = {'lon': self.gps_y, 'lat': self.gps_x}
        return toret

这样,手册expire(pin) 就变得不必要了,因为在这种情况下,列属性无论如何都必须刷新自己。当然,由于您在构建新的Pin 时已经知道坐标,因此您可以预先填充它们:

lon = float(data['longitude'])
lat = float(data['latitude'])
gps = Point(lon, lat).wkt
pin = Pin(gps=gps, gps_x=lat, gps_y=lon)

因此甚至不需要刷新、过期和提取。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    • 1970-01-01
    • 2011-05-07
    • 1970-01-01
    • 2015-05-23
    • 2015-03-11
    相关资源
    最近更新 更多