summaryrefslogtreecommitdiff
path: root/examples/postgis/postgis.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2012-08-16 22:20:53 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2012-08-16 22:20:53 -0400
commit20fa7fe2b85d356e3da08191f01d7528ded42033 (patch)
treedd98bed523dbad242ac1db901cee5ccda541a83c /examples/postgis/postgis.py
parent0aff01bdfd7b9428334e862d5ff141b07298e75a (diff)
downloadsqlalchemy-20fa7fe2b85d356e3da08191f01d7528ded42033.tar.gz
- modernize most of the postgis example. would like to do [ticket:1534] also.
- we don't have coverage for type-wide instrumentation events, the listener was broke. could break again too.
Diffstat (limited to 'examples/postgis/postgis.py')
-rw-r--r--examples/postgis/postgis.py176
1 files changed, 85 insertions, 91 deletions
diff --git a/examples/postgis/postgis.py b/examples/postgis/postgis.py
index 1d930c5a1..1239d66a4 100644
--- a/examples/postgis/postgis.py
+++ b/examples/postgis/postgis.py
@@ -1,8 +1,6 @@
-from sqlalchemy.orm.interfaces import AttributeExtension
-from sqlalchemy.orm.properties import ColumnProperty
from sqlalchemy.types import UserDefinedType
from sqlalchemy.sql import expression
-from sqlalchemy import event
+from sqlalchemy import event, Table
# Python datatypes
@@ -21,7 +19,8 @@ class GisElement(object):
return self.desc
def __repr__(self):
- return "<%s at 0x%x; %r>" % (self.__class__.__name__, id(self), self.desc)
+ return "<%s at 0x%x; %r>" % (self.__class__.__name__,
+ id(self), self.desc)
class PersistentGisElement(GisElement):
"""Represents a Geometry value as loaded from the database."""
@@ -30,7 +29,8 @@ class PersistentGisElement(GisElement):
self.desc = desc
class TextualGisElement(GisElement, expression.Function):
- """Represents a Geometry value as expressed within application code; i.e. in wkt format.
+ """Represents a Geometry value as expressed within application code;
+ i.e. in wkt format.
Extends expression.Function so that the value is interpreted as
GeomFromText(value) in a SQL expression context.
@@ -52,12 +52,28 @@ class Geometry(UserDefinedType):
"""
+ name = "GEOMETRY"
+
def __init__(self, dimension=None, srid=-1):
self.dimension = dimension
self.srid = srid
+ class comparator_factory(UserDefinedType.Comparator):
+ """Define custom operations for geometry types."""
+
+ # override the __eq__() operator
+ def __eq__(self, other):
+ return self.op('~=')(_to_postgis(other))
+
+ # add a custom operator
+ def intersects(self, other):
+ return self.op('&&')(_to_postgis(other))
+
+ # any number of GIS operators can be overridden/added here
+ # using the techniques above.
+
def get_col_spec(self):
- return "GEOMETRY"
+ return self.name
def bind_processor(self, dialect):
def process(value):
@@ -75,8 +91,7 @@ class Geometry(UserDefinedType):
return value
return process
-# other datatypes can be added as needed, which
-# currently only affect DDL statements.
+# other datatypes can be added as needed.
class Point(Geometry):
name = 'POINT'
@@ -91,63 +106,76 @@ class LineString(Curve):
# DDL integration
+# Postgis historically has required AddGeometryColumn/DropGeometryColumn
+# and other management methods in order to create Postgis columns. Newer
+# versions don't appear to require these special steps anymore. However,
+# here we illustrate how to set up these features in any case.
-class GISDDL(object):
- """A DDL extension which integrates SQLAlchemy table create/drop
- methods with PostGis' AddGeometryColumn/DropGeometryColumn functions.
-
- Usage::
-
- sometable = Table('sometable', metadata, ...)
-
- GISDDL(sometable)
+def setup_ddl_events():
+ @event.listens_for(Table, "before_create")
+ def before_create(target, connection, **kw):
+ dispatch("before-create", target, connection)
- sometable.create()
+ @event.listens_for(Table, "after_create")
+ def after_create(target, connection, **kw):
+ dispatch("after-create", target, connection)
- """
-
- def __init__(self, table):
- event.listen(table, "before_create", self.before_create)
- event.listen(table, "after_create", self.after_create)
- event.listen(table, "before_drop", self.before_drop)
- event.listen(table, "after_drop", self.before_drop)
- self._stack = []
-
- def before_create(self, target, connection, **kw):
- self("before-create", target, connection)
-
- def after_create(self, target, connection, **kw):
- self("after-create", target, connection)
+ @event.listens_for(Table, "before_drop")
+ def before_drop(target, connection, **kw):
+ dispatch("before-drop", target, connection)
- def before_drop(self, target, connection, **kw):
- self("before-drop", target, connection)
+ @event.listens_for(Table, "after_drop")
+ def after_drop(target, connection, **kw):
+ dispatch("after-drop", target, connection)
- def after_drop(self, target, connection, **kw):
- self("after-drop", target, connection)
-
- def __call__(self, event, table, bind):
+ def dispatch(event, table, bind):
if event in ('before-create', 'before-drop'):
- regular_cols = [c for c in table.c if not isinstance(c.type, Geometry)]
+ regular_cols = [c for c in table.c if not
+ isinstance(c.type, Geometry)]
gis_cols = set(table.c).difference(regular_cols)
- self._stack.append(table.c)
+ table.info["_saved_columns"] = table.c
+
+ # temporarily patch a set of columns not including the
+ # Geometry columns
table.columns = expression.ColumnCollection(*regular_cols)
if event == 'before-drop':
for c in gis_cols:
- bind.execute(select([func.DropGeometryColumn('public', table.name, c.name)], autocommit=True))
+ bind.execute(
+ select([
+ func.DropGeometryColumn(
+ 'public', table.name, c.name)],
+ autocommit=True)
+ )
elif event == 'after-create':
- table.columns = self._stack.pop()
+ table.columns = table.info.pop('_saved_columns')
for c in table.c:
if isinstance(c.type, Geometry):
- bind.execute(select([func.AddGeometryColumn(table.name, c.name, c.type.srid, c.type.get_col_spec(), c.type.dimension)], autocommit=True))
+ bind.execute(
+ select([
+ func.AddGeometryColumn(
+ table.name, c.name,
+ c.type.srid,
+ c.type.name,
+ c.type.dimension)],
+ autocommit=True)
+ )
elif event == 'after-drop':
- table.columns = self._stack.pop()
+ table.columns = table.info.pop('_saved_columns')
+setup_ddl_events()
# ORM integration
def _to_postgis(value):
- """Interpret a value as a GIS-compatible construct."""
+ """Interpret a value as a GIS-compatible construct.
+
+
+ TODO. I'd like to make this unnecessary also,
+ and see if the Geometry type can do the coersion.
+ This would require [ticket:1534].
+
+ """
if hasattr(value, '__clause_element__'):
return value.__clause_element__()
@@ -160,54 +188,24 @@ def _to_postgis(value):
else:
raise Exception("Invalid type")
+# without importing "orm", the "attribute_instrument"
+# event isn't even set up.
+from sqlalchemy import orm
-class GisAttribute(AttributeExtension):
- """Intercepts 'set' events on a mapped instance attribute and
- converts the incoming value to a GIS expression.
-
- """
-
- def set(self, state, value, oldvalue, initiator):
- return _to_postgis(value)
-
-class GisComparator(ColumnProperty.ColumnComparator):
- """Intercepts standard Column operators on mapped class attributes
- and overrides their behavior.
-
- """
+@event.listens_for(type, "attribute_instrument")
+def attribute_instrument(cls, key, inst):
+ type_ = getattr(inst, "type", None)
+ if isinstance(type_, Geometry):
+ @event.listens_for(inst, "set", retval=True)
+ def set_value(state, value, oldvalue, initiator):
+ return _to_postgis(value)
- # override the __eq__() operator
- def __eq__(self, other):
- return self.__clause_element__().op('~=')(_to_postgis(other))
-
- # add a custom operator
- def intersects(self, other):
- return self.__clause_element__().op('&&')(_to_postgis(other))
-
- # any number of GIS operators can be overridden/added here
- # using the techniques above.
-
-
-def GISColumn(*args, **kw):
- """Define a declarative column property with GIS behavior.
-
- This just produces orm.column_property() with the appropriate
- extension and comparator_factory arguments. The given arguments
- are passed through to Column. The declarative module extracts
- the Column for inclusion in the mapped table.
-
- """
- return column_property(
- Column(*args, **kw),
- extension=GisAttribute(),
- comparator_factory=GisComparator
- )
# illustrate usage
if __name__ == '__main__':
from sqlalchemy import (create_engine, MetaData, Column, Integer, String,
func, literal, select)
- from sqlalchemy.orm import sessionmaker, column_property
+ from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('postgresql://scott:tiger@localhost/test', echo=True)
@@ -219,12 +217,8 @@ if __name__ == '__main__':
road_id = Column(Integer, primary_key=True)
road_name = Column(String)
- road_geom = GISColumn(Geometry(2))
+ road_geom = Column(Geometry(2))
- # enable the DDL extension, which allows CREATE/DROP operations
- # to work correctly. This is not needed if working with externally
- # defined tables.
- GISDDL(Road.__table__)
metadata.drop_all()
metadata.create_all()