# Path: api/cache.py
# def get_cache_key(name: str, kwargs: dict):
#     key_parts = [name]
#     if kwargs:
#         key_parts.append(KWARG_MARK)
#         key_parts.extend(sorted(kwargs.items()))
#     return str(hash(tuple(key_parts)))


from http import HTTPStatus
from werkzeug.contrib.cache import BaseCache
from api.cache import get_cache_key
import logging
import aiohttp
        })

        if self.cache:
            etag = (self.cache.get(cache_key) or {}).get('etag')
            if etag:
                kwargs['headers']['If-None-Match'] = etag

        full_url = '{API_URL}/{url}'.format(API_URL=API_URL, url=url)
        async with aiohttp.request(method.upper(), full_url, **kwargs) as resp:
            if resp.status >= 400:
                json = await resp.json()
                raise GitHubClientError(json, resp)
            else:
                if self.cache and resp.status == HTTPStatus.NOT_MODIFIED:
                    logger.info('Cache HIT for {}; Etag: {}'.format(url, etag))
                    return self.cache.get(cache_key)['response']
                else:
                    json = await resp.json()
                    new_etag = resp.headers.get('ETag')
                    logger.info('Cache MISS for {}'.format(url))
                    if new_etag and self.cache:
                        cache_payload = {
                            'etag': new_etag,
                            'response': json
                        }
                        logger.info('Caching etag and response: {}'.format(new_etag))
                        self.cache.set(cache_key, cache_payload)
                    return json

    async def user_repos(self, username: str, sort: str='pushed'):

# Path: api/cache.py
# def get_cache_key(name: str, kwargs: dict):
#     key_parts = [name]
#     if kwargs:
#         key_parts.append(KWARG_MARK)
#         key_parts.extend(sorted(kwargs.items()))
#     return str(hash(tuple(key_parts)))


# Path: api/github/client.py
from http import HTTPStatus
from werkzeug.contrib.cache import BaseCache
from api.cache import get_cache_key
import logging
import aiohttp

        })

        if self.cache:
            etag = (self.cache.get(cache_key) or {}).get('etag')
            if etag:
                kwargs['headers']['If-None-Match'] = etag

        full_url = '{API_URL}/{url}'.format(API_URL=API_URL, url=url)
        async with aiohttp.request(method.upper(), full_url, **kwargs) as resp:
            if resp.status >= 400:
                json = await resp.json()
                raise GitHubClientError(json, resp)
            else:
                if self.cache and resp.status == HTTPStatus.NOT_MODIFIED:
                    logger.info('Cache HIT for {}; Etag: {}'.format(url, etag))
                    return self.cache.get(cache_key)['response']
                else:
                    json = await resp.json()
                    new_etag = resp.headers.get('ETag')
                    logger.info('Cache MISS for {}'.format(url))
                    if new_etag and self.cache:
                        cache_payload = {
                            'etag': new_etag,
                            'response': json
                        }
                        logger.info('Caching etag and response: {}'.format(new_etag))
                        self.cache.set(cache_key, cache_payload)
                    return json

    async def user_repos(self, username: str, sort: str='pushed'):

        cache_key = get_cache_key('user_repos', {