The Neo4j Type System

At this point, we should take a look at the Cypher type system. As Neo4j is written in Java (the j in Neo4j stands for Java after all), there are some discrepancies between the types stored in the Neo4j database and native Python types.

Some values like strings, floats, booleans, and nulls map directly to Python types, but more complex types need special handling.

Python Types to Neo4j Types
Python Type Neo4j Cypher Type Notes

None

null

bool

Boolean

int

Integer

float

Float

str

String

bytearray

Bytes [1]

list

List

dict

Map

neo4j.spatial.Point

Point

See Spatial Data Types

neo4j.spatial.CartesianPoint

Point (Cartesian)

See Spatial Data Types

neo4j.spatial.WGS84Point

Point (WGS-84)

See Spatial Data Types

neo4j.graph.Node

Node

See Nodes & Relationships

neo4j.graph.Relationship

Relationship

See Nodes & Relationships

neo4j.graph.Path

Path

See Nodes & Relationships

For more information about Temporal Data Types, see Temporal Data Types.

Let’s take a look at some of these types in more detail.

Nodes & Relationships

Nodes and Relationships are both returned as similar classes.

As an example, let’s take the following code snippet:

python
Return Nodes and Relationships
result = tx.run("""
MATCH path = (person:Person)-[actedIn:ACTED_IN]->(movie:Movie {title: $title})
RETURN path, person, actedIn, movie
""", title=movie)

The query will return one record for each :Person and :Movie node with an :ACTED_IN relationship between them.

Nodes

We can retrieve the movie value from a record using the [] brackets method, providing a string that refers to the alias for the :Movie node in the Cypher statement.

python
for record in result:
    node = record["movie"]

The value assigned to the node variable will be the instance of a Node. Node is a type provided by the Neo4j Python Driver to hold the information held in Neo4j for the node.

python
Working with Node Objects
print(node.id)              # (1)
print(node.labels)          # (2)
print(node.items())         # (3)

# (4)
print(node["name"])
print(node.get("name", "N/A"))
  1. The id property provides access to the node’s Internal ID
    eg. 1234

  2. The labels property is a frozenset containing an array of labels attributed to the Node
    eg. ['Person', 'Actor']

  3. The items() method provides access to the node’s properties as an iterable of all name-value pairs.
    eg. {name: 'Tom Hanks', tmdbId: '31' }

  4. A single property can be retrieved by either using [] brackets or using the get() method. The get() method also allows you to define a default property if none exists.

Internal IDs

Internal IDs refer to the position in the Neo4j store files where the record is held. These numbers can be re-used, a best practice is to always look up a node by an indexed property rather than relying on an internal ID.

Relationships

Relationship objects are similar to a Node in that they provide the same method for accessing the internal ID and properties.

python
Working with Relationship Objects
acted_in = record["actedIn"]

print(acted_in.id)         # (1)
print(acted_in.type)       # (2)
print(acted_in.items())    # (3)

# 4
print(acted_in["roles"])
print(acted_in.get("roles", "(Unknown)"))

print(acted_in.start_node) # (5)
print(acted_in.end_node)   # (6)
  1. The id property holds the internal ID of the relationship.
    eg. 9876

  2. The type property holds the relationship type
    eg. ACTED_IN

  3. The items() method provides access to the relationships’s properties as an iterable of all name-value pairs.
    eg. {role: 'Woody'}

  4. As with Nodes, you can access a single relationship property using brackets or the get() method.

  5. start_node - an integer representing the internal ID for the node at the start of the relationship

  6. end_node - an integer representing the internal ID for the node at the end of the relationship

Paths

If you return a path of nodes and relationships, they will be returned as an instance of a Path.

python
Working with Path Objects
path = record["path"]

print(path.start_node)  # (1)
print(path.end_node)    # (2)
print(len(path))  # (1)
print(path.relationships)  # (1)
  1. start_node - a Neo4j Integer representing the internal ID for the node at the start of the path

  2. end_node - a Neo4j Integer representing the internal ID for the node at the end of the path

  3. len(path) - A count of the number of relationships within the path

  4. relationships - An array of Relationship objects within the path.

Path Segments

A path is split into segments representing each relationship in the path. For example, say we have a path of (p:Person)-[:ACTED_IN]→(m:Movie)-[:IN_GENRE]→(g:Genre), there would be two relationships.

  1. (p:Person)-[:ACTED_IN]→(m:Movie)

  2. (m:Movie)-[:IN_GENRE]→(g:Genre)

The relationships within a path can be iterated over using the iter() function.

python
Iterating over Segments
for rel in iter(path):
    print(rel.type)
    print(rel.start_node)
    print(rel.end_node)

Temporal Data Types

Temporal data types are implemented by the neo4j.time module.

It provides a set of types compliant with ISO-8601 and Cypher, which are similar to those found in the built-in datetime module. Sub-second values are measured to nanosecond precision and the types are compatible with pytz.

The table below shows the general mappings between Cypher and the temporal types provided by the driver.

In addition, the built-in temporal types can be passed as parameters and will be mapped appropriately.

Learn how to interact with Neo4j from Python using the Neo4j Python DriverTemporal Types
Neo4j Cypher Type Python driver type Python built-in type tzinfo

Date

neo4j.time.Date

datetime.date

Time

neo4j.time.Time

datetime.time

not None

LocalTime

neo4j.time.Time

datetime.time

None

DateTime

neo4j.time.DateTime

datetime.datetime

not None

LocalDateTime

neo4j.time.DateTime

datetime.datetime

None

Duration

neo4j.time.Duration

datetime.timedelta

python
Working with Temporal types
# Create a DateTime instance using individual values
datetime = neo4j.time.DateTime(year, month, day, hour, minute, second, nanosecond)

#  Create a DateTime  a time stamp (seconds since unix epoch).
from_timestamp = neo4j.time.DateTime(1609459200000) # 2021-01-01

# Get the current date and time.
now = neo4j.time.DateTime.now()

print(now.year) # 2022

Each of the above types has a number of attributes for accessing the different, for example year, month, day, and in the case of the types that include a time, hour, minute and second.

For more information, see Temporal Data Types&.

Spatial Data Types

Cypher has built-in support for handling spatial values (points), and the underlying database supports storing these point values as properties on nodes and relationships.

Points

Cypher Type Python Type

Point

neo4j.spatial.Point

Point (Cartesian)

neo4j.spatial.CartesianPoint

Point (WGS-84)

neo4j.spatial.WGS84Point

CartesianPoint

A Cartesian Point can be created in Cypher by supplying x and y values to the point() function. The optional z value represents the height.

To create a Cartesian Point in Python, you can import the neo4j.spatial.CartesianPoint class.

python
Cartesian
# Using X and Y values
twoD=CartesianPoint((1.23, 4.56))
print(twoD.x, twoD.y)

# Using X, Y and Z
threeD=CartesianPoint((1.23, 4.56, 7.89))
print(threeD.x, threeD.y, threeD.z)

For more information, see the Python reference.

WGS84Point

A WGS84 Point can be created in Cypher by supplying latitude and longitude values to the point() function. To create a Cartesian Point in Python, you can import the neo4j.spatial.WGS84Point class.

python
WGS84
london=WGS84Point((-0.118092, 51.509865))
print(london.longitude, london.latitude)

the_shard=WGS84Point((-0.086500, 51.504501, 310))
print(the_shard.longitude, the_shard.latitude, the_shard.height)

For more information, see the Python reference.

Distance

When using the point.distance function in Cypher, the distance calculated between two points is returned as a float.

cypher
WITH point({x: 1, y:1}) AS one,
     point({x: 10, y: 10}) AS two

RETURN point.distance(one, two) // 12.727922061357855

For more information on Spatial types, see the Cypher Manual.

Check Your Understanding

1. Accessing Node Properties

Which of the following options are valid methods for accessing the name property on the node object node?

  • node["name"]

  • node["properties"]["name"]

  • node.properties.name

  • node.get("name")

  • property(node, "name")

Hint

You can get a property of a node using two of the methods above.

Solution

The valid options to get a property from a node or relationship are using square brackets (node["name"]) or using the .get() method (node.get("name")).

2. Relationship Types

What method would you use to access the type of the relationship acted_in?

  • acted_in["type"]

  • acted_in.labels

  • acted_in.type

  • neo4j.getType(acted_in)

Hint

You are looking for the type of the relationship.

Solution

The answer is acted_in.type.

Lesson Summary

In this lesson you have learned how to handle some of the more complex objects returned by a Cypher statement.

As we progress through this module, you will use the knowledge gained so far to read data from, and write data back to the database. In the next Challenge, you will modify the repository to read from the database.