几何("点")列作为 str 对象返回



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

gps = Column(Geometry('POINT'))

我在模型类中实现了一个to_dict函数,为此我需要解构 gps 对象以给我纬度和长度。这在另一个模型中成功地为我工作。但是由于某种原因,在所讨论的类中,以下代码段会导致属性错误("str">对象没有属性"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() 函数没有做我认为它应该做的事情......

与此相关的是会话对象的配置。由于所有这些都在金字塔 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

起初我认为

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()访问器从数据库请求坐标:

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)

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

最新更新