adapter.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import functools
  2. from pip._vendor.requests.adapters import HTTPAdapter
  3. from .controller import CacheController
  4. from .cache import DictCache
  5. from .filewrapper import CallbackFileWrapper
  6. class CacheControlAdapter(HTTPAdapter):
  7. invalidating_methods = set(['PUT', 'DELETE'])
  8. def __init__(self, cache=None,
  9. cache_etags=True,
  10. controller_class=None,
  11. serializer=None,
  12. heuristic=None,
  13. *args, **kw):
  14. super(CacheControlAdapter, self).__init__(*args, **kw)
  15. self.cache = cache or DictCache()
  16. self.heuristic = heuristic
  17. controller_factory = controller_class or CacheController
  18. self.controller = controller_factory(
  19. self.cache,
  20. cache_etags=cache_etags,
  21. serializer=serializer,
  22. )
  23. def send(self, request, **kw):
  24. """
  25. Send a request. Use the request information to see if it
  26. exists in the cache and cache the response if we need to and can.
  27. """
  28. if request.method == 'GET':
  29. cached_response = self.controller.cached_request(request)
  30. if cached_response:
  31. return self.build_response(request, cached_response,
  32. from_cache=True)
  33. # check for etags and add headers if appropriate
  34. request.headers.update(
  35. self.controller.conditional_headers(request)
  36. )
  37. resp = super(CacheControlAdapter, self).send(request, **kw)
  38. return resp
  39. def build_response(self, request, response, from_cache=False):
  40. """
  41. Build a response by making a request or using the cache.
  42. This will end up calling send and returning a potentially
  43. cached response
  44. """
  45. if not from_cache and request.method == 'GET':
  46. # apply any expiration heuristics
  47. if response.status == 304:
  48. # We must have sent an ETag request. This could mean
  49. # that we've been expired already or that we simply
  50. # have an etag. In either case, we want to try and
  51. # update the cache if that is the case.
  52. cached_response = self.controller.update_cached_response(
  53. request, response
  54. )
  55. if cached_response is not response:
  56. from_cache = True
  57. # We are done with the server response, read a
  58. # possible response body (compliant servers will
  59. # not return one, but we cannot be 100% sure) and
  60. # release the connection back to the pool.
  61. response.read(decode_content=False)
  62. response.release_conn()
  63. response = cached_response
  64. # We always cache the 301 responses
  65. elif response.status == 301:
  66. self.controller.cache_response(request, response)
  67. else:
  68. # Check for any heuristics that might update headers
  69. # before trying to cache.
  70. if self.heuristic:
  71. response = self.heuristic.apply(response)
  72. # Wrap the response file with a wrapper that will cache the
  73. # response when the stream has been consumed.
  74. response._fp = CallbackFileWrapper(
  75. response._fp,
  76. functools.partial(
  77. self.controller.cache_response,
  78. request,
  79. response,
  80. )
  81. )
  82. resp = super(CacheControlAdapter, self).build_response(
  83. request, response
  84. )
  85. # See if we should invalidate the cache.
  86. if request.method in self.invalidating_methods and resp.ok:
  87. cache_url = self.controller.cache_url(request.url)
  88. self.cache.delete(cache_url)
  89. # Give the request a from_cache attr to let people use it
  90. resp.from_cache = from_cache
  91. return resp
  92. def close(self):
  93. self.cache.close()
  94. super(CacheControlAdapter, self).close()