Skip to content
This repository was archived by the owner on May 21, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
dist: xenial
language: python

python:
- "2.7"
- "3.7"

services:
- postgres
Expand All @@ -11,18 +13,18 @@ env:
- DB=generic
- DB=postgres POSTGIS_VERSION=1.5
#- DB=postgres POSTGIS_VERSION=2
#- DB=mysql
- DB=mysql

install:
# install GeoAlchemy in editable mode
- pip install -e .

# install nose and rednose for colorful output
- pip install nose rednose
# install pytest and nose
- pip install pytest nose

# install individual dependencies
- if [[ "$DB" == "postgres" ]]; then pip install psycopg2; fi
- if [[ "$DB" == "mysql" ]]; then pip install MySQL-python; fi
- if [[ "$DB" == "mysql" ]]; then pip install mysqlclient; fi

# install PostGIS 2.x
- if [[ "$DB" == "postgres" ]] && [[ "$POSTGIS_VERSION" != "1.5" ]]; then sudo apt-add-repository -y ppa:sharpie/for-science; fi
Expand All @@ -33,10 +35,6 @@ install:
- if [[ "$DB" == "postgres" ]]; then sudo apt-get install postgresql-9.1-postgis -q; fi

before_script:
# activate verbose and rednose mode
- export NOSE_REDNOSE=1
- export NOSE_VERBOSE=2

# create database user "gis"
- if [[ "$DB" == "postgres" ]]; then psql -U postgres -c "CREATE ROLE gis PASSWORD 'gis' SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN;"; fi
- if [[ "$DB" == "mysql" ]]; then mysql --user=root -e "CREATE USER 'gis'@'localhost' IDENTIFIED BY 'gis';"; fi
Expand All @@ -53,11 +51,11 @@ before_script:

script:
# run tests
- if [[ "$DB" == "generic" ]]; then python geoalchemy/tests/test_dialect.py; fi
- if [[ "$DB" == "generic" ]]; then python geoalchemy/tests/test_type.py; fi
- if [[ "$DB" == "generic" ]]; then pytest geoalchemy/tests/test_dialect.py; fi
- if [[ "$DB" == "generic" ]]; then pytest geoalchemy/tests/test_type.py; fi

- if [[ "$DB" == "postgres" ]]; then python geoalchemy/tests/test_postgis.py; fi
- if [[ "$DB" == "mysql" ]]; then python geoalchemy/tests/test_mysql.py; fi
- if [[ "$DB" == "postgres" ]]; then pytest geoalchemy/tests/test_postgis.py; fi
- if [[ "$DB" == "mysql" ]]; then pytest --tb=short geoalchemy/tests/test_mysql.py; fi

notifications:
email:
Expand Down
12 changes: 6 additions & 6 deletions doc/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Testing GeoAlchemy
Requirements
------------

In order to test GeoAlchemy you must have nose tools installed. Also you should
In order to test GeoAlchemy you must have pytest installed. Also you should
have installed SQLAlchemy, GeoAlchemy and all the DB-APIs. Refer the
`installation docs`_ for details. In case you are interested in
testing against only one DB-API (say Postgres), you need not have the other
Expand All @@ -28,19 +28,19 @@ would use an in-memory database.
Running the Tests
-----------------

The tests can be run using nosetests as:
The tests can be run using pytest as:

.. code-block:: bash

$ nosetests
$ pytest

For running the tests against a single database at a time:

.. code-block:: bash

$ nosetests geoalchemy/tests/test_postgis.py
$ nosetests geoalchemy/tests/test_mysql.py
$ nosetests geoalchemy/tests/test_spatialite.py
$ pytest geoalchemy/tests/test_postgis.py
$ pytest geoalchemy/tests/test_mysql.py
$ pytest geoalchemy/tests/test_spatialite.py

.. _`installation docs`: install.html
.. _`Spatialite specific notes`: tutorial.html#notes-for-spatialite
4 changes: 2 additions & 2 deletions examples/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from geoalchemy import (Geometry, Point, LineString, Polygon,
GeometryColumn, GeometryDDL, WKTSpatialElement)

engine = create_engine('mysql://gis:gis@localhost/gis', echo=True)
engine = create_engine('mysql+mysqldb://gis:gis@localhost/gis', echo=True)
metadata = MetaData(engine)
session = sessionmaker(bind=engine)()
Base = declarative_base(metadata=metadata)
Expand Down Expand Up @@ -36,7 +36,7 @@ class Spot(Base):

# enable the DDL extension, which allows CREATE/DROP operations
# to work correctly. This is not needed if working with externally
# defined tables.
# defined tables.
GeometryDDL(Road.__table__)
GeometryDDL(Lake.__table__)
GeometryDDL(Spot.__table__)
Expand Down
113 changes: 57 additions & 56 deletions geoalchemy/base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import six
from sqlalchemy.orm.properties import ColumnProperty
from sqlalchemy.sql import expression, not_
from sqlalchemy.sql.expression import ColumnClause, literal
Expand All @@ -8,8 +9,8 @@
from sqlalchemy.types import UserDefinedType
from sqlalchemy.ext.compiler import compiles

from utils import from_wkt
from functions import functions, _get_function, BaseFunction
from .utils import from_wkt
from .functions import functions, _get_function, BaseFunction

# Base classes for geoalchemy

Expand All @@ -23,23 +24,23 @@ def __str__(self):

def __repr__(self):
return "<%s at 0x%x; %r>" % (self.__class__.__name__, id(self), self.desc)

def __getattr__(self, name):
return getattr(functions, name)(self)
#
def __get_wkt(self, session):
"""This method converts the object into a WKT geometry. It takes into
account that WKTSpatialElement does not have to make a new query
to retrieve the WKT geometry.

"""
if isinstance(self, WKTSpatialElement):
# for WKTSpatialElement we don't need to make a new query
return self.desc
return self.desc
elif isinstance(self.desc, WKTSpatialElement):
return self.desc.desc
else:
return session.scalar(self.wkt)
return session.scalar(self.wkt)

def geom_type(self, session):
wkt = self.__get_wkt(session)
Expand All @@ -52,19 +53,19 @@ def coords(self, session):
class WKTSpatialElement(SpatialElement, Function):
"""Represents a Geometry value expressed within application code; i.e. in
the OGC Well Known Text (WKT) format.
Extends expression.Function so that in a SQL expression context the value
is interpreted as 'GeomFromText(value)' or as the equivalent function in the

Extends expression.Function so that in a SQL expression context the value
is interpreted as 'GeomFromText(value)' or as the equivalent function in the
currently used database.

"""

def __init__(self, desc, srid=4326, geometry_type='GEOMETRY'):
assert isinstance(desc, basestring)
assert isinstance(desc, six.string_types)
self.desc = desc
self.srid = srid
self.geometry_type = geometry_type

Function.__init__(self, "")

@property
Expand All @@ -75,75 +76,75 @@ def geom_wkt(self):
@compiles(WKTSpatialElement)
def __compile_wktspatialelement(element, compiler, **kw):
function = _get_function(element, compiler, (element.desc, element.srid), kw.get('within_columns_clause', False))

return compiler.process(function)

class WKBSpatialElement(SpatialElement, Function):
"""Represents a Geometry value as expressed in the OGC Well
Known Binary (WKB) format.
Extends expression.Function so that in a SQL expression context the value
is interpreted as 'GeomFromWKB(value)' or as the equivalent function in the

Extends expression.Function so that in a SQL expression context the value
is interpreted as 'GeomFromWKB(value)' or as the equivalent function in the
currently used database .

"""

def __init__(self, desc, srid=4326, geometry_type='GEOMETRY'):
assert isinstance(desc, (basestring, buffer))
assert isinstance(desc, (six.string_types, buffer))
self.desc = desc
self.srid = srid
self.geometry_type = geometry_type

Function.__init__(self, "")

@compiles(WKBSpatialElement)
def __compile_wkbspatialelement(element, compiler, **kw):
from geoalchemy.dialect import DialectManager
from geoalchemy.dialect import DialectManager
database_dialect = DialectManager.get_spatial_dialect(compiler.dialect)
function = _get_function(element, compiler, (database_dialect.bind_wkb_value(element),

function = _get_function(element, compiler, (database_dialect.bind_wkb_value(element),
element.srid),
kw.get('within_columns_clause', False))

return compiler.process(function)


class DBSpatialElement(SpatialElement, Function):
"""This class can be used to wrap a geometry returned by a
"""This class can be used to wrap a geometry returned by a
spatial database operation.
For example::

For example::

element = DBSpatialElement(session.scalar(r.geom.buffer(10.0)))
session.scalar(element.wkt)

"""

def __init__(self, desc):
self.desc = desc
Function.__init__(self, "", desc)

@compiles(DBSpatialElement)
def __compile_dbspatialelement(element, compiler, **kw):
function = _get_function(element, compiler, [literal(element.desc)],
function = _get_function(element, compiler, [literal(element.desc)],
kw.get('within_columns_clause', False))

return compiler.process(function)

class PersistentSpatialElement(SpatialElement):
"""Represents a Geometry value loaded from the database."""

def __init__(self, desc):
self.desc = desc
@property

@property
def geom_wkb(self):
if self.desc is not None and isinstance(self.desc, WKBSpatialElement):
return self.desc.desc
else:
return None
@property

@property
def geom_wkt(self):
if self.desc is not None and isinstance(self.desc, WKTSpatialElement):
return self.desc.desc
Expand All @@ -167,7 +168,7 @@ def __init__(self, dimension=2, srid=4326, spatial_index=True,

def get_col_spec(self):
return self.name

def bind_processor(self, dialect):
def process(value):
if value is not None:
Expand All @@ -180,7 +181,7 @@ def process(value):
else:
return value
return process

def result_processor(self, dialect, coltype=None):
def process(value):
if value is not None:
Expand All @@ -205,15 +206,15 @@ def _to_gis(value, srid_db):
if isinstance(value.desc, (WKBSpatialElement, WKTSpatialElement)):
return _check_srid(value.desc, srid_db)
return _check_srid(value, srid_db)
elif isinstance(value, basestring):
elif isinstance(value, six.string_types):
return _check_srid(WKTSpatialElement(value), srid_db)
elif isinstance(value, expression.ClauseElement):
return value
elif value is None:
return None
else:
raise Exception("Invalid type")

def _check_srid(spatial_element, srid_db):
"""Check if the SRID of the spatial element which we are about to insert
into the database equals the SRID used for the geometry column.
Expand All @@ -222,7 +223,7 @@ def _check_srid(spatial_element, srid_db):
if srid_db is None or not hasattr(spatial_element, 'srid') or \
isinstance(spatial_element.srid, BaseFunction):
return spatial_element

if spatial_element.srid == srid_db:
return spatial_element
else:
Expand All @@ -235,42 +236,42 @@ class RawColumn(ColumnClause):
def __init__(self, column):
self.column = column
ColumnClause.__init__(self, column.name, column.table)

def _make_proxy(self, selectable, name=None):
return self.column._make_proxy(selectable, name)

@compiles(RawColumn)
def __compile_rawcolumn(rawcolumn, compiler, **kw):
return compiler.visit_column(rawcolumn.column)

class SpatialComparator(ColumnProperty.Comparator):
"""Intercepts standard Column operators on mapped class attributes
and overrides their behavior.
A comparator class makes sure that queries like

A comparator class makes sure that queries like
"session.query(Lake).filter(Lake.lake_geom.gcontains(..)).all()" can be executed.
"""

@property
def RAW(self):
"""For queries like 'select extent(spots.spot_location) from spots' the
"""For queries like 'select extent(spots.spot_location) from spots' the
geometry column should not be surrounded by 'AsBinary(..)'. If 'RAW' is
called on a geometry column, this column wont't be converted to WKB::
session.query(func.extent(Spot.spot_location.RAW)).first()

session.query(func.extent(Spot.spot_location.RAW)).first()

"""
return RawColumn(self.__clause_element__())

def __getattr__(self, name):
return getattr(functions, name)(self)

# override the __eq__() operator (allows to use '==' on geometries)
def __eq__(self, other):
def __eq__(self, other):
if other is None:
return self.op("IS")(None)
return functions.equals(self, other)

def __ne__(self, other):
if other is None:
return self.op("IS NOT")(None)
Expand Down
Loading