From d42fb7b0324e00fae5566bc85043e5db0bf7c8a3 Mon Sep 17 00:00:00 2001 From: Athanasios Anastasiou Date: Tue, 20 Nov 2018 17:28:33 +0000 Subject: [PATCH] Moved spatial_properties to contrib, turned import warning to ImportError when shapely is not installed, relocated the tests, added neo4j 3.4.0 to travis to test spatial properties properly. --- .travis.yml | 1 + neomodel/__init__.py | 9 --- neomodel/{ => contrib}/spatial_properties.py | 10 ++- test/conftest.py | 1 + .../test_spatial_datatypes.py | 71 ++++++++++--------- .../test_spatial_properties.py | 43 ++++++----- 6 files changed, 72 insertions(+), 63 deletions(-) rename neomodel/{ => contrib}/spatial_properties.py (97%) rename test/{ => test_contrib}/test_spatial_datatypes.py (65%) rename test/{ => test_contrib}/test_spatial_properties.py (66%) diff --git a/.travis.yml b/.travis.yml index af85e430..d16261ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ env: - NEO4J_VERSION="3.1.7" - NEO4J_VERSION="3.2.9" - NEO4J_VERSION="3.3.3" + - NEO4J_VERSION="3.4.0" install: - sudo apt-get update && sudo apt-get install oracle-java8-installer - curl -L http://dist.neo4j.org/neo4j-community-$NEO4J_VERSION-unix.tar.gz | tar xz diff --git a/neomodel/__init__.py b/neomodel/__init__.py index 4e83bd75..9c89d628 100644 --- a/neomodel/__init__.py +++ b/neomodel/__init__.py @@ -16,15 +16,6 @@ NormalizedProperty, RegexProperty, EmailProperty, JSONProperty, ArrayProperty, UniqueIdProperty) - -# If shapely is not installed, its import will fail and the spatial properties will not be available -try: - from .spatial_properties import (NeomodelPoint,PointProperty) -except ImportError: - sys.stderr.write('WARNING: Shapely not found on system, spatial capabilities will not be available.\n' - 'If required, you can install Shapely via `pip install shapely`.') - - __author__ = 'Robin Edwards' __email__ = 'robin.ge@gmail.com' __license__ = 'MIT' diff --git a/neomodel/spatial_properties.py b/neomodel/contrib/spatial_properties.py similarity index 97% rename from neomodel/spatial_properties.py rename to neomodel/contrib/spatial_properties.py index dc42e14a..fda40eed 100644 --- a/neomodel/spatial_properties.py +++ b/neomodel/contrib/spatial_properties.py @@ -22,9 +22,15 @@ __author__ = "Athanasios Anastasiou" import neo4j.v1 -from shapely.geometry import Point as ShapelyPoint -from neomodel.properties import Property, validator +# If shapely is not installed, its import will fail and the spatial properties will not be available +try: + from shapely.geometry import Point as ShapelyPoint +except ImportError: + raise ImportError('NEOMODEL ERROR: Shapely not found. If required, you can install Shapely via ' + '`pip install shapely`.') + +from neomodel.properties import Property, validator # Note: Depending on how Neo4J decides to handle the resolution of geographical points, these two # private attributes might have to be updated in the future or removed altogether. diff --git a/test/conftest.py b/test/conftest.py index dca0c2fb..e8b5b4a7 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -16,6 +16,7 @@ def pytest_addoption(parser): """ parser.addoption("--resetdb", action="store_true", help = "Ensures that the database is clear prior to running tests for neomodel", default=False) + def pytest_sessionstart(session): """ Provides initial connection to the database and sets up the rest of the test suite diff --git a/test/test_spatial_datatypes.py b/test/test_contrib/test_spatial_datatypes.py similarity index 65% rename from test/test_spatial_datatypes.py rename to test/test_contrib/test_spatial_datatypes.py index 914763c5..5e52397f 100644 --- a/test/test_spatial_datatypes.py +++ b/test/test_contrib/test_spatial_datatypes.py @@ -7,6 +7,7 @@ """ import neomodel +import neomodel.contrib.spatial_properties import shapely import pytest @@ -30,7 +31,8 @@ def basic_type_assertions(ground_truth, tested_object, test_description, check_n assert tested_object.srid == ground_truth.srid, \ '{} does not have the expected SRID({})'.format(test_description, ground_truth.srid) assert len(tested_object) == len(ground_truth), \ - '{} dimensionality mismatch. Expected {}, had {}'.format(len(ground_truth.coords), len(tested_object.coords)) + '{} dimensionality mismatch. Expected {}, had {}'.format(len(ground_truth.coords), + len(tested_object.coords)) else: assert isinstance(tested_object, type(ground_truth)), '{} did not return NeomodelPoint'.format(test_description) assert tested_object.crs == ground_truth.crs, \ @@ -48,41 +50,41 @@ def test_coord_constructor(): """ # Implicit cartesian point with coords - ground_truth_object = neomodel.NeomodelPoint((0.0, 0.0)) - new_point = neomodel.NeomodelPoint((0.0, 0.0)) + ground_truth_object = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0)) + new_point = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0)) basic_type_assertions(ground_truth_object, new_point, "Implicit 2d cartesian point instantiation") - ground_truth_object = neomodel.NeomodelPoint((0.0, 0.0, 0.0)) - new_point = neomodel.NeomodelPoint((0.0, 0.0, 0.0)) + ground_truth_object = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0, 0.0)) + new_point = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0, 0.0)) basic_type_assertions(ground_truth_object, new_point, "Implicit 3d cartesian point instantiation") # Explicit geographical point with coords - ground_truth_object = neomodel.NeomodelPoint((0.0, 0.0), crs='wgs-84') - new_point = neomodel.NeomodelPoint((0.0, 0.0), crs='wgs-84') + ground_truth_object = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0), crs='wgs-84') + new_point = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0), crs='wgs-84') basic_type_assertions(ground_truth_object, new_point, "Explicit 2d geographical point with tuple of coords instantiation") - ground_truth_object = neomodel.NeomodelPoint((0.0, 0.0, 0.0), crs='wgs-84-3d') - new_point = neomodel.NeomodelPoint((0.0, 0.0, 0.0), crs='wgs-84-3d') + ground_truth_object = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0, 0.0), crs='wgs-84-3d') + new_point = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0, 0.0), crs='wgs-84-3d') basic_type_assertions(ground_truth_object, new_point, "Explicit 3d geographical point with tuple of coords instantiation") # Cartesian point with named arguments - ground_truth_object = neomodel.NeomodelPoint(x=0.0, y=0.0) - new_point = neomodel.NeomodelPoint(x=0.0, y=0.0) + ground_truth_object = neomodel.contrib.spatial_properties.NeomodelPoint(x=0.0, y=0.0) + new_point = neomodel.contrib.spatial_properties.NeomodelPoint(x=0.0, y=0.0) basic_type_assertions(ground_truth_object, new_point, "Cartesian 2d point with named arguments") - ground_truth_object = neomodel.NeomodelPoint(x=0.0, y=0.0, z=0.0) - new_point = neomodel.NeomodelPoint(x=0.0, y=0.0, z=0.0) + ground_truth_object = neomodel.contrib.spatial_properties.NeomodelPoint(x=0.0, y=0.0, z=0.0) + new_point = neomodel.contrib.spatial_properties.NeomodelPoint(x=0.0, y=0.0, z=0.0) basic_type_assertions(ground_truth_object, new_point, "Cartesian 3d point with named arguments") # Geographical point with named arguments - ground_truth_object = neomodel.NeomodelPoint(longitude=0.0, latitude=0.0) - new_point = neomodel.NeomodelPoint(longitude=0.0, latitude=0.0) + ground_truth_object = neomodel.contrib.spatial_properties.NeomodelPoint(longitude=0.0, latitude=0.0) + new_point = neomodel.contrib.spatial_properties.NeomodelPoint(longitude=0.0, latitude=0.0) basic_type_assertions(ground_truth_object, new_point, "Geographical 2d point with named arguments") - ground_truth_object = neomodel.NeomodelPoint(longitude=0.0, latitude=0.0, height=0.0) - new_point = neomodel.NeomodelPoint(longitude=0.0, latitude=0.0 ,height=0.0) + ground_truth_object = neomodel.contrib.spatial_properties.NeomodelPoint(longitude=0.0, latitude=0.0, height=0.0) + new_point = neomodel.contrib.spatial_properties.NeomodelPoint(longitude=0.0, latitude=0.0 ,height=0.0) basic_type_assertions(ground_truth_object, new_point, "Geographical 3d point with named arguments") @@ -95,21 +97,21 @@ def test_copy_constructors(): # Instantiate from Shapely point # Implicit cartesian from shapely point - ground_truth = neomodel.NeomodelPoint((0.0, 0.0), crs='cartesian') + ground_truth = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0), crs='cartesian') shapely_point = shapely.geometry.Point((0.0, 0.0)) - new_point = neomodel.NeomodelPoint(shapely_point) + new_point = neomodel.contrib.spatial_properties.NeomodelPoint(shapely_point) basic_type_assertions(ground_truth, new_point, 'Implicit cartesian by shapely Point') # Explicit geographical by shapely point - ground_truth = neomodel.NeomodelPoint((0.0, 0.0, 0.0), crs='wgs-84-3d') + ground_truth = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0, 0.0), crs='wgs-84-3d') shapely_point = shapely.geometry.Point((0.0, 0.0, 0.0)) - new_point = neomodel.NeomodelPoint(shapely_point, crs='wgs-84-3d') + new_point = neomodel.contrib.spatial_properties.NeomodelPoint(shapely_point, crs='wgs-84-3d') basic_type_assertions(ground_truth, new_point, 'Explicit geographical by shapely Point') # Copy constructor for NeomodelPoints - ground_truth = neomodel.NeomodelPoint((0.0, 0.0)) - other_neomodel_point = neomodel.NeomodelPoint((0.0, 0.0)) - new_point = neomodel.NeomodelPoint(other_neomodel_point) + ground_truth = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0)) + other_neomodel_point = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0)) + new_point = neomodel.contrib.spatial_properties.NeomodelPoint(other_neomodel_point) basic_type_assertions(ground_truth, new_point, 'NeomodelPoint copy constructor') @@ -121,23 +123,26 @@ def test_prohibited_constructor_forms(): """ # Absurd CRS with pytest.raises(ValueError, message='Expected ValueError("Invalid CRS...")'): - new_point = neomodel.NeomodelPoint((0,0), crs='blue_hotel') + new_point = neomodel.contrib.spatial_properties.NeomodelPoint((0,0), crs='blue_hotel') # Absurd coord dimensionality with pytest.raises(ValueError, message='Expected ValueError("Invalid vector dimensions...")'): - new_point = neomodel.NeomodelPoint((0,0,0,0,0,0,0), crs='cartesian') + new_point = neomodel.contrib.spatial_properties.NeomodelPoint((0,0,0,0,0,0,0), crs='cartesian') # Absurd datatype passed to copy constructor with pytest.raises(TypeError, message='Expected TypeError("Invalid object passed to copy constructor...")'): - new_point = neomodel.NeomodelPoint('it don''t mean a thing if it ain''t got that swing', crs='cartesian') + new_point = neomodel.contrib.spatial_properties.NeomodelPoint('it don''t mean a thing if it ' + 'ain''t got that swing', crs='cartesian') # Trying to instantiate a point with any of BOTH x,y,z or longitude, latitude, height with pytest.raises(ValueError, message='Expected ValueError("Invalid instantiation via arguments...")'): - new_point = neomodel.NeomodelPoint(x=0.0, y=0.0, longitude=0.0, latitude=2.0, height=-2.0, crs='cartesian') + new_point = neomodel.contrib.spatial_properties.NeomodelPoint(x=0.0, y=0.0, + longitude=0.0, latitude=2.0, height=-2.0, + crs='cartesian') # Trying to instantiate a point with absolutely NO parameters with pytest.raises(ValueError, message='Expected ValueError("Invalid instantiation via no arguments...")'): - new_point = neomodel.NeomodelPoint() + new_point = neomodel.contrib.spatial_properties.NeomodelPoint() def test_property_accessors_depending_on_crs(): @@ -147,7 +152,7 @@ def test_property_accessors_depending_on_crs(): :return: """ # Geometrical points only have x,y,z coordinates - new_point = neomodel.NeomodelPoint((0.0, 0.0, 0.0), crs='cartesian') + new_point = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0, 0.0), crs='cartesian') with pytest.raises(AttributeError, message='Expected AttributeError("Invalid coordinate(''longitude'')...")'): new_point.longitude with pytest.raises(AttributeError, message='Expected AttributeError("Invalid coordinate(''latitude'')...")'): @@ -156,7 +161,7 @@ def test_property_accessors_depending_on_crs(): new_point.height # Geographical points only have longitude, latitude, height coordinates - new_point = neomodel.NeomodelPoint((0.0, 0.0, 0.0), crs='wgs-84-3d') + new_point = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0, 0.0), crs='wgs-84-3d') with pytest.raises(AttributeError, message='Expected AttributeError("Invalid coordinate(''x'')...")'): new_point.x with pytest.raises(AttributeError, message='Expected AttributeError("Invalid coordinate(''y'')...")'): @@ -172,13 +177,13 @@ def test_property_accessors(): :return: """ # Geometrical points - new_point = neomodel.NeomodelPoint((0.0, 1.0, 2.0), crs='cartesian-3d') + new_point = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 1.0, 2.0), crs='cartesian-3d') assert new_point.x == 0.0, 'Expected x coordinate to be 0.0' assert new_point.y == 1.0, 'Expected y coordinate to be 1.0' assert new_point.z == 2.0, 'Expected z coordinate to be 2.0' # Geographical points - new_point = neomodel.NeomodelPoint((0.0, 1.0, 2.0), crs='wgs-84-3d') + new_point = neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 1.0, 2.0), crs='wgs-84-3d') assert new_point.longitude == 0.0, 'Expected longitude to be 0.0' assert new_point.latitude == 1.0, 'Expected latitude to be 1.0' assert new_point.height == 2.0, 'Expected height to be 2.0' diff --git a/test/test_spatial_properties.py b/test/test_contrib/test_spatial_properties.py similarity index 66% rename from test/test_spatial_properties.py rename to test/test_contrib/test_spatial_properties.py index 4f686caa..89985626 100644 --- a/test/test_spatial_properties.py +++ b/test/test_contrib/test_spatial_properties.py @@ -5,6 +5,7 @@ """ import neomodel +import neomodel.contrib.spatial_properties import pytest import neo4j.v1 from .test_spatial_datatypes import basic_type_assertions @@ -18,13 +19,13 @@ def test_spatial_point_property(): :return: """ with pytest.raises(ValueError, message='Expected ValueError("Invalid CRS (CRS not specified)")'): - a_point_property = neomodel.PointProperty() + a_point_property = neomodel.contrib.spatial_properties.PointProperty() with pytest.raises(ValueError, message='Expected ValueError("Invalid CRS (CRS not acceptable)")'): - a_point_property = neomodel.PointProperty(crs='crs_isaak') + a_point_property = neomodel.contrib.spatial_properties.PointProperty(crs='crs_isaak') with pytest.raises(TypeError, message='Expected TypeError("Invalid default value")'): - a_point_property = neomodel.PointProperty(default=(0.0, 0.0), crs='cartesian') + a_point_property = neomodel.contrib.spatial_properties.PointProperty(default=(0.0, 0.0), crs='cartesian') def test_inflate(): @@ -49,10 +50,10 @@ def test_inflate(): # Run the above tests for a_value in values_from_db: - expected_point = neomodel.NeomodelPoint(tuple(a_value[0]), - crs=neomodel.spatial_properties.SRID_TO_CRS[a_value[0].srid]) - inflated_point = neomodel.PointProperty(crs=neomodel.spatial_properties.SRID_TO_CRS[a_value[0].srid]).inflate( - a_value[0]) + expected_point = neomodel.contrib.spatial_properties.NeomodelPoint(tuple(a_value[0]), + crs=neomodel.contrib.spatial_properties.SRID_TO_CRS[a_value[0].srid]) + inflated_point = neomodel.contrib.spatial_properties.PointProperty( + crs=neomodel.contrib.spatial_properties.SRID_TO_CRS[a_value[0].srid]).inflate(a_value[0]) basic_type_assertions(expected_point, inflated_point, '{}, received {}'.format(a_value[1], inflated_point)) @@ -63,22 +64,22 @@ def test_deflate(): """ # Please see inline comments in `test_inflate`. This test function is 90% to that one with very minor differences. # - CRS_TO_SRID = dict([(value, key) for key, value in neomodel.spatial_properties.SRID_TO_CRS.items()]) + CRS_TO_SRID = dict([(value, key) for key, value in neomodel.contrib.spatial_properties.SRID_TO_CRS.items()]) # Values to construct and expect during deflation - values_from_neomodel = [(neomodel.NeomodelPoint((0.0, 0.0), crs='cartesian'), + values_from_neomodel = [(neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0), crs='cartesian'), 'Expected Neo4J 2d cartesian point when deflating Neomodel 2d cartesian point'), - (neomodel.NeomodelPoint((0.0, 0.0, 0.0), crs='cartesian-3d'), + (neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0, 0.0), crs='cartesian-3d'), 'Expected Neo4J 3d cartesian point when deflating Neomodel 3d cartesian point'), - (neomodel.NeomodelPoint((0.0,0.0), crs='wgs-84'), + (neomodel.contrib.spatial_properties.NeomodelPoint((0.0,0.0), crs='wgs-84'), 'Expected Neo4J 2d geographical point when deflating Neomodel 2d geographical point'), - (neomodel.NeomodelPoint((0.0, 0.0, 0.0), crs='wgs-84-3d'), + (neomodel.contrib.spatial_properties.NeomodelPoint((0.0, 0.0, 0.0), crs='wgs-84-3d'), 'Expected Neo4J 3d geographical point when deflating Neomodel 3d geographical point')] # Run the above tests. for a_value in values_from_neomodel: expected_point = neo4j.v1.spatial.Point(tuple(a_value[0].coords[0])) expected_point.srid = CRS_TO_SRID[a_value[0].crs] - deflated_point = neomodel.PointProperty(crs=a_value[0].crs).deflate(a_value[0]) + deflated_point = neomodel.contrib.spatial_properties.PointProperty(crs=a_value[0].crs).deflate(a_value[0]) basic_type_assertions(expected_point, deflated_point, '{}, received {}'.format(a_value[1], deflated_point), check_neo4j_points=True) @@ -90,14 +91,14 @@ def test_default_value(): """ def get_some_point(): - return neomodel.NeomodelPoint((random.random(),random.random())) + return neomodel.contrib.spatial_properties.NeomodelPoint((random.random(),random.random())) class LocalisableEntity(neomodel.StructuredNode): """ A very simple entity to try out the default value assignment. """ identifier = neomodel.UniqueIdProperty() - location = neomodel.PointProperty(crs='cartesian', default=get_some_point) + location = neomodel.contrib.spatial_properties.PointProperty(crs='cartesian', default=get_some_point) # Save an object an_object = LocalisableEntity().save() @@ -105,7 +106,8 @@ class LocalisableEntity(neomodel.StructuredNode): # Retrieve it retrieved_object = LocalisableEntity.nodes.get(identifier=an_object.identifier) # Check against an independently created value - assert retrieved_object.location == neomodel.NeomodelPoint(coords), "Default value assignment failed." + assert retrieved_object.location == neomodel.contrib.spatial_properties.NeomodelPoint(coords), \ + "Default value assignment failed." def test_array_of_points(): @@ -120,12 +122,15 @@ class AnotherLocalisableEntity(neomodel.StructuredNode): A very simple entity with an array of locations """ identifier = neomodel.UniqueIdProperty() - locations = neomodel.ArrayProperty(neomodel.PointProperty(crs='cartesian')) + locations = neomodel.ArrayProperty(neomodel.contrib.spatial_properties.PointProperty(crs='cartesian')) an_object = AnotherLocalisableEntity(locations= - [neomodel.NeomodelPoint((0.0,0.0)), neomodel.NeomodelPoint((1.0,0.0))]).save() + [neomodel.contrib.spatial_properties.NeomodelPoint((0.0,0.0)), + neomodel.contrib.spatial_properties.NeomodelPoint((1.0,0.0))]).save() retrieved_object = AnotherLocalisableEntity.nodes.get(identifier=an_object.identifier) + assert type(retrieved_object.locations) is list, "Array of Points definition failed." - assert retrieved_object.locations == [neomodel.NeomodelPoint((0.0,0.0)), neomodel.NeomodelPoint((1.0,0.0))], \ + assert retrieved_object.locations == [neomodel.contrib.spatial_properties.NeomodelPoint((0.0,0.0)), + neomodel.contrib.spatial_properties.NeomodelPoint((1.0,0.0))], \ "Array of Points incorrect values."