Source code for py2neo.database.selection

#!/usr/bin/env python
# -*- encoding: utf-8 -*-

# Copyright 2011-2016, Nigel Small
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from py2neo.database.cypher import cypher_escape


def _property_equality_conditions(properties, offset=1):
    for i, (key, value) in enumerate(properties.items(), start=offset):
        if key == "__id__":
            condition = "id(_)"
        else:
            condition = "_.%s" % cypher_escape(key)
        if isinstance(value, (tuple, set, frozenset)):
            condition += " IN {%d}" % i
            parameters = {"%d" % i: list(value)}
        else:
            condition += " = {%d}" % i
            parameters = {"%d" % i: value}
        yield condition, parameters


[docs]class NodeSelection(object): """ An immutable set of node selection criteria. """ def __init__(self, graph, labels=frozenset(), conditions=tuple(), order_by=tuple(), skip=None, limit=None): self.graph = graph self._labels = frozenset(labels) self._conditions = tuple(conditions) self._order_by = tuple(order_by) self._skip = skip self._limit = limit def __iter__(self): for node, in self.graph.run(*self._query_and_parameters): yield node
[docs] def first(self): """ Evaluate the selection and return the first :py:class:`.Node` selected or :py:const:`None` if no matching nodes are found. :return: a single matching :py:class:`.Node` or :py:const:`None` """ return self.graph.evaluate(*self._query_and_parameters)
@property def _query_and_parameters(self): """ A tuple of the Cypher query and parameters used to select the nodes that match the criteria for this selection. :return: Cypher query string """ clauses = ["MATCH (_%s)" % "".join(":%s" % cypher_escape(label) for label in self._labels)] parameters = {} if self._conditions: conditions = [] for condition in self._conditions: if isinstance(condition, tuple): condition, param = condition parameters.update(param) conditions.append(condition) clauses.append("WHERE %s" % " AND ".join(conditions)) clauses.append("RETURN _") if self._order_by: clauses.append("ORDER BY %s" % (", ".join(self._order_by))) if self._skip: clauses.append("SKIP %d" % self._skip) if self._limit is not None: clauses.append("LIMIT %d" % self._limit) return " ".join(clauses), parameters
[docs] def where(self, *conditions, **properties): """ Create a new selection based on this selection. The criteria specified for refining the selection consist of conditions and properties. Conditions are individual Cypher expressions that would be found in a `WHERE` clause; properties are used as exact matches for property values. To refer to the current node within a condition expression, use the underscore character ``_``. For example:: selection.where("_.name =~ 'J.*") Simple property equalities can also be specified:: selection.where(born=1976) :param conditions: Cypher expressions to add to the selection `WHERE` clause :param properties: exact property match keys and values :return: refined selection object """ return self.__class__(self.graph, self._labels, self._conditions + conditions + tuple(_property_equality_conditions(properties)), self._order_by, self._skip, self._limit)
[docs] def order_by(self, *fields): """ Order by the fields or field expressions specified. To refer to the current node within a field or field expression, use the underscore character ``_``. For example:: selection.order_by("_.name", "max(_.a, _.b)") :param fields: fields or field expressions to order by :return: refined selection object """ return self.__class__(self.graph, self._labels, self._conditions, fields, self._skip, self._limit)
[docs] def skip(self, amount): """ Skip the first `amount` nodes in the result. :param amount: number of nodes to skip :return: refined selection object """ return self.__class__(self.graph, self._labels, self._conditions, self._order_by, amount, self._limit)
[docs] def limit(self, amount): """ Limit the selection to at most `amount` nodes. :param amount: maximum number of nodes to select :return: refined selection object """ return self.__class__(self.graph, self._labels, self._conditions, self._order_by, self._skip, amount)
[docs]class NodeSelector(object): """ A :py:class:`.NodeSelector` can be used to locate nodes that fulfil a specific set of criteria. Typically, a single node can be identified passing a specific label and property key-value pair. However, any number of labels and any condition supported by the Cypher `WHERE` clause is allowed. For a simple selection by label and property:: >>> from py2neo import Graph, NodeSelector >>> graph = Graph() >>> selector = NodeSelector(graph) >>> selected = selector.select("Person", name="Keanu Reeves") >>> list(selected) [(f9726ea:Person {born:1964,name:"Keanu Reeves"})] For a more comprehensive selection using Cypher expressions, the :meth:`.NodeSelection.where` method can be used for further refinement. Here, the underscore character can be used to refer to the node being filtered:: >>> selected = selector.select("Person").where("_.name =~ 'J.*'", "1960 <= _.born < 1970") >>> list(selected) [(a03f6eb:Person {born:1967,name:"James Marshall"}), (e59993d:Person {born:1966,name:"John Cusack"}), (c44901e:Person {born:1960,name:"John Goodman"}), (b141775:Person {born:1965,name:"John C. Reilly"}), (e40244b:Person {born:1967,name:"Julia Roberts"})] The underlying query is only evaluated when the selection undergoes iteration or when a specific evaluation method is called (such as :meth:`.NodeSelection.first`). This means that a :class:`.NodeSelection` instance may be reused before and after a data changes for different results. """ selection_class = NodeSelection def __init__(self, graph): self.graph = graph self._all = self.selection_class(self.graph)
[docs] def select(self, *labels, **properties): """ Describe a basic node selection using labels and property equality. :param labels: node labels to match :param properties: set of property keys and values to match :return: :py:class:`.NodeSelection` instance """ if labels or properties: return self.selection_class(self.graph, frozenset(labels), tuple(_property_equality_conditions(properties))) else: return self._all