我有一个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
答案 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)
因此甚至不需要刷新,过期和取出。