'''
Client for REST APIs.
'''

import re
from syndicate import data as m_data
from syndicate.adapters import sync as m_sync, async as m_async


class ServiceError(Exception):
    pass


class ResponseError(ServiceError):

    def __init__(self, response):
        self.response = response

    def __str__(self):
        return '%s(%s)' % (type(self).__name__, self.response)


class Service(object):
    """ A stateful connection to a service. """

    default_page_size = 100
    urlpartition = re.compile('([?;])')

    @staticmethod
    def default_data_getter(response):
        if response.error:
            raise response.error
        content = response.content
        if not content:
            return None
        if not content['success']:
            raise ResponseError(content)
        return content.get('data')

    @staticmethod
    def default_meta_getter(response):
        content = response.content
        if not content:
            return None
        return content.get('meta')

    def __init__(self, uri=None, urn='', auth=None, serializer='json',
                 data_getter=None, meta_getter=None, trailing_slash=True,
                 async=False, **adapter_config):
        if not uri:
            raise TypeError("Required: uri")
        self.closed = False
        self.async = async
        self.auth = auth
        self.filters = []
        self.trailing_slash = trailing_slash
        self.uri = uri
        self.urn = urn
        self.data_getter = data_getter or self.default_data_getter
        self.meta_getter = meta_getter or self.default_meta_getter
        if hasattr(serializer, 'mime'):
            self.serializer = serializer
        else:
            self.serializer = m_data.serializers[serializer]
        self.adapter = self.make_adapter(ingress_filter=self.ingress_filter,
                                         serializer=self.serializer,
                                         auth=self.auth, async=async,
                                         **adapter_config)

    def make_adapter(self, async=False, **config):
        Adapter = m_async.AsyncAdapter if async else m_sync.SyncAdapter
        a = Adapter(**config)
        a.set_header('accept', self.serializer.mime)
        a.set_header('content-type', self.serializer.mime)
        return a

    def ingress_filter(self, response):
        """ Flatten a response with meta and data keys into an object. """
        data = self.data_getter(response)
        if isinstance(data, dict):
            data = m_data.DictResponse(data)
        elif isinstance(data, list):
            data = m_data.ListResponse(data)
        else:
            return data
        data.meta = self.meta_getter(response)
        return data

    def do(self, method, path, urn=None, callback=None, data=None,
           timeout=None, **query):
        urlparts = [self.uri, self.urn if urn is None else urn]
        urlparts.extend(path)
        url = '/'.join(filter(None, (x.strip('/') for x in urlparts)))
        if self.trailing_slash:
            parts = self.urlpartition.split(url, 1)
            if not parts[0].endswith('/'):
                parts[0] += '/'
                url = ''.join(parts)
        return self.adapter.request(method, url, callback=callback, data=data,
                                    query=query, timeout=timeout)

    def get(self, *path, **kwargs):
        return self.do('get', path, **kwargs)

    def get_pager(self, *path, **kwargs):
        """ A generator for all the results a resource can provide. The pages
        are lazily loaded. """
        page_arg = kwargs.pop('page_size', None)
        limit_arg = kwargs.pop('limit', None)
        kwargs['limit'] = page_arg or limit_arg or self.default_page_size
        return self.adapter.get_pager(self.get, path, kwargs)

    def post(self, *path_and_data, **kwargs):
        path = list(path_and_data)
        data = path.pop(-1)
        return self.do('post', path, data=data, **kwargs)

    def delete(self, *path, **kwargs):
        data = kwargs.pop('data', None)
        return self.do('delete', path, data=data, **kwargs)

    def put(self, *path_and_data, **kwargs):
        path = list(path_and_data)
        data = path.pop(-1)
        return self.do('put', path, data=data, **kwargs)

    def patch(self, *path_and_data, **kwargs):
        path = list(path_and_data)
        data = path.pop(-1)
        return self.do('patch', path, data=data, **kwargs)

    def close(self):
        if self.closed:
            return
        if self.adapter is not None:
            self.adapter.close()
            self.adapter = None
        self.closed = True

@@ -24,16 +24,35 @@
 s.%0A'''%0A%0A
+import collections%0A
 import r
@@ -3212,16 +3212,160 @@
 (parts)%0A
+        norm_query = collections.OrderedDict(sorted((k, str(v))%0A                                                    for k, v in query.items()))%0A
         
@@ -3477,16 +3477,21 @@
   query=
+norm_
 query, t

Fix for new versions of yarl.