Source code for everest.url
"""
URL <-> resource conversion.
This file is part of the everest project.
See LICENSE.txt for licensing, CONTRIBUTORS.txt for contributor information.
Created on Jun 28, 2011.
"""
from cgi import parse_qsl
from everest.interfaces import IResourceUrlConverter
from everest.querying.base import EXPRESSION_KINDS
from everest.querying.filterparser import parse_filter
from everest.querying.interfaces import IFilterSpecificationBuilder
from everest.querying.interfaces import IFilterSpecificationDirector
from everest.querying.interfaces import IOrderSpecificationBuilder
from everest.querying.interfaces import IOrderSpecificationDirector
from everest.querying.orderparser import parse_order
from everest.querying.utils import get_filter_specification_factory
from everest.querying.utils import get_order_specification_factory
from everest.resources.interfaces import ICollectionResource
from everest.resources.interfaces import IMemberResource
from everest.utils import get_filter_specification_visitor
from everest.utils import get_order_specification_visitor
from pyramid.threadlocal import get_current_registry
from pyramid.threadlocal import get_current_request
from pyramid.traversal import find_resource
from pyramid.traversal import traversal_path
from pyramid.url import model_url
from urllib import unquote
from urlparse import urlparse
from zope.interface import implements # pylint: disable=E0611,F0401
from zope.interface import providedBy as provided_by # pylint: disable=E0611,F0401
__docformat__ = 'reStructuredText en'
__all__ = ['ResourceUrlConverter',
'UrlPartsConverter',
'resource_to_url',
'url_to_resource',
]
[docs]class ResourceUrlConverter(object):
"""
Performs URL <-> resource instance conversion.
See http://en.wikipedia.org/wiki/Query_string for information on characters
supported in query strings.
"""
implements(IResourceUrlConverter)
def __init__(self, request):
# The request is needed for access to app URL, registry, traversal.
self.__request = request
[docs] def url_to_resource(self, url):
"""
Converts the given url into a resource.
:param str url: URL to convert
:return: member or collection resource
::note : If the query string in the URL has multiple values for a
query parameter, the last definition in the query string wins.
"""
parsed = urlparse(url)
parsed_path = parsed.path # namedtupble problem pylint: disable=E1101
rc = find_resource(self.__request.root, traversal_path(parsed_path))
if ICollectionResource in provided_by(rc):
# In case we found a collection, we have to filter, order, slice.
parsed_query = parsed.query # namedtupble problem pylint: disable=E1101
params = dict(parse_qsl(parsed_query))
filter_string = params.get('q')
if not filter_string is None:
rc.filter = \
UrlPartsConverter.make_filter_specification(filter_string)
order_string = params.get('sort')
if not order_string is None:
rc.order = \
UrlPartsConverter.make_order_specification(order_string)
start_string = params.get('start')
size_string = params.get('size')
if not (start_string is None or size_string is None):
rc.slice = \
UrlPartsConverter.make_slice_key(start_string, size_string)
elif not IMemberResource in provided_by(rc):
raise ValueError('Traversal found non-resource object "%s".' % rc)
return rc
def resource_to_url(self, resource, **kw):
if ICollectionResource in provided_by(resource):
query = {}
query.update(kw)
if not resource.filter is None:
query['q'] = \
UrlPartsConverter.make_filter_string(resource.filter)
if not resource.order is None:
query['sort'] = \
UrlPartsConverter.make_order_string(resource.order)
if not resource.slice is None:
query['start'], query['size'] = \
UrlPartsConverter.make_slice_strings(resource.slice)
if query != {}:
url = model_url(resource, self.__request, query=query)
else:
url = model_url(resource, self.__request)
elif not IMemberResource in provided_by(resource):
raise ValueError('Can not convert non-resource object "%s to '
'URL".' % resource)
else:
if resource.__parent__ is None:
raise ValueError('Can not generate URL for floating member '
'"%s".' % resource)
url = model_url(resource, self.__request)
return unquote(url)
def resource_to_url(resource, request=None):
if request is None:
request = get_current_request()
# cnv = request.registry.getAdapter(request, IResourceUrlConverter)
reg = get_current_registry()
cnv = reg.getAdapter(request, IResourceUrlConverter)
return cnv.resource_to_url(resource)
def url_to_resource(url, request=None):
if request is None:
request = get_current_request()
# cnv = request.registry.getAdapter(request, IResourceUrlConverter)
reg = get_current_registry()
cnv = reg.getAdapter(request, IResourceUrlConverter)
return cnv.url_to_resource(url)
class UrlPartsConverter(object):
@classmethod
def make_filter_specification(cls, filter_string):
"""
Extracts the "query" parameter from the given request and converts
the given query string into a filter specification.
"""
reg = get_current_registry()
spec_factory = get_filter_specification_factory()
builder_cls = reg.getUtility(IFilterSpecificationBuilder)
builder = builder_cls(spec_factory)
director_cls = reg.getUtility(IFilterSpecificationDirector)
director = director_cls(parse_filter, builder)
director.construct(unquote(filter_string))
if director.has_errors():
errors = '\n'.join(director.get_errors())
raise ValueError(errors)
return builder.specification
@classmethod
def make_filter_string(cls, filter_specification):
visitor_cls = get_filter_specification_visitor(EXPRESSION_KINDS.CQL)
visitor = visitor_cls()
filter_specification.accept(visitor)
return str(visitor.expression)
@classmethod
def make_order_specification(cls, order_string):
reg = get_current_registry()
order_factory = get_order_specification_factory()
builder_cls = reg.getUtility(IOrderSpecificationBuilder)
builder = builder_cls(order_factory)
director_cls = reg.getUtility(IOrderSpecificationDirector)
director = director_cls(parse_order, builder)
director.construct(unquote(order_string))
if director.has_errors():
errors = '\n'.join(director.get_errors())
raise ValueError(errors)
return builder.specification
@classmethod
def make_order_string(cls, order_specification):
visitor_cls = get_order_specification_visitor(EXPRESSION_KINDS.CQL)
visitor = visitor_cls()
order_specification.accept(visitor)
return str(visitor.expression)
@classmethod
def make_slice_key(cls, start_string, size_string):
"""
Extracts the "start" and "size" parameters from the given
start and size parameter strings and constructs a slice from it.
:return: slice key
:rtype: slice
"""
try:
start = int(start_string)
except ValueError, err:
err.message = 'Query parameter "start" must be a number.'
raise err
if start < 0:
raise ValueError('Query parameter "start" must be zero or '
'a positive number.')
try:
size = int(size_string)
except ValueError, err:
err.message = 'Query parameter "size" must be a number.'
raise err
if size < 1:
raise ValueError('Query parameter "size" must be a positive '
'number.')
return slice(start, start + size)
@classmethod
def make_slice_strings(cls, slice_key):
start = slice_key.start
size = slice_key.stop - start
return (str(start), str(size))