1

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

gps = Column(Geometry('POINT'))

我在模型类中实现了一个 to_dict 函数,为此我需要解构 gps 对象以给我经纬度。这在另一个模型中成功地为我工作。但由于某种原因,在有问题的类中,以下代码导致属性错误('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 的评论之后,我了解到问题是对象没有被写入磁盘,显然几何列将被视为字符串,直到发生这种情况。但是即使现在我也遇到了同样的错误。基本上,在这一点上,transaction.commit() 函数没有做我认为它应该做的事情......

与此相关的是会话对象的配置。由于所有这些都在 Pyramid Web 框架下,因此我使用默认会话配置,如此所述(您可以跳过前几段,直到他们开始讨论/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
4

1 回答 1

2

一开始我还以为是什么原因

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仍会影响请求期间正在进行的事务,但现在默认似乎是显式事务管理器

实际问题是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

可以看出,没有发生提交,并且实例仍处于挂起状态,因此其gps属性包含来自分配的文本值。如果您希望以您的方式处理序列化,您可以首先将更改刷新到数据库,然后使实例属性过期

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)就变得不必要了,因为在这种情况下,列属性无论如何都必须刷新自己。当然,既然你在构建 new 时已经知道你的坐标Pin,你可以预先填充它们:

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

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

于 2018-01-19T13:04:27.373 回答