Skip to content

Commit

Permalink
Finalised documentation and tests for NeomodelPoint, PointProperty
Browse files Browse the repository at this point in the history
aanastasiou committed Nov 20, 2018

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 3ccef44 commit 3b37b63
Showing 6 changed files with 99 additions and 39 deletions.
6 changes: 6 additions & 0 deletions doc/source/module_documentation.rst
Original file line number Diff line number Diff line change
@@ -20,6 +20,12 @@ Properties
:members:
:show-inheritance:

Spatial Properties & Datatypes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: neomodel.spatial_properties
:members:
:show-inheritance:

Relationships
-------------
.. automodule:: neomodel.relationship
25 changes: 16 additions & 9 deletions doc/source/properties.rst
Original file line number Diff line number Diff line change
@@ -4,14 +4,14 @@ Property types

The following properties are available on nodes and relationships:

============================================== ==============================================
============================================== ===================================================
:class:`~neomodel.properties.AliasProperty` :class:`~neomodel.properties.IntegerProperty`
:class:`~neomodel.properties.ArrayProperty` :class:`~neomodel.properties.JSONProperty`
:class:`~neomodel.properties.BooleanProperty` :class:`~neomodel.properties.RegexProperty`
:class:`~neomodel.properties.DateProperty` :class:`~neomodel.properties.StringProperty`
:class:`~neomodel.properties.DateTimeProperty` :class:`~neomodel.properties.UniqueIdProperty`
:class:`~neomodel.properties.FloatProperty` :class:`~neomodel.properties.PointProperty`
============================================== ==============================================
:class:`~neomodel.properties.FloatProperty` :class:`~neomodel.spatial_properties.PointProperty`
============================================== ===================================================


Defaults
@@ -87,13 +87,20 @@ Other properties
* `RegexProperty` - passing in a validator regex: `RegexProperty(expression=r'\d\w')`
* `NormalProperty` - use one method (normalize) to inflate and deflate.
* `PointProperty` - store and validate `spatial values <https://neo4j.com/docs/developer-manual/3.4/cypher/syntax/spatial/>`_
* A `Point Property` requires its `crs` argument to be set during definition and returns `NeomodelPoint` objects.
`NeomodelPoint` objects have attributes such as `crs,x,y,z,longitude,latitude,height` (**depending on** the type
of Point) but more importantly are subclasses of `shapely.geometry.Point <http://toblerity.org/shapely/manual.html#geometric-objects>`_.
Therefore, they can readily participate in further geospatial processing via `shapely` (or
* A `PointProperty` requires its `crs` argument to be set during definition and returns
:class:`~neomodel.spatial_properties.NeomodelPoint` objects.
:class:`~neomodel.spatial_properties.NeomodelPoint` objects have attributes such as
`crs,x,y,z,longitude,latitude,height` (**depending on** the type of Point) but more importantly are subclasses
of `shapely.geometry.Point <http://toblerity.org/shapely/manual.html#geometric-objects>`_. Therefore, they can
readily participate in further geospatial processing via `shapely` (or
`PySAL <https://pysal.readthedocs.io/en/latest/users/tutorials/shapely.html>`_) out of the box.
`NeomodelPoint` objects are immutable. To update a `PointProperty`, please construct a new object rather than trying
to modify the existing one.
* :class:`~neomodel.spatial_properties.NeomodelPoint` objects are immutable. To update a `PointProperty`,
please construct a new object rather than trying to modify the existing one.
* If `shapely <https://pypi.org/project/Shapely/>`_ is not installed, then `NeomodelPoint, PointProperty` will not
be available through neomodel. That is, `shapely` is not an absolute requirement for `neomodel`. Once `shapely` is
installed, this will be picked up by neomodel and the datatypes and properties will become available without
having to re-install it.
* `PointProperty` objects can be used anywhere a `neomodel` property can (i.e. in indices, array definitions, etc).

Aliasing properties
===================
2 changes: 1 addition & 1 deletion neomodel/spatial_properties.py
Original file line number Diff line number Diff line change
@@ -296,4 +296,4 @@ def deflate(self, value):
elif value.crs == 'wgs-84':
return neo4j.v1.spatial.WGS84Point((value.longitude, value.latitude))
elif value.crs == 'wgs-84-3d':
return neo4j.v1.spatial.WGS84Point((value.longitude, value.latitude, value.height))
return neo4j.v1.spatial.WGS84Point((value.longitude, value.latitude, value.height))
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@
keywords='graph neo4j ORM OGM',
scripts=['scripts/neomodel_install_labels', 'scripts/neomodel_remove_labels'],
setup_requires=['pytest-runner'] if any(x in ('pytest', 'test') for x in sys.argv) else [],
tests_require=['pytest'],
tests_require=['pytest', 'shapely'],
install_requires=['neo4j-driver>=1.5.2, <1.7.0', 'pytz>=2016.10'],
classifiers=[
"Development Status :: 5 - Production/Stable",
39 changes: 20 additions & 19 deletions test/test_spatial_datatypes.py
Original file line number Diff line number Diff line change
@@ -11,20 +11,18 @@
import pytest


try:
basestring
except NameError:
basestring = str


def basic_type_assertions(ground_truth, tested_object, test_description, check_neo4j_points=False):
"""
Tests if `tested_object` has been created as intended
:param ground_truth:
:param tested_object:
:param test_description:
:param check_srid:
Tests that `tested_object` has been created as intended.
:param ground_truth: The object as it is supposed to have been created.
:type ground_truth: NeomodelPoint or neo4j.v1.spatial.Point
:param tested_object: The object as it results from one of the contructors.
:type tested_object: NeomodelPoint or neo4j.v1.spatial.Point
:param test_description: A brief description of the test being performed.
:type test_description: str
:param check_neo4j_points: Whether to assert between NeomodelPoint or neo4j.v1.spatial.Point objects.
:type check_neo4j_points: bool
:return:
"""
if check_neo4j_points:
@@ -45,11 +43,11 @@ def basic_type_assertions(ground_truth, tested_object, test_description, check_n
# Object Construction
def test_coord_constructor():
"""
Tests all the possible ways by which a NeomodelPoint can be instantiated succesfully via passing coordinates.
Tests all the possible ways by which a NeomodelPoint can be instantiated successfully via passing coordinates.
:return:
"""

# Implicit cartesian with coords
# Implicit cartesian point with coords
ground_truth_object = neomodel.NeomodelPoint((0.0, 0.0))
new_point = neomodel.NeomodelPoint((0.0, 0.0))
basic_type_assertions(ground_truth_object, new_point, "Implicit 2d cartesian point instantiation")
@@ -90,7 +88,7 @@ def test_coord_constructor():

def test_copy_constructors():
"""
Tests all the possible ways by which a NeomodelPoint can be instantiated succesfully via a copy constructor call.
Tests all the possible ways by which a NeomodelPoint can be instantiated successfully via a copy constructor call.
:return:
"""
@@ -117,7 +115,8 @@ def test_copy_constructors():

def test_prohibited_constructor_forms():
"""
Tests all the possible forms by which construction of NeomodelPoints should fail
Tests all the possible forms by which construction of NeomodelPoints should fail.
:return:
"""
# Absurd CRS
@@ -143,7 +142,8 @@ def test_prohibited_constructor_forms():

def test_property_accessors_depending_on_crs():
"""
Tests that points are accessed via their respective accessors
Tests that points are accessed via their respective accessors.
:return:
"""
# Geometrical points only have x,y,z coordinates
@@ -167,7 +167,8 @@ def test_property_accessors_depending_on_crs():

def test_property_accessors():
"""
Tests that points are accessed via their respective accessors and that these accessors return the right values
Tests that points are accessed via their respective accessors and that these accessors return the right values.
:return:
"""
# Geometrical points
@@ -180,4 +181,4 @@ def test_property_accessors():
new_point = neomodel.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'
assert new_point.height == 2.0, 'Expected height to be 2.0'
64 changes: 55 additions & 9 deletions test/test_spatial_properties.py
Original file line number Diff line number Diff line change
@@ -8,16 +8,13 @@
import pytest
import neo4j.v1
from .test_spatial_datatypes import basic_type_assertions

try:
basestring
except NameError:
basestring = str
import random


def test_spatial_point_property():
"""
Tests that specific modes of instantiation fail as predicted
Tests that specific modes of instantiation fail as expected.
:return:
"""
with pytest.raises(ValueError, message='Expected ValueError("Invalid CRS (CRS not specified)")'):
@@ -32,7 +29,8 @@ def test_spatial_point_property():

def test_inflate():
"""
Tests that the marshalling from neo4j to neomodel data types works as expected
Tests that the marshalling from neo4j to neomodel data types works as expected.
:return:
"""

@@ -63,7 +61,7 @@ def test_deflate():
Tests that the marshalling from neomodel to neo4j data types works as expected
:return:
"""
# Please see inline comments in `test_inflate`. This test function is 90% to that one with very minor differences
# 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()])
# Values to construct and expect during deflation
@@ -82,4 +80,52 @@ def test_deflate():
expected_point.srid = CRS_TO_SRID[a_value[0].crs]
deflated_point = neomodel.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)
check_neo4j_points=True)


def test_default_value():
"""
Tests that the default value passing mechanism works as expected with NeomodelPoint values.
:return:
"""

def get_some_point():
return neomodel.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)

# Save an object
an_object = LocalisableEntity().save()
coords = an_object.location.coords[0]
# 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."


def test_array_of_points():
"""
Tests that Arrays of Points work as expected.
:return:
"""

class AnotherLocalisableEntity(neomodel.StructuredNode):
"""
A very simple entity with an array of locations
"""
identifier = neomodel.UniqueIdProperty()
locations = neomodel.ArrayProperty(neomodel.PointProperty(crs='cartesian'))

an_object = AnotherLocalisableEntity(locations=
[neomodel.NeomodelPoint((0.0,0.0)), neomodel.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))], \
"Array of Points incorrect values."

0 comments on commit 3b37b63

Please sign in to comment.