diff -Nru geoalchemy2-0.4.2/CHANGES.txt geoalchemy2-0.5.0/CHANGES.txt --- geoalchemy2-0.4.2/CHANGES.txt 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/CHANGES.txt 2018-08-24 12:43:36.000000000 +0000 @@ -1,6 +1,16 @@ GeoAlchemy 2 Changelog ====================== +0.5.0 +----- + +* Add support for function ST_Azimuth @simlmx (#175) +* Remove Python 3.3 from the test matrix @elemoine (#179) +* Correct spelling mistakes @EdwardBetts @elemoine (#180) +* Make WKTElement and WKBElement pickable @elemoine (#182) +* Add SpatiaLite support @elemoine (#181) +* Fix to_shape with SpatiaLite @elemoine (#185) + 0.4.2 ----- @@ -10,7 +20,7 @@ ----- * Fix docstring for overlaps_or_above @dcere (#166) -* Add a WKTElement extented example @movermeyer (#164) +* Add a WKTElement extended example @movermeyer (#164) * Add checks to _GISType constructor @elemoine (#162) * Support geometry column with no typmod @elemoine (#161) * Add ST_AsTWKB function. @JacobHayes (#146) diff -Nru geoalchemy2-0.4.2/debian/changelog geoalchemy2-0.5.0/debian/changelog --- geoalchemy2-0.4.2/debian/changelog 2018-05-11 17:00:00.000000000 +0000 +++ geoalchemy2-0.5.0/debian/changelog 2018-11-08 17:49:56.000000000 +0000 @@ -1,3 +1,20 @@ +geoalchemy2 (0.5.0-1) unstable; urgency=medium + + * New upstream release + * debian/control: Update standards version, no changes + * debian/control: correct Maintainer and Uploaders + + -- Edward Betts Thu, 08 Nov 2018 17:49:56 +0000 + +geoalchemy2 (0.4.2-2) unstable; urgency=medium + + * Team upload. + * Change Build-Depends from postgresql-10-postgis-2.4 to + postgresql-11-postgis-2.5; change Test dependencies to unversioned + virtual packages. + + -- Christoph Berg Thu, 01 Nov 2018 22:29:17 +0100 + geoalchemy2 (0.4.2-1) unstable; urgency=low * Initial release. (Closes: #898490) diff -Nru geoalchemy2-0.4.2/debian/control geoalchemy2-0.5.0/debian/control --- geoalchemy2-0.4.2/debian/control 2018-05-11 17:00:00.000000000 +0000 +++ geoalchemy2-0.5.0/debian/control 2018-11-08 17:49:45.000000000 +0000 @@ -1,19 +1,20 @@ Source: geoalchemy2 -Maintainer: Edward Betts +Maintainer: Debian Python Modules Team +Uploaders: Edward Betts Section: python Priority: optional Build-Depends: debhelper (>= 11), dh-python, postgis, - postgresql-10-postgis-2.4, - postgresql-10-postgis-2.4-scripts, + postgresql-11-postgis-2.5 | postgresql-postgis, + postgresql-11-postgis-2.5-scripts | postgresql-postgis-scripts, python3-all, python3-psycopg2, python3-pytest, python3-setuptools, python3-shapely, python3-sqlalchemy -Standards-Version: 4.1.4 +Standards-Version: 4.2.1 Homepage: http://geoalchemy.org/ Vcs-Browser: https://salsa.debian.org/python-team/modules/geoalchemy2 Vcs-Git: https://salsa.debian.org/python-team/modules/geoalchemy2.git diff -Nru geoalchemy2-0.4.2/debian/tests/control geoalchemy2-0.5.0/debian/tests/control --- geoalchemy2-0.4.2/debian/tests/control 2018-05-11 17:00:00.000000000 +0000 +++ geoalchemy2-0.5.0/debian/tests/control 2018-11-08 17:47:45.000000000 +0000 @@ -1,2 +1,2 @@ Tests: setup-db-and-run-tests.py -Depends: @, postgis, postgresql-10-postgis-2.4, postgresql-10-postgis-2.4-scripts, python3-psycopg2, python3-pytest, python3-shapely, python3-sqlalchemy +Depends: @, postgis, postgresql-postgis, postgresql-postgis-scripts, python3-psycopg2, python3-pytest, python3-shapely, python3-sqlalchemy diff -Nru geoalchemy2-0.4.2/doc/conf.py geoalchemy2-0.5.0/doc/conf.py --- geoalchemy2-0.4.2/doc/conf.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/doc/conf.py 2018-08-24 12:43:36.000000000 +0000 @@ -49,9 +49,9 @@ # built documents. # # The short X.Y version. -version = '0.4.2' +version = '0.5.0' # The full version, including alpha/beta/rc tags. -release = '0.4.2' +release = '0.5.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff -Nru geoalchemy2-0.4.2/doc/index.rst geoalchemy2-0.5.0/doc/index.rst --- geoalchemy2-0.4.2/doc/index.rst 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/doc/index.rst 2018-08-24 12:43:36.000000000 +0000 @@ -9,12 +9,8 @@ GeoAlchemy 2 focuses on `PostGIS `_. PostGIS 1.5 and PostGIS 2 are supported. -.. note:: - - GeoAlchemy 2 doesn't currently support other dialects than - PostgreSQL/PostGIS. Supporting Oracle Locator in the previous series was - the main contributor to code complexity. So it is currently not clear - whether we want to go there again. +SpatiaLite is also supported, but using GeoAlchemy 2 with SpatiaLite requires some specific +configuration on the application side. GeoAlchemy 2 works with SpatiaLite 4.3.0 and higher. GeoAlchemy 2 aims to be simpler than its predecessor, `GeoAlchemy `_. Simpler to use, and simpler @@ -41,7 +37,6 @@ * GeoAlchemy 2 supports PostGIS' ``geometry`` type, as well as the ``geography`` and ``raster`` types. - * The first series had its own namespace for spatial functions. With GeoAlchemy 2, spatial functions are called like any other SQLAlchemy function, using ``func``, which is SQLAlchemy's `standard way @@ -78,6 +73,7 @@ orm_tutorial core_tutorial + spatialite_tutorial Reference Documentation ----------------------- diff -Nru geoalchemy2-0.4.2/doc/orm_tutorial.rst geoalchemy2-0.5.0/doc/orm_tutorial.rst --- geoalchemy2-0.4.2/doc/orm_tutorial.rst 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/doc/orm_tutorial.rst 2018-08-24 12:43:36.000000000 +0000 @@ -99,7 +99,7 @@ represents the shape of our lake. Since we have not yet told SQLAlchemy to persist the ``lake`` object, its ``id`` is ``None``. -The EWKT (Extented WKT) format is also supported. So, for example, if the +The EWKT (Extended WKT) format is also supported. So, for example, if the spatial reference system for the geometry column were ``4326``, the string ``SRID=4326;POLYGON((0 0,1 0,1,0 1,0 0))`` could be used as the geometry representation. diff -Nru geoalchemy2-0.4.2/doc/spatialite_tutorial.rst geoalchemy2-0.5.0/doc/spatialite_tutorial.rst --- geoalchemy2-0.4.2/doc/spatialite_tutorial.rst 1970-01-01 00:00:00.000000000 +0000 +++ geoalchemy2-0.5.0/doc/spatialite_tutorial.rst 2018-08-24 12:43:36.000000000 +0000 @@ -0,0 +1,227 @@ +.. _spatialite_tutorial: + +SpatiaLite Tutorial +=================== + +GeoAlchemy 2's main target is PostGIS. But GeoAlchemy 2 also supports SpatiaLite, the spatial +extension to SQLite. This tutorial describes how to use GeoAlchemy 2 with SpatiaLite. It's based on +the :ref:`orm_tutorial`, which you may want to read first. + +Connect to the DB +----------------- + +Just like when using PostGIS connecting to a SpatiaLite database requires an ``Engine``. This is how +you create one for SpatiaLite:: + + >>> from sqlalchemy import create_engine + >>> from sqlalchemy.event import listen + >>> + >>> def load_spatialite(dbapi_conn, connection_record): + ... dbapi_conn.enable_load_extension(True) + ... dbapi_conn.load_extension('/usr/lib/x86_64-linux-gnu/mod_spatialite.so') + ... + >>> + >>> engine = create_engine('sqlite:///gis.db', echo=True) + >>> listen(engine, 'connect', load_spatialite) + +The call to ``create_engine`` creates an engine bound to the database file ``gis.db``. After that +a ``connect`` listener is registered on the engine. The listener is responsible for loading the +SpatiaLite extension, which is a necessary operation for using SpatiaLite through SQL. + +At this point you can test that you are able to connect to the database:: + + >> conn = engine.connect() + 2018-05-30 17:12:02,675 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 + 2018-05-30 17:12:02,676 INFO sqlalchemy.engine.base.Engine () + 2018-05-30 17:12:02,676 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 + 2018-05-30 17:12:02,676 INFO sqlalchemy.engine.base.Engine () + +You can also check that the ``gis.db`` SQLite database file was created on the file system. + +One additional step is required for using SpatiaLite: create the ``geometry_columns`` and +``spatial_ref_sys`` metadata tables. This is done by calling SpatiaLite's ``InitSpatialMetaData`` +function:: + + >>> from sqlalchemy.sql import select, func + >>> + >>> conn.execute(select([func.InitSpatialMetaData()])) + +Note that this operation may take some time the first time it is executed for a database. When +``InitSpatialMetaData`` is executed again it will report an error:: + + InitSpatiaMetaData() error:"table spatial_ref_sys already exists" + +You can safely ignore that error. + +Before going further we can close the current connection:: + + >>> conn.close() + +Declare a Mapping +----------------- + +Now that we have a working connection we can go ahead and create a mapping between +a Python class and a database table. + +:: + + >>> from sqlalchemy.ext.declarative import declarative_base + >>> from sqlalchemy import Column, Integer, String + >>> from geoalchemy2 import Geometry + >>> + >>> Base = declarative_base() + >>> + >>> class Lake(Base): + ... __tablename__ = 'lake' + ... id = Column(Integer, primary_key=True) + ... name = Column(String) + ... geom = Column(Geometry(geometry_type='POLYGON', management=True, use_st_prefix=False)) + +This basically works in the way as with PostGIS. The difference is the ``management`` and +``use_st_prefix`` arguments that must be set to ``True`` and ``False``, respectively. + +Setting ``management`` to ``True`` indicates that the ``AddGeometryColumn`` and +``DiscardGeometryColumn`` management functions will be used for the creation and removal of the +geometry column. This is required with SpatiaLite. + +Setting ``use_st_prefix`` to ``False`` indicates that ``GeomFromEWKT`` and ``AsEWKB`` will be used +rather than ``ST_GeomFromEWKT`` and ``ST_AsEWKB``. Again this is required with SpatiaLite, as +SpatiaLite doesn't have ``ST_GeomFromEWKT`` and ``ST_AsEWKB`` functions. + +Create the Table in the Database +-------------------------------- + +We can now create the ``lake`` table in the ``gis.db`` database:: + + >>> Lake.__table__.create(engine) + +If we wanted to drop the table we'd use:: + + >>> Lake.__table__.drop(engine) + +There's nothing specific to SpatiaLite here. + +Create a Session +---------------- + +When using the SQLAlchemy ORM the ORM interacts with the database through a ``Session``. + + >>> from sqlalchemy.orm import sessionmaker + >>> Session = sessionmaker(bind=engine) + >>> session = Session() + +The session is associated with our SpatiaLite ``Engine``. Again, there's nothing +specific to SpatiaLite here. + +Add New Objects +--------------- + +We can now create and insert new ``Lake`` objects into the database, the same way we'd +do it using GeoAlchemy 2 with PostGIS. + +:: + + >>> lake = Lake(name='Majeur', geom='POLYGON((0 0,1 0,1 1,0 1,0 0))') + >>> session.add(lake) + >>> session.commit() + +We can now query the database for ``Majeur``:: + + >>> our_lake = session.query(Lake).filter_by(name='Majeur').first() + >>> our_lake.name + u'Majeur' + >>> our_lake.geom + + >>> our_lake.id + 1 + +Let's add more lakes:: + + >>> session.add_all([ + ... Lake(name='Garde', geom='POLYGON((1 0,3 0,3 2,1 2,1 0))'), + ... Lake(name='Orta', geom='POLYGON((3 0,6 0,6 3,3 3,3 0))') + ... ]) + >>> session.commit() + +Query +----- + +Let's make a simple, non-spatial, query:: + + >>> query = session.query(Lake).order_by(Lake.name) + >>> for lake in query: + ... print(lake.name) + ... + Garde + Majeur + Orta + +Now a spatial query:: + + >>> from geolachemy2 import WKTElement + >>> query = session.query(Lake).filter( + ... func.ST_Contains(Lake.geom, WKTElement('POINT(4 1)'))) + ... + >>> for lake in query: + ... print(lake.name) + ... + Orta + +Here's another spatial query, using ``ST_Intersects`` this time:: + + >>> query = session.query(Lake).filter( + ... Lake.geom.ST_Intersects(WKTElement('LINESTRING(2 1,4 1)'))) + ... + >>> for lake in query: + ... print(lake.name) + ... + Garde + Orta + +We can also apply relationship functions to :class:`geoalchemy2.elements.WKBElement`. For example:: + + >>> lake = session.query(Lake).filter_by(name='Garde').one() + >>> print(session.scalar(lake.geom.ST_Intersects(WKTElement('LINESTRING(2 1,4 1)')))) + 1 + +``session.scalar`` allows executing a clause and returning a scalar value (an integer value in this +case). + +The value ``1`` indicates that the lake "Garde" does intersects the ``LINESTRING(2 1,4 1)`` +geometry. See the SpatiaLite SQL functions reference list for more information. + +Caveats +------- + +You may encounter cases where queries will fail with the following error:: + + sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such function: ST_AsEWKB [SQL:... + +For example the following query will produce this error:: + + >>> buffers = session.query(Lake.geom.ST_Buffer(2)).all() + +The query fails because GeoAlchemy 2 sets :class:`geoalchemy2.types.Geometry` as the return type +of ``ST_Buffer``, but ``use_st_prefix`` defaults to ``True`` in the ``Geometry`` class. To work +around the issue it is required to pass a properly configured ``Geometry`` instance when calling +``ST_Buffer``:: + + >>> geometry_type = Geometry(management=True, use_st_prefix=False) + >>> buffers = session.query(Lake.geom.ST_Buffer(2, type_=geometry_type) + +This issue applies to all the functions that return geometries: ``ST_Buffer``, ``ST_Difference``, +``ST_Intersection``, etc. + +Here is another example where passing a ``type_`` is required:: + + >>> lake = session.query(Lake).filter_by(name='Garde').one() + >>> lake_buffer = session.scalar(lake.geom.ST_Buffer(2, type_=geometry_type) + +Further Reference +----------------- + +* GeoAlchemy 2 ORM Tutotial: :ref:`orm_tutorial` +* GeoAlchemy 2 Spatial Functions Reference: :ref:`spatial_functions` +* GeoAlchemy 2 Spatial Operators Reference: :ref:`spatial_operators` +* GeoAlchemy 2 Elements Reference: :ref:`elements` +* `SpatiaLite 4.3.0 SQL functions reference list `_ diff -Nru geoalchemy2-0.4.2/.flake8 geoalchemy2-0.5.0/.flake8 --- geoalchemy2-0.4.2/.flake8 1970-01-01 00:00:00.000000000 +0000 +++ geoalchemy2-0.5.0/.flake8 2018-08-24 12:43:36.000000000 +0000 @@ -0,0 +1,2 @@ +[flake8] +max-line-length=100 diff -Nru geoalchemy2-0.4.2/geoalchemy2/comparator.py geoalchemy2-0.5.0/geoalchemy2/comparator.py --- geoalchemy2-0.4.2/geoalchemy2/comparator.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/geoalchemy2/comparator.py 2018-08-24 12:43:36.000000000 +0000 @@ -91,7 +91,7 @@ # We create our own _FunctionGenerator here, and use it in place of # SQLAlchemy's "func" object. This is to be able to "bind" the - # function to the SQL expression. See also GenericFunction above. + # function to the SQL expression. See also GenericFunction. func_ = _FunctionGenerator(expr=self.expr) return getattr(func_, name) diff -Nru geoalchemy2-0.4.2/geoalchemy2/compat.py geoalchemy2-0.5.0/geoalchemy2/compat.py --- geoalchemy2-0.4.2/geoalchemy2/compat.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/geoalchemy2/compat.py 2018-08-24 12:43:36.000000000 +0000 @@ -15,9 +15,10 @@ PY3 = False buffer = getattr(builtins, 'buffer') bytes = str - + str = getattr(builtins, 'unicode') else: PY3 = True # Python 2.6 flake8 workaround buffer = getattr(builtins, 'memoryview') bytes = bytes + str = str diff -Nru geoalchemy2-0.4.2/geoalchemy2/elements.py geoalchemy2-0.5.0/geoalchemy2/elements.py --- geoalchemy2-0.4.2/geoalchemy2/elements.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/geoalchemy2/elements.py 2018-08-24 12:43:36.000000000 +0000 @@ -1,5 +1,5 @@ import binascii -from geoalchemy2.compat import PY3 +from geoalchemy2.compat import PY3, str as str_ try: from sqlalchemy.sql import functions @@ -29,14 +29,20 @@ ``extended`` A boolean indicating whether the extended format (EWKT or EWKB) - is used. Default is `False`. + is used. Default is ``False``. + + ``use_st_prefix`` + + A boolean indicating whether the ST_ versions of the GeomFromEWKT + and GeomFromEWKB functions are used. Default is ``True``. """ - def __init__(self, data, srid=-1, extended=False): + def __init__(self, data, srid=-1, extended=False, use_st_prefix=True): self.srid = srid self.data = data self.extended = extended + self.use_st_prefix = use_st_prefix def __str__(self): return self.desc @@ -60,6 +66,23 @@ func_ = functions._FunctionGenerator(expr=self) return getattr(func_, name) + def __getstate__(self): + state = { + 'data': str(self), + 'srid': self.srid, + 'extended': self.extended, + } + return state + + def __setstate__(self, state): + self.srid = state['srid'] + self.extended = state['extended'] + self.data = self._data_from_desc(state['data']) + + @staticmethod + def _data_from_desc(desc): + raise NotImplementedError() + class WKTElement(_SpatialElement, functions.Function): """ @@ -76,7 +99,8 @@ def __init__(self, *args, **kwargs): _SpatialElement.__init__(self, *args, **kwargs) if self.extended: - args = ("ST_GeomFromEWKT", self.data) + f = "ST_GeomFromEWKT" if self.use_st_prefix else "GeomFromEWKT" + args = (f, self.data) else: args = ("ST_GeomFromText", self.data, self.srid) functions.Function.__init__(self, *args) @@ -88,6 +112,10 @@ """ return self.data + @staticmethod + def _data_from_desc(desc): + return desc + class WKBElement(_SpatialElement, functions.Function): """ @@ -104,7 +132,8 @@ def __init__(self, *args, **kwargs): _SpatialElement.__init__(self, *args, **kwargs) if self.extended: - args = ("ST_GeomFromEWKB", self.data) + f = "ST_GeomFromEWKB" if self.use_st_prefix else "GeomFromEWKB" + args = (f, self.data) else: args = ("ST_GeomFromWKB", self.data, self.srid) functions.Function.__init__(self, *args) @@ -114,12 +143,20 @@ """ This element's description string. """ + if isinstance(self.data, str_): + return self.data desc = binascii.hexlify(self.data) if PY3: # hexlify returns a bytes object on py3 desc = str(desc, encoding="utf-8") return desc + @staticmethod + def _data_from_desc(desc): + if PY3: + desc = desc.encode(encoding="utf-8") + return binascii.unhexlify(desc) + class RasterElement(FunctionElement): """ @@ -167,7 +204,7 @@ # We create our own _FunctionGenerator here, and use it in place of # SQLAlchemy's "func" object. This is to be able to "bind" the - # function to the SQL expression. See also GenericFunction above. + # function to the SQL expression. See also GenericFunction. func_ = functions._FunctionGenerator(expr=self) return getattr(func_, name) diff -Nru geoalchemy2-0.4.2/geoalchemy2/functions.py geoalchemy2-0.5.0/geoalchemy2/functions.py --- geoalchemy2-0.4.2/geoalchemy2/functions.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/geoalchemy2/functions.py 2018-08-24 12:43:36.000000000 +0000 @@ -139,7 +139,7 @@ # ('ST_Transform', types.Geometry, - 'Returns a new geometry with its coordinates transformed to the SRID ' + 'Return a new geometry with its coordinates transformed to the SRID ' 'referenced by the integer parameter.'), # @@ -186,6 +186,11 @@ 'For ``geometry`` type area is in SRID units. For ``geography`` area is ' 'in square meters.'), + ('ST_Azimuth', None, + 'Returns the angle in radians from the horizontal of the ' + 'vector defined by pointA and pointB. Angle is computed clockwise from ' + 'down-to-up: on the clock: 12=0; 3=PI/2; 6=PI; 9=3PI/2.'), + ('ST_Centroid', types.Geometry, 'Returns the geometric center of a geometry.'), diff -Nru geoalchemy2-0.4.2/geoalchemy2/__init__.py geoalchemy2-0.5.0/geoalchemy2/__init__.py --- geoalchemy2-0.4.2/geoalchemy2/__init__.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/geoalchemy2/__init__.py 2018-08-24 12:43:36.000000000 +0000 @@ -10,6 +10,8 @@ RasterElement ) +from .exc import ArgumentError + from . import functions # NOQA from sqlalchemy import Table, event @@ -55,12 +57,19 @@ table.columns = column_collection if event == 'before-drop': - # Drop the managed Geometry columns with DropGeometryColumn() + # Drop the managed Geometry columns table_schema = table.schema or 'public' for c in gis_cols: - stmt = select([ - func.DropGeometryColumn( - table_schema, table.name, c.name)]) + if bind.dialect.name == 'sqlite': + drop_func = 'DiscardGeometryColumn' + elif bind.dialect.name == 'postgresql': + drop_func = 'DropGeometryColumn' + else: + raise ArgumentError('dialect {} is not supported'.format(bind.dialect.name)) + args = [table.schema] if table.schema else [] + args.extend([table.name, c.name]) + + stmt = select([getattr(func, drop_func)(*args)]) stmt = stmt.execution_options(autocommit=True) bind.execute(stmt) @@ -72,14 +81,14 @@ for c in table.c: # Add the managed Geometry columns with AddGeometryColumn() if isinstance(c.type, Geometry) and c.type.management is True: - args = [ - table_schema, + args = [table.schema] if table.schema else [] + args.extend([ table.name, c.name, c.type.srid, c.type.geometry_type, c.type.dimension - ] + ]) if c.type.use_typmod is not None: args.append(c.type.use_typmod) @@ -90,10 +99,17 @@ # Add spatial indices for the Geometry and Geography columns if isinstance(c.type, (Geometry, Geography)) and \ c.type.spatial_index is True: - bind.execute('CREATE INDEX "idx_%s_%s" ON "%s"."%s" ' - 'USING GIST ("%s")' % - (table.name, c.name, table_schema, - table.name, c.name)) + if bind.dialect.name == 'sqlite': + stmt = select([func.CreateSpatialIndex(table.name, c.name)]) + stmt = stmt.execution_options(autocommit=True) + bind.execute(stmt) + elif bind.dialect.name == 'postgresql': + bind.execute('CREATE INDEX "idx_%s_%s" ON "%s"."%s" ' + 'USING GIST ("%s")' % + (table.name, c.name, table_schema, + table.name, c.name)) + else: + raise ArgumentError('dialect {} is not supported'.format(bind.dialect.name)) # Add spatial indices for the Raster columns # diff -Nru geoalchemy2-0.4.2/geoalchemy2/shape.py geoalchemy2-0.5.0/geoalchemy2/shape.py --- geoalchemy2-0.4.2/geoalchemy2/shape.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/geoalchemy2/shape.py 2018-08-24 12:43:36.000000000 +0000 @@ -11,7 +11,7 @@ import shapely.wkt from .elements import WKBElement, WKTElement -from .compat import buffer, bytes +from .compat import buffer, bytes, str def to_shape(element): @@ -26,7 +26,9 @@ """ assert isinstance(element, (WKBElement, WKTElement)) if isinstance(element, WKBElement): - return shapely.wkb.loads(bytes(element.data)) + data, hex = (element.data, True) if isinstance(element.data, str) else \ + (bytes(element.data), False) + return shapely.wkb.loads(data, hex=hex) elif isinstance(element, WKTElement): return shapely.wkt.loads(element.data) diff -Nru geoalchemy2-0.4.2/geoalchemy2/types.py geoalchemy2-0.5.0/geoalchemy2/types.py --- geoalchemy2-0.4.2/geoalchemy2/types.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/geoalchemy2/types.py 2018-08-24 12:43:36.000000000 +0000 @@ -90,6 +90,13 @@ into account if ``management`` is set to ``True`` and is only available for PostGIS 2.x. + ``use_st_prefix`` + + Whether to use the ``ST_`` versions of the from_text and as_binary + functions. For example, for Geometry, ``GeomFromEWKT`` will be used + if ``use_st_prefix`` is ``False``, otherwise ``ST_GeomFromEWKT`` will + be used. Default is ``True``. + """ name = None @@ -109,7 +116,7 @@ geometry/geography columns. """ def __init__(self, geometry_type='GEOMETRY', srid=-1, dimension=2, - spatial_index=True, management=False, use_typmod=None): + spatial_index=True, management=False, use_typmod=None, use_st_prefix=True): geometry_type, srid = self.check_ctor_args( geometry_type, srid, management, use_typmod) self.geometry_type = geometry_type @@ -118,7 +125,11 @@ self.spatial_index = spatial_index self.management = management self.use_typmod = use_typmod - self.extended = self.as_binary == 'ST_AsEWKB' + self.extended = self.as_binary == 'AsEWKB' + self.use_st_prefix = use_st_prefix + if use_st_prefix: + self.from_text = 'ST_' + self.from_text + self.as_binary = 'ST_' + self.as_binary def get_col_spec(self): if not self.geometry_type: @@ -131,8 +142,8 @@ def result_processor(self, dialect, coltype): def process(value): if value is not None: - return WKBElement(value, srid=self.srid, - extended=self.extended) + return WKBElement(value, srid=self.srid, extended=self.extended, + use_st_prefix=self.use_st_prefix) return process def bind_expression(self, bindvalue): @@ -181,11 +192,11 @@ name = 'geometry' """ Type name used for defining geometry columns in ``CREATE TABLE``. """ - from_text = 'ST_GeomFromEWKT' + from_text = 'GeomFromEWKT' """ The "from text" geometry constructor. Used by the parent class' ``bind_expression`` method. """ - as_binary = 'ST_AsEWKB' + as_binary = 'AsEWKB' """ The "as binary" function to use. Used by the parent class' ``column_expression`` method. """ @@ -206,11 +217,11 @@ name = 'geography' """ Type name used for defining geography columns in ``CREATE TABLE``. """ - from_text = 'ST_GeogFromText' + from_text = 'GeogFromText' """ The ``FromText`` geography constructor. Used by the parent class' ``bind_expression`` method. """ - as_binary = 'ST_AsBinary' + as_binary = 'AsBinary' """ The "as binary" function to use. Used by the parent class' ``column_expression`` method. """ diff -Nru geoalchemy2-0.4.2/install-spatialite.sh geoalchemy2-0.5.0/install-spatialite.sh --- geoalchemy2-0.4.2/install-spatialite.sh 1970-01-01 00:00:00.000000000 +0000 +++ geoalchemy2-0.5.0/install-spatialite.sh 2018-08-24 12:43:36.000000000 +0000 @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +LIBSPATIALITE="libspatialite-4.3.0a" + +if [[ ! -d ${LIBSPATIALITE}/src/.libs ]]; then + + wget http://www.gaia-gis.it/gaia-sins/${LIBSPATIALITE}.tar.gz + tar xvzf ${LIBSPATIALITE}.tar.gz + + cd ${LIBSPATIALITE} + + ./configure --disable-freexl --disable-libxml2 + make -j2 +else + cd ${LIBSPATIALITE} +fi + +sudo make install diff -Nru geoalchemy2-0.4.2/RELEASE.rst geoalchemy2-0.5.0/RELEASE.rst --- geoalchemy2-0.4.2/RELEASE.rst 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/RELEASE.rst 2018-08-24 12:43:36.000000000 +0000 @@ -16,5 +16,5 @@ Go to http://readthedocs.org/dashboard/geoalchemy-2/edit/ and set "Default version" to x.y. -Note that there's no need to manually upload the pakage to PyPI. This is +Note that there's no need to manually upload the package to PyPI. This is done automatically by Travis when the release tag is pushed to GitHub. diff -Nru geoalchemy2-0.4.2/requirements.txt geoalchemy2-0.5.0/requirements.txt --- geoalchemy2-0.4.2/requirements.txt 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/requirements.txt 2018-08-24 12:43:36.000000000 +0000 @@ -4,7 +4,7 @@ # Additional requirements for running the testsuite and development pycodestyle==2.2.0 flake8==3.2.0 -pytest==3.1.3 +pytest==3.6.0 pytest-cov==2.5.1 Shapely>=1.3.0 diff -Nru geoalchemy2-0.4.2/setup.py geoalchemy2-0.5.0/setup.py --- geoalchemy2-0.4.2/setup.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/setup.py 2018-08-24 12:43:36.000000000 +0000 @@ -1,7 +1,7 @@ from setuptools import setup, find_packages -version = '0.4.2' +version = '0.5.0' setup( name='GeoAlchemy2', diff -Nru geoalchemy2-0.4.2/TEST.rst geoalchemy2-0.5.0/TEST.rst --- geoalchemy2-0.4.2/TEST.rst 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/TEST.rst 2018-08-24 12:43:36.000000000 +0000 @@ -13,13 +13,17 @@ $ sudo apt-get install python2.7-dev libpq-dev libgeos-dev +Install SpatiaLite:: + + $ sudo apt-get install libsqlite3-mod-spatialite + Install the Python dependencies:: $ pip install -r requirements.txt $ pip install psycopg2 -Set up the database -=================== +Set up the PostGIS database +=========================== Create the ``gis`` role:: @@ -41,7 +45,18 @@ For PostGIS 2:: - $sudo -u postgres psql -d gis -U postgres -c "CREATE EXTENSION postgis;" + $ sudo -u postgres psql -d gis -U postgres -c "CREATE EXTENSION postgis;" + +Set the path to the SpatiaLite module +===================================== + +By default the SpatiaLite functional tests are not run. To run them the ``SPATIALITE_LIBRARY_PATH`` +environment variable must be set. + +For example, on Debian Sid, and relying on the official SpatiaLite Debian package, the path to +the SpatiaLite library is ``/usr/lib/x86_64-linux-gnu/mod_spatialite.so``, so you would use this:: + + $ export SPATIALITE_LIBRARY_PATH="/usr/lib/x86_64-linux-gnu/mod_spatialite.so" Run Tests ========= diff -Nru geoalchemy2-0.4.2/tests/test_elements.py geoalchemy2-0.5.0/tests/test_elements.py --- geoalchemy2-0.4.2/tests/test_elements.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/tests/test_elements.py 2018-08-24 12:43:36.000000000 +0000 @@ -6,6 +6,7 @@ from geoalchemy2.elements import ( WKTElement, WKBElement, RasterElement, CompositeElement ) +from geoalchemy2.compat import str as str_ @pytest.fixture @@ -37,6 +38,15 @@ u'ST_GeomFromText_2': -1 } + def test_pickle_unpickle(self): + import pickle + e = WKTElement('POINT(1 2)', srid=3, extended=True) + pickled = pickle.dumps(e) + unpickled = pickle.loads(pickled) + assert unpickled.srid == 3 + assert unpickled.extended is True + assert unpickled.data == 'POINT(1 2)' + class TestExtendedWKTElement(): @@ -115,6 +125,10 @@ e = WKBElement(b'\x01\x02', extended=True) assert e.desc == '0102' + def test_desc_str(self): + e = WKBElement(str_('0102')) + assert e.desc == str_('0102') + def test_function_call(self): e = WKBElement(b'\x01\x02', extended=True) f = e.ST_Buffer(2) @@ -130,6 +144,15 @@ e = WKBElement(b'\x01\x02', extended=True) assert isinstance(str(e), str) + def test_pickle_unpickle(self): + import pickle + e = WKBElement(b'\x01\x02', srid=3, extended=True) + pickled = pickle.dumps(e) + unpickled = pickle.loads(pickled) + assert unpickled.srid == 3 + assert unpickled.extended is True + assert unpickled.data == b'\x01\x02' + class TestExtendedWKBElement(): diff -Nru geoalchemy2-0.4.2/tests/test_functional.py geoalchemy2-0.5.0/tests/test_functional.py --- geoalchemy2-0.4.2/tests/test_functional.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/tests/test_functional.py 2018-08-24 12:43:36.000000000 +0000 @@ -253,6 +253,38 @@ assert bottom_right is None +class TestPickle(): + + def setup(self): + metadata.drop_all(checkfirst=True) + metadata.create_all() + + def teardown(self): + session.rollback() + metadata.drop_all() + + def _create_one_lake(self): + l = Lake(WKTElement('LINESTRING(0 0,1 1)', srid=4326)) + session.add(l) + session.flush() + return l.id + + def test_pickle_unpickle(self): + import pickle + + lake_id = self._create_one_lake() + + lake = session.query(Lake).get(lake_id) + assert isinstance(lake.geom, WKBElement) + data_desc = str(lake.geom) + + pickled = pickle.dumps(lake) + unpickled = pickle.loads(pickled) + assert unpickled.geom.extended + assert unpickled.geom.srid == 4326 + assert str(unpickled.geom) == data_desc + + class TestCallFunction(): def setup(self): diff -Nru geoalchemy2-0.4.2/tests/test_functional_spatialite.py geoalchemy2-0.5.0/tests/test_functional_spatialite.py --- geoalchemy2-0.4.2/tests/test_functional_spatialite.py 1970-01-01 00:00:00.000000000 +0000 +++ geoalchemy2-0.5.0/tests/test_functional_spatialite.py 2018-08-24 12:43:36.000000000 +0000 @@ -0,0 +1,233 @@ +import os +import pytest +import platform + +from sqlalchemy import create_engine, MetaData, Column, Integer +from sqlalchemy.orm import sessionmaker +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.event import listen +from sqlalchemy.exc import IntegrityError +from sqlalchemy.sql import select, func + +from geoalchemy2 import Geometry +from geoalchemy2.elements import WKTElement, WKBElement +from geoalchemy2.shape import from_shape, to_shape +from geoalchemy2.compat import str as str_ + +from shapely.geometry import LineString + + +if 'SPATIALITE_LIBRARY_PATH' not in os.environ: + pytest.skip('SPATIALITE_LIBRARY_PATH is not defined, skip SpatiaLite tests', + allow_module_level=True) + +if platform.python_implementation().lower() == 'pypy': + pytest.skip('skip SpatiaLite tests on PyPy', allow_module_level=True) + + +def load_spatialite(dbapi_conn, connection_record): + dbapi_conn.enable_load_extension(True) + dbapi_conn.load_extension(os.environ['SPATIALITE_LIBRARY_PATH']) + dbapi_conn.enable_load_extension(False) + + +engine = create_engine('sqlite:///spatialdb', echo=True) +listen(engine, 'connect', load_spatialite) + +metadata = MetaData(engine) +Base = declarative_base(metadata=metadata) + +# geometry type to use when calling geometry-returning functions +geometry_type = Geometry(use_st_prefix=False) + + +class Lake(Base): + __tablename__ = 'lake' + id = Column(Integer, primary_key=True) + geom = Column(Geometry(geometry_type='LINESTRING', srid=4326, + management=True, use_st_prefix=False)) + + def __init__(self, geom): + self.geom = geom + + +session = sessionmaker(bind=engine)() + +session.execute('SELECT InitSpatialMetaData()') + + +class TestInsertionCore(): + + def setup(self): + metadata.drop_all(checkfirst=True) + metadata.create_all() + self.conn = engine.connect() + + def teardown(self): + self.conn.close() + metadata.drop_all() + + def test_insert(self): + conn = self.conn + + # Issue two inserts using DBAPI's executemany() method. This tests + # the Geometry type's bind_processor and bind_expression functions. + conn.execute(Lake.__table__.insert(), [ + {'geom': 'SRID=4326;LINESTRING(0 0,1 1)'}, + {'geom': WKTElement('LINESTRING(0 0,2 2)', srid=4326, use_st_prefix=False)} + + # Having WKBElement objects as bind values is not supported, so + # the following does not work: + # {'geom': from_shape(LineString([[0, 0], [3, 3]], srid=4326)} + ]) + + results = conn.execute(Lake.__table__.select()) + rows = results.fetchall() + + row = rows[0] + assert isinstance(row[1], WKBElement) + wkt = session.execute(row[1].ST_AsText()).scalar() + assert wkt == 'LINESTRING(0 0, 1 1)' + srid = session.execute(row[1].ST_SRID()).scalar() + assert srid == 4326 + + row = rows[1] + assert isinstance(row[1], WKBElement) + wkt = session.execute(row[1].ST_AsText()).scalar() + assert wkt == 'LINESTRING(0 0, 2 2)' + srid = session.execute(row[1].ST_SRID()).scalar() + assert srid == 4326 + + +class TestInsertionORM(): + + def setup(self): + metadata.drop_all(checkfirst=True) + metadata.create_all() + + def teardown(self): + session.rollback() + metadata.drop_all() + + def test_WKT(self): + l = Lake('LINESTRING(0 0,1 1)') + session.add(l) + + with pytest.raises(IntegrityError): + session.flush() + + def test_WKTElement(self): + l = Lake(WKTElement('LINESTRING(0 0,1 1)', srid=4326)) + session.add(l) + session.flush() + session.expire(l) + assert isinstance(l.geom, WKBElement) + assert str(l.geom) == '0102000020E6100000020000000000000000000000000000000000000000000000' \ + '0000F03F000000000000F03F' + wkt = session.execute(l.geom.ST_AsText()).scalar() + assert wkt == 'LINESTRING(0 0, 1 1)' + srid = session.execute(l.geom.ST_SRID()).scalar() + assert srid == 4326 + + def test_WKBElement(self): + shape = LineString([[0, 0], [1, 1]]) + l = Lake(from_shape(shape, srid=4326)) + session.add(l) + session.flush() + session.expire(l) + assert isinstance(l.geom, WKBElement) + assert str(l.geom) == '0102000020E6100000020000000000000000000000000000000000000000000000' \ + '0000F03F000000000000F03F' + wkt = session.execute(l.geom.ST_AsText()).scalar() + assert wkt == 'LINESTRING(0 0, 1 1)' + srid = session.execute(l.geom.ST_SRID()).scalar() + assert srid == 4326 + + +class TestShapely(): + + def setup(self): + metadata.drop_all(checkfirst=True) + metadata.create_all() + + def teardown(self): + session.rollback() + metadata.drop_all() + + def test_to_shape(self): + l = Lake(WKTElement('LINESTRING(0 0,1 1)', srid=4326)) + session.add(l) + session.flush() + session.expire(l) + l = session.query(Lake).one() + assert isinstance(l.geom, WKBElement) + assert isinstance(l.geom.data, str_) + assert l.geom.srid == 4326 + s = to_shape(l.geom) + assert isinstance(s, LineString) + assert s.wkt == 'LINESTRING (0 0, 1 1)' + l = Lake(l.geom) + session.add(l) + session.flush() + session.expire(l) + assert isinstance(l.geom, WKBElement) + assert isinstance(l.geom.data, str_) + assert l.geom.srid == 4326 + + +class TestCallFunction(): + + def setup(self): + metadata.drop_all(checkfirst=True) + metadata.create_all() + + def teardown(self): + session.rollback() + metadata.drop_all() + + def _create_one_lake(self): + l = Lake(WKTElement('LINESTRING(0 0,1 1)', srid=4326)) + session.add(l) + session.flush() + return l.id + + def test_ST_GeometryType(self): + lake_id = self._create_one_lake() + + s = select([func.ST_GeometryType(Lake.__table__.c.geom)]) + r1 = session.execute(s).scalar() + assert r1 == 'LINESTRING' + + lake = session.query(Lake).get(lake_id) + r2 = session.execute(lake.geom.ST_GeometryType()).scalar() + assert r2 == 'LINESTRING' + + r3 = session.query(Lake.geom.ST_GeometryType()).scalar() + assert r3 == 'LINESTRING' + + r4 = session.query(Lake).filter( + Lake.geom.ST_GeometryType() == 'LINESTRING').one() + assert isinstance(r4, Lake) + assert r4.id == lake_id + + def test_ST_Buffer(self): + lake_id = self._create_one_lake() + + s = select([func.ST_Buffer(Lake.__table__.c.geom, 2, type_=geometry_type)]) + r1 = session.execute(s).scalar() + assert isinstance(r1, WKBElement) + + lake = session.query(Lake).get(lake_id) + r2 = session.execute(lake.geom.ST_Buffer(2, type_=geometry_type)).scalar() + assert isinstance(r2, WKBElement) + + r3 = session.query(Lake.geom.ST_Buffer(2, type_=geometry_type)).scalar() + assert isinstance(r3, WKBElement) + + assert r1.data == r2.data == r3.data + + r4 = session.query(Lake).filter( + func.ST_Within(WKTElement('POINT(0 0)', srid=4326), + Lake.geom.ST_Buffer(2, type_=geometry_type))).one() + assert isinstance(r4, Lake) + assert r4.id == lake_id diff -Nru geoalchemy2-0.4.2/tests/test_functions.py geoalchemy2-0.5.0/tests/test_functions.py --- geoalchemy2-0.4.2/tests/test_functions.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/tests/test_functions.py 2018-08-24 12:43:36.000000000 +0000 @@ -99,6 +99,10 @@ _test_simple_func('ST_Area') +def test_ST_Azimuth(): + _test_simple_func('ST_Azimuth') + + def test_ST_Centroid(): _test_geometry_returning_func('ST_Centroid') diff -Nru geoalchemy2-0.4.2/tests/test_shape.py geoalchemy2-0.5.0/tests/test_shape.py --- geoalchemy2-0.4.2/tests/test_shape.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/tests/test_shape.py 2018-08-24 12:43:36.000000000 +0000 @@ -1,4 +1,4 @@ -from geoalchemy2.compat import buffer, bytes +from geoalchemy2.compat import buffer, bytes, str from geoalchemy2.elements import WKBElement, WKTElement from geoalchemy2.shape import from_shape, to_shape @@ -13,6 +13,15 @@ s = to_shape(e) assert isinstance(s, Point) assert s.x == 1 + assert s.y == 2 + + +def test_to_shape_WKBElement_str(): + # POINT(1 2) + e = WKBElement(str('0101000000000000000000f03f0000000000000040')) + s = to_shape(e) + assert isinstance(s, Point) + assert s.x == 1 assert s.y == 2 diff -Nru geoalchemy2-0.4.2/tests/test_types.py geoalchemy2-0.5.0/tests/test_types.py --- geoalchemy2-0.4.2/tests/test_types.py 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/tests/test_types.py 2018-08-24 12:43:36.000000000 +0000 @@ -19,6 +19,12 @@ @pytest.fixture +def geometry_table_no_st_prefix(): + table = Table('table', MetaData(), Column('geom', Geometry(use_st_prefix=False))) + return table + + +@pytest.fixture def geography_table(): table = Table('table', MetaData(), Column('geom', Geography)) return table @@ -60,25 +66,46 @@ s = select([geometry_table.c.geom]) eq_sql(s, 'SELECT ST_AsEWKB("table".geom) AS geom FROM "table"') + def test_column_expression_no_st_prefix(self, geometry_table_no_st_prefix): + s = select([geometry_table_no_st_prefix.c.geom]) + eq_sql(s, 'SELECT AsEWKB("table".geom) AS geom FROM "table"') + def test_select_bind_expression(self, geometry_table): s = select([text('foo')]).where(geometry_table.c.geom == 'POINT(1 2)') eq_sql(s, 'SELECT foo FROM "table" WHERE ' '"table".geom = ST_GeomFromEWKT(:geom_1)') assert s.compile().params == {'geom_1': 'POINT(1 2)'} + def test_select_bind_expression_no_st_prefix(self, geometry_table_no_st_prefix): + s = select([text('foo')]).where(geometry_table_no_st_prefix.c.geom == 'POINT(1 2)') + eq_sql(s, 'SELECT foo FROM "table" WHERE ' + '"table".geom = GeomFromEWKT(:geom_1)') + assert s.compile().params == {'geom_1': 'POINT(1 2)'} + def test_insert_bind_expression(self, geometry_table): i = insert(geometry_table).values(geom='POINT(1 2)') eq_sql(i, 'INSERT INTO "table" (geom) VALUES (ST_GeomFromEWKT(:geom))') assert i.compile().params == {'geom': 'POINT(1 2)'} + def test_insert_bind_expression_no_st_prefix(self, geometry_table_no_st_prefix): + i = insert(geometry_table_no_st_prefix).values(geom='POINT(1 2)') + eq_sql(i, 'INSERT INTO "table" (geom) VALUES (GeomFromEWKT(:geom))') + assert i.compile().params == {'geom': 'POINT(1 2)'} + def test_function_call(self, geometry_table): s = select([geometry_table.c.geom.ST_Buffer(2)]) eq_sql(s, 'SELECT ST_AsEWKB(ST_Buffer("table".geom, :ST_Buffer_2)) ' 'AS "ST_Buffer_1" FROM "table"') - def test_non_ST_function_call(self, geometry_table): + def test_function_call_no_st_prefix(self, geometry_table): + type_ = Geometry(use_st_prefix=False) + s = select([geometry_table.c.geom.ST_Buffer(2, type_=type_)]) + eq_sql(s, + 'SELECT AsEWKB(ST_Buffer("table".geom, :ST_Buffer_2)) ' + 'AS "ST_Buffer_1" FROM "table"') + def test_non_ST_function_call(self, geometry_table): with pytest.raises(AttributeError): geometry_table.c.geom.Buffer(2) diff -Nru geoalchemy2-0.4.2/tox.ini geoalchemy2-0.5.0/tox.ini --- geoalchemy2-0.4.2/tox.ini 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/tox.ini 2018-08-24 12:43:36.000000000 +0000 @@ -1,7 +1,8 @@ [tox] -envlist=py27sqla11, py33sqla11, py34sqla11, py35sqla11, pypysqla11 +envlist=py27sqla11, py34sqla11, py35sqla11, pypysqla11 [testenv] +passenv=SPATIALITE_LIBRARY_PATH commands= py.test \ {posargs} @@ -11,13 +12,7 @@ deps= SQLAlchemy==1.1.2 psycopg2 - -rrequirements.txt - -[testenv:py33sqla11] -basepython=python3.3 -deps= - SQLAlchemy==1.1.2 - psycopg2 + pysqlite -rrequirements.txt [testenv:py34sqla11] diff -Nru geoalchemy2-0.4.2/.travis.yml geoalchemy2-0.5.0/.travis.yml --- geoalchemy2-0.4.2/.travis.yml 2018-01-10 12:42:08.000000000 +0000 +++ geoalchemy2-0.5.0/.travis.yml 2018-08-24 12:43:36.000000000 +0000 @@ -5,6 +5,17 @@ apt: packages: - postgresql-9.3-postgis-2.3 + - python2.7-dev + - python3-dev + - autotools-dev + - libexpat1-dev + - libfreexl-dev + - libgeos-dev + - libproj-dev + - libreadline-dev + - libsqlite3-dev + - libxml2-dev + - zlib1g-dev notifications: email: @@ -13,14 +24,15 @@ services: - postgresql +cache: + directories: + - libspatialite-4.3.0a + matrix: include: - python: 2.7 env: - TOX_ENV=py27sqla11 - - python: 3.3 - env: - - TOX_ENV=py33sqla11 - python: 3.4 env: - TOX_ENV=py34sqla11 @@ -31,7 +43,14 @@ env: - TOX_ENV=pypysqla11 +env: + global: + - SPATIALITE_LIBRARY_PATH="/usr/local/lib/mod_spatialite.so" + install: + # Install Spatialite + - ./install-spatialite.sh + # Install tox - pip install tox