将几何(' POINT')列作为str对象

时间:2018-01-17 10:28:41

标签: python sqlalchemy pyramid geoalchemy2

我有一个sqlalchemy模型对象,其中包含以下列:

gps = Column(Geometry('POINT'))

我在模型类中实现了一个to_dict函数,为此我需要解构gps对象以给我lat和long。这在另一个模型中成功地适用于我。但由于某些原因,在相关课程中,以下代码会导致属性错误(' str'对象没有属性'数据' ) :

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框架下,我使用默认会话配置,如here所述(您可以跳过前几段,直到他们开始讨论/models/__init__.py文件.Ctrl + F if需要)。

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

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

1 个答案:

答案 0 :(得分: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在提交时关闭会话,但是实例未过期,但事实并非如此。这是因为在一段时间之前全局transaction在请求期间仍会影响正在进行的事务时使用了Pyramid,但现在默认值似乎是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)

因此甚至不需要刷新,过期和取出。