Source code for surf.resource.result_proxy

""" Module for ResultProxy. """

from surf.exc import NoResultFound, MultipleResultsFound
from surf.rdf import Literal
from surf.util import attr2rdf, value_to_rdf

[docs]class ResultProxy(object): """ Interface to :meth:`surf.store.Store.get_by`. ResultProxy collects filtering parameters. When iterated, it executes :meth:`surf.store.Store.get_by` with collected parameters and yields results. ResultProxy doesn't know how to convert data returned by :meth:`surf.store.Store.get_by` into :class:`surf.resource.Resource`, `URIRef` and `Literal` objects. It delegates this task to `instancemaker` function. """ def __init__(self, params = {}, store = None, instancemaker = None): self.__params = params self.__get_by_response = None if store: self.__params["store"] = store if instancemaker: self.__params["instancemaker"] = instancemaker
[docs] def instancemaker(self, instancemaker_function): """ Specify the function for converting triples into instances. ``instancemaker_function`` function can also be specified as argument to constructor when instantiating :class:`ResultProxy`. ``instancemaker_function`` will be executed whenever :class:`ResultProxy` needs to return a resource. It has to accept two arguments: ``params`` and ``instance_data``. ``params`` will be a dictionary containing query parameters gathered by :class:`ResultProxy`. Information from ``params`` can be used by ``instancemaker_function``, for example, to decide what context should be set for created instances. ``instance_data`` will be a dictionary containing keys `direct` and `inverse`. These keys map to dictionaries describing direct and inverse attributes respectively. """ params = self.__params.copy() params["instancemaker"] = instancemaker_function return ResultProxy(params)
[docs] def limit(self, value): """ Set the limit for returned result count. """ params = self.__params.copy() params["limit"] = value return ResultProxy(params)
[docs] def offset(self, value): """ Set the limit for returned results. """ params = self.__params.copy() params["offset"] = value return ResultProxy(params)
[docs] def full(self, only_direct = False): """ Enable eager-loading of resource attributes. If ``full`` is set to `True`, returned resources will have attributes already loaded. Whether setting this will bring performance improvements depends on reader plugin implementation. For example, sparql_protocol plugin is capable of using SPARQL subqueries to fully load multiple resources in one request. """ params = self.__params.copy() params["full"] = True params["only_direct"] = only_direct return ResultProxy(params)
[docs] def order(self, value = True): """ Request results to be ordered. If no arguments are specified, resources will be ordered by their subject URIs. If ``value`` is set to an URIRef, corresponding attribute will be used for sorting. For example, sorting persons by surname:: FoafPerson = session.get_class(surf.ns.FOAF.Person) for person in FoafPerson.all().order(surf.ns.FOAF.surname): print person.foaf_name.first, person.foaf_surname.first Currently only one sorting key is supported. """ params = self.__params.copy() params["order"] = value return ResultProxy(params)
[docs] def desc(self): """ Set sorting order to descending. """ params = self.__params.copy() params["desc"] = True return ResultProxy(params)
[docs] def get_by(self, **kwargs): """ Add filter conditions. Arguments are expected in form:: foaf_name = "John" Multiple arguments are supported. An example that retrieves all persons named "John Smith":: FoafPerson = session.get_class(surf.ns.FOAF.Person) for person in FoafPerson.get_by(foaf_name = "John", foaf_surname = "Smith"): print person.subject """ params = self.__params.copy() # Don't overwrite existing get_by parameters, just append new ones. # Overwriting get_by params would cause resource.some_attr.get_by() # to work incorrectly. params.setdefault("get_by", []) for name, value in kwargs.items(): attr, direct = attr2rdf(name) if hasattr(value, "subject"): # If value has a subject attribute, this must be a Resource, # take its subject. value = value.subject elif hasattr(value, "__iter__"): # Map alternatives value = map(lambda val: hasattr(val, "subject") and val.subject or value_to_rdf(val), value) else: value = value_to_rdf(value) params["get_by"].append((attr, value, direct)) return ResultProxy(params)
[docs] def filter(self, **kwargs): """ Add filter conditions. Expects arguments in form:: ns_predicate = "(%s > 15)" ``ns_predicate`` specifies which predicate will be used for filtering, a query variable will be bound to it. `%s` is a placeholder for this variable. Filter expression (in example: "(%s > 15)") must follow SPARQL specification, on execution "%s" will be substituted with variable and the resulting string will be placed in query as-is. Because of string substitution percent signs need to be escaped. For example:: Person.all().filter(foaf_name = "(%s LIKE 'J%%')") This Virtuoso-specific filter is intended to select persons with names starting with "J". In generated query it will look like this:: ... ?s <http://xmlns.com/foaf/0.1/name> ?f1 . FILTER (?f1 LIKE 'J%') ... """ params = self.__params.copy() params.setdefault("filter", []) for name, value in kwargs.items(): attr, direct = attr2rdf(name) assert direct, "Only direct attributes can be used for filters" # Assume by plain strings user means literals if type(value) in [str, unicode]: value = Literal(value) params["filter"].append((attr, value, direct)) return ResultProxy(params)
[docs] def context(self, context): """ Specify context/graph that resources should be loaded from. """ params = self.__params.copy() params["context"] = context return ResultProxy(params)
def __execute_get_by(self): if self.__get_by_response is None: self.__get_by_args = {} for key in ["limit", "offset", "full", "order", "desc", "get_by", "only_direct", "context", "filter"]: if key in self.__params: self.__get_by_args[key] = self.__params[key] store = self.__params["store"] self.__get_by_response = store.get_by(self.__get_by_args) return self.__get_by_args, self.__get_by_response def __iterator(self): get_by_args, get_by_response = self.__execute_get_by() instancemaker = self.__params["instancemaker"] for instance_data in get_by_response: yield instancemaker(get_by_args, instance_data) def __iter__(self): """ Return iterator over resources in this collection. """ return self.__iterator() def __len__(self): """ Return count of resources in this collection. """ _, get_by_response = self.__execute_get_by() return len(get_by_response)
[docs] def first(self): """ Return first resource or None if there aren't any. """ item = None try: item = iter(self).next() except StopIteration: pass return item
[docs] def one(self): """ Return the only resource or raise if resource count != 1. If the query matches no resources, this method will raise :class:`surf.exc.NoResultFound` exception. If the query matches more than one resource, this method will raise :class:`surf.exc.MultipleResultsFound` exception. """ iterator = iter(self) try: item = iterator.next() except StopIteration: raise NoResultFound("List is empty") try: iterator.next() except StopIteration: # As expected, return item return item raise MultipleResultsFound("List has more than one item")