| import types |
| import functools |
| |
| from pip._vendor.requests.adapters import HTTPAdapter |
| |
| from .controller import CacheController |
| from .cache import DictCache |
| from .filewrapper import CallbackFileWrapper |
| |
| |
| class CacheControlAdapter(HTTPAdapter): |
| invalidating_methods = set(['PUT', 'DELETE']) |
| |
| def __init__(self, cache=None, |
| cache_etags=True, |
| controller_class=None, |
| serializer=None, |
| heuristic=None, |
| *args, **kw): |
| super(CacheControlAdapter, self).__init__(*args, **kw) |
| self.cache = cache or DictCache() |
| self.heuristic = heuristic |
| |
| controller_factory = controller_class or CacheController |
| self.controller = controller_factory( |
| self.cache, |
| cache_etags=cache_etags, |
| serializer=serializer, |
| ) |
| |
| def send(self, request, **kw): |
| """ |
| Send a request. Use the request information to see if it |
| exists in the cache and cache the response if we need to and can. |
| """ |
| if request.method == 'GET': |
| cached_response = self.controller.cached_request(request) |
| if cached_response: |
| return self.build_response(request, cached_response, |
| from_cache=True) |
| |
| # check for etags and add headers if appropriate |
| request.headers.update( |
| self.controller.conditional_headers(request) |
| ) |
| |
| resp = super(CacheControlAdapter, self).send(request, **kw) |
| |
| return resp |
| |
| def build_response(self, request, response, from_cache=False): |
| """ |
| Build a response by making a request or using the cache. |
| |
| This will end up calling send and returning a potentially |
| cached response |
| """ |
| if not from_cache and request.method == 'GET': |
| # Check for any heuristics that might update headers |
| # before trying to cache. |
| if self.heuristic: |
| response = self.heuristic.apply(response) |
| |
| # apply any expiration heuristics |
| if response.status == 304: |
| # We must have sent an ETag request. This could mean |
| # that we've been expired already or that we simply |
| # have an etag. In either case, we want to try and |
| # update the cache if that is the case. |
| cached_response = self.controller.update_cached_response( |
| request, response |
| ) |
| |
| if cached_response is not response: |
| from_cache = True |
| |
| # We are done with the server response, read a |
| # possible response body (compliant servers will |
| # not return one, but we cannot be 100% sure) and |
| # release the connection back to the pool. |
| response.read(decode_content=False) |
| response.release_conn() |
| |
| response = cached_response |
| |
| # We always cache the 301 responses |
| elif response.status == 301: |
| self.controller.cache_response(request, response) |
| else: |
| # Wrap the response file with a wrapper that will cache the |
| # response when the stream has been consumed. |
| response._fp = CallbackFileWrapper( |
| response._fp, |
| functools.partial( |
| self.controller.cache_response, |
| request, |
| response, |
| ) |
| ) |
| if response.chunked: |
| super_update_chunk_length = response._update_chunk_length |
| |
| def _update_chunk_length(self): |
| super_update_chunk_length() |
| if self.chunk_left == 0: |
| self._fp._close() |
| response._update_chunk_length = types.MethodType(_update_chunk_length, response) |
| |
| resp = super(CacheControlAdapter, self).build_response( |
| request, response |
| ) |
| |
| # See if we should invalidate the cache. |
| if request.method in self.invalidating_methods and resp.ok: |
| cache_url = self.controller.cache_url(request.url) |
| self.cache.delete(cache_url) |
| |
| # Give the request a from_cache attr to let people use it |
| resp.from_cache = from_cache |
| |
| return resp |
| |
| def close(self): |
| self.cache.close() |
| super(CacheControlAdapter, self).close() |