Source code for surf.resource.result_proxy

""" Module for ResultProxy. """

from surf.exceptions import NoResultFound, MultipleResultsFound
from surf.log import deprecation
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 `instance_factory` function. """ def __init__(self, params = None, store = None, instance_factory = None): self.__params = params if params else dict() self.__get_by_response = None if store: self.__params["store"] = store if instance_factory: self.__params["instance_factory"] = instance_factory
[docs] def instance_factory(self, instance_factory_func): """ Specify the function for converting triples into instances. ``instance_factory_func`` function can also be specified as argument to constructor when instantiating :class:`ResultProxy`. ``instance_factory_func`` 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 ``instance_factory_func``, 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["instance_factory"] = instance_factory_func 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, direct_only = False, **kwargs): """ Enable eager-loading of resource attributes. With this modifier, resources will have their attributes already loaded. If ``only_direct`` is set to `True`, only direct attributes will be loaded. Accessing inverse attributes will work but will generate extra requests to triple store. 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. """ #TODO: -------------------[remove in v1.2.0]------------------------ if 'only_direct' in kwargs: deprecation('the only_direct argument is deprecated and will be removed in version 1.2.0, use direct_only instead!') direct_only = kwargs['only_direct'] #TODO: -------------------[remove in v1.2.0]------------------------ params = self.__params.copy() params['full'] = True params['direct_only'] = direct_only 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', 'direct_only', '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() instance_factory = self.__params['instance_factory'] for instance_data in get_by_response: yield instance_factory(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")