We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
set user-agent to something pex-identifiable
Donald did some interesting research into where downloads were coming from on PyPI.  Realized that we have no custom user-agent to identify pex.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pex/http.py`
Content:
```
1 # Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
2 # Licensed under the Apache License, Version 2.0 (see LICENSE).
3 
4 import contextlib
5 import hashlib
6 import os
7 import shutil
8 import uuid
9 from abc import abstractmethod
10 from email import message_from_string
11 
12 from .common import safe_mkdtemp, safe_open
13 from .compatibility import PY3, AbstractClass
14 from .tracer import TRACER
15 from .variables import ENV
16 
17 try:
18   import requests
19 except ImportError:
20   requests = None
21 
22 try:
23   from cachecontrol import CacheControl
24   from cachecontrol.caches import FileCache
25 except ImportError:
26   CacheControl = FileCache = None
27 
28 if PY3:
29   import urllib.request as urllib_request
30 else:
31   import urllib2 as urllib_request
32 
33 # This is available as hashlib.algorithms_guaranteed in >=3.2 and as
34 # hashlib.algorithms in >=2.7, but not available in 2.6, so we enumerate
35 # here.
36 HASHLIB_ALGORITHMS = frozenset(['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'])
37 
38 
39 class Context(AbstractClass):
40   """Encapsulate the networking necessary to do requirement resolution.
41 
42   At a minimum, the Context must implement ``open(link)`` by returning a
43   file-like object.  Reference implementations of ``read(link)`` and
44   ``fetch(link)`` are provided based upon ``open(link)`` but may be further
45   specialized by individual implementations.
46   """
47 
48   DEFAULT_ENCODING = 'iso-8859-1'
49 
50   class Error(Exception):
51     """Error base class for Contexts to wrap application-specific exceptions."""
52     pass
53 
54   _REGISTRY = []
55 
56   @classmethod
57   def register(cls, context_impl):
58     """Register a concrete implementation of a :class:`Context` to be recognized."""
59     cls._REGISTRY.insert(0, context_impl)
60 
61   @classmethod
62   def get(cls):
63     for context_class in cls._REGISTRY:
64       try:
65         context = context_class()
66         TRACER.log('Constructed %s context %r' % (context.__class__.__name__, context), V=4)
67         return context
68       except cls.Error:
69         continue
70     raise cls.Error('Could not initialize a request context.')
71 
72   @abstractmethod
73   def open(self, link):
74     """Return an open file-like object to the link.
75 
76     :param link: The :class:`Link` to open.
77     """
78 
79   def read(self, link):
80     """Return the binary content associated with the link.
81 
82     :param link: The :class:`Link` to read.
83     """
84     with contextlib.closing(self.open(link)) as fp:
85       return fp.read()
86 
87   def content(self, link):
88     """Return the encoded content associated with the link.
89 
90     :param link: The :class:`Link` to read.
91     """
92 
93   def fetch(self, link, into=None):
94     """Fetch the binary content associated with the link and write to a file.
95 
96     :param link: The :class:`Link` to fetch.
97     :keyword into: If specified, write into the directory ``into``.  If ``None``, creates a new
98       temporary directory that persists for the duration of the interpreter.
99     """
100     target = os.path.join(into or safe_mkdtemp(), link.filename)
101 
102     if os.path.exists(target):
103       # Assume that if the local file already exists, it is safe to use.
104       return target
105 
106     with TRACER.timed('Fetching %s' % link.url, V=2):
107       target_tmp = '%s.%s' % (target, uuid.uuid4())
108       with contextlib.closing(self.open(link)) as in_fp:
109         with safe_open(target_tmp, 'wb') as out_fp:
110           shutil.copyfileobj(in_fp, out_fp)
111 
112     os.rename(target_tmp, target)
113     return target
114 
115 
116 class UrllibContext(Context):
117   """Default Python standard library Context."""
118 
119   def open(self, link):
120     return urllib_request.urlopen(link.url)
121 
122   def content(self, link):
123     if link.local:
124       raise self.Error('Context.content only works with remote URLs.')
125 
126     with contextlib.closing(self.open(link)) as fp:
127       encoding = message_from_string(str(fp.headers)).get_content_charset(self.DEFAULT_ENCODING)
128       return fp.read().decode(encoding, 'replace')
129 
130   def __init__(self, *args, **kw):
131     TRACER.log('Warning, using a UrllibContext which is known to be flaky.')
132     TRACER.log('Please build pex with the requests module for more reliable downloads.')
133     super(UrllibContext, self).__init__(*args, **kw)
134 
135 
136 Context.register(UrllibContext)
137 
138 
139 class StreamFilelike(object):
140   """A file-like object wrapper around requests streams that performs hash validation."""
141 
142   @classmethod
143   def detect_algorithm(cls, link):
144     """Detect the hashing algorithm from the fragment in the link, if any."""
145     if any(link.fragment.startswith('%s=' % algorithm) for algorithm in HASHLIB_ALGORITHMS):
146       algorithm, value = link.fragment.split('=', 2)
147       try:
148         return hashlib.new(algorithm), value
149       except ValueError:  # unsupported algorithm
150         return None, None
151     return None, None
152 
153   def __init__(self, request, link, chunk_size=16384):
154     self._iterator = request.iter_content(chunk_size)
155     self.encoding = request.encoding
156     self._bytes = b''
157     self._link = link
158     self._hasher, self._hash_value = self.detect_algorithm(link)
159 
160   def read(self, length=None):
161     while length is None or len(self._bytes) < length:
162       try:
163         next_chunk = next(self._iterator)
164         if self._hasher:
165           self._hasher.update(next_chunk)
166         self._bytes += next_chunk
167       except StopIteration:
168         self._validate()
169         break
170     if length is None:
171       length = len(self._bytes)
172     chunk, self._bytes = self._bytes[:length], self._bytes[length:]
173     return chunk
174 
175   def _validate(self):
176     if self._hasher:
177       if self._hash_value != self._hasher.hexdigest():
178         raise Context.Error('%s failed checksum!' % (self._link.url))
179       else:
180         TRACER.log('Validated %s (%s)' % (self._link.filename, self._link.fragment), V=3)
181 
182   def close(self):
183     pass
184 
185 
186 class RequestsContext(Context):
187   """A requests-based Context."""
188 
189   @staticmethod
190   def _create_session(max_retries):
191     session = requests.session()
192     retrying_adapter = requests.adapters.HTTPAdapter(max_retries=max_retries)
193     session.mount('http://', retrying_adapter)
194     session.mount('https://', retrying_adapter)
195 
196     return session
197 
198   def __init__(self, session=None, verify=True, env=ENV):
199     if requests is None:
200       raise RuntimeError('requests is not available.  Cannot use a RequestsContext.')
201 
202     self._verify = verify
203 
204     max_retries = env.PEX_HTTP_RETRIES
205     if max_retries < 0:
206       raise ValueError('max_retries may not be negative.')
207 
208     self._max_retries = max_retries
209     self._session = session or self._create_session(self._max_retries)
210 
211   def open(self, link):
212     # requests does not support file:// -- so we must short-circuit manually
213     if link.local:
214       return open(link.path, 'rb')  # noqa: T802
215     for attempt in range(self._max_retries + 1):
216       try:
217         return StreamFilelike(self._session.get(link.url, verify=self._verify, stream=True), link)
218       except requests.exceptions.ReadTimeout:
219         # Connect timeouts are handled by the HTTPAdapter, unfortunately read timeouts are not
220         # so we'll retry them ourselves.
221         TRACER.log('Read timeout trying to fetch %s, retrying. %d retries remain.' % (
222             link.url,
223             self._max_retries - attempt))
224       except requests.exceptions.RequestException as e:
225         raise self.Error(e)
226 
227     raise self.Error(
228         requests.packages.urllib3.exceptions.MaxRetryError(
229             None,
230             link,
231             'Exceeded max retries of %d' % self._max_retries))
232 
233   def content(self, link):
234     if link.local:
235       raise self.Error('Context.content only works with remote URLs.')
236 
237     with contextlib.closing(self.open(link)) as request:
238       return request.read().decode(request.encoding or self.DEFAULT_ENCODING, 'replace')
239 
240 
241 if requests:
242   Context.register(RequestsContext)
243 
244 
245 class CachedRequestsContext(RequestsContext):
246   """A requests-based Context with CacheControl support."""
247 
248   DEFAULT_CACHE = '~/.pex/cache'
249 
250   def __init__(self, cache=None, **kw):
251     self._cache = os.path.realpath(os.path.expanduser(cache or self.DEFAULT_CACHE))
252     super(CachedRequestsContext, self).__init__(
253         CacheControl(requests.session(), cache=FileCache(self._cache)), **kw)
254 
255 
256 if CacheControl:
257   Context.register(CachedRequestsContext)
258
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pex/http.py b/pex/http.py
--- a/pex/http.py
+++ b/pex/http.py
@@ -13,6 +13,7 @@
 from .compatibility import PY3, AbstractClass
 from .tracer import TRACER
 from .variables import ENV
+from .version import __version__ as PEX_VERSION
 
 try:
   import requests
@@ -185,6 +186,7 @@
 
 class RequestsContext(Context):
   """A requests-based Context."""
+  USER_AGENT = 'pex/%s' % PEX_VERSION
 
   @staticmethod
   def _create_session(max_retries):
@@ -192,7 +194,6 @@
     retrying_adapter = requests.adapters.HTTPAdapter(max_retries=max_retries)
     session.mount('http://', retrying_adapter)
     session.mount('https://', retrying_adapter)
-
     return session
 
   def __init__(self, session=None, verify=True, env=ENV):
@@ -214,7 +215,9 @@
       return open(link.path, 'rb')  # noqa: T802
     for attempt in range(self._max_retries + 1):
       try:
-        return StreamFilelike(self._session.get(link.url, verify=self._verify, stream=True), link)
+        return StreamFilelike(self._session.get(
+            link.url, verify=self._verify, stream=True, headers={'User-Agent': self.USER_AGENT}),
+            link)
       except requests.exceptions.ReadTimeout:
         # Connect timeouts are handled by the HTTPAdapter, unfortunately read timeouts are not
         # so we'll retry them ourselves.

{"golden_diff": "diff --git a/pex/http.py b/pex/http.py\n--- a/pex/http.py\n+++ b/pex/http.py\n@@ -13,6 +13,7 @@\n from .compatibility import PY3, AbstractClass\n from .tracer import TRACER\n from .variables import ENV\n+from .version import __version__ as PEX_VERSION\n \n try:\n   import requests\n@@ -185,6 +186,7 @@\n \n class RequestsContext(Context):\n   \"\"\"A requests-based Context.\"\"\"\n+  USER_AGENT = 'pex/%s' % PEX_VERSION\n \n   @staticmethod\n   def _create_session(max_retries):\n@@ -192,7 +194,6 @@\n     retrying_adapter = requests.adapters.HTTPAdapter(max_retries=max_retries)\n     session.mount('http://', retrying_adapter)\n     session.mount('https://', retrying_adapter)\n-\n     return session\n \n   def __init__(self, session=None, verify=True, env=ENV):\n@@ -214,7 +215,9 @@\n       return open(link.path, 'rb')  # noqa: T802\n     for attempt in range(self._max_retries + 1):\n       try:\n-        return StreamFilelike(self._session.get(link.url, verify=self._verify, stream=True), link)\n+        return StreamFilelike(self._session.get(\n+            link.url, verify=self._verify, stream=True, headers={'User-Agent': self.USER_AGENT}),\n+            link)\n       except requests.exceptions.ReadTimeout:\n         # Connect timeouts are handled by the HTTPAdapter, unfortunately read timeouts are not\n         # so we'll retry them ourselves.\n", "issue": "set user-agent to something pex-identifiable\nDonald did some interesting research into where downloads were coming from on PyPI.  Realized that we have no custom user-agent to identify pex.\n\n", "before_files": [{"content": "# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\nimport contextlib\nimport hashlib\nimport os\nimport shutil\nimport uuid\nfrom abc import abstractmethod\nfrom email import message_from_string\n\nfrom .common import safe_mkdtemp, safe_open\nfrom .compatibility import PY3, AbstractClass\nfrom .tracer import TRACER\nfrom .variables import ENV\n\ntry:\n  import requests\nexcept ImportError:\n  requests = None\n\ntry:\n  from cachecontrol import CacheControl\n  from cachecontrol.caches import FileCache\nexcept ImportError:\n  CacheControl = FileCache = None\n\nif PY3:\n  import urllib.request as urllib_request\nelse:\n  import urllib2 as urllib_request\n\n# This is available as hashlib.algorithms_guaranteed in >=3.2 and as\n# hashlib.algorithms in >=2.7, but not available in 2.6, so we enumerate\n# here.\nHASHLIB_ALGORITHMS = frozenset(['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'])\n\n\nclass Context(AbstractClass):\n  \"\"\"Encapsulate the networking necessary to do requirement resolution.\n\n  At a minimum, the Context must implement ``open(link)`` by returning a\n  file-like object.  Reference implementations of ``read(link)`` and\n  ``fetch(link)`` are provided based upon ``open(link)`` but may be further\n  specialized by individual implementations.\n  \"\"\"\n\n  DEFAULT_ENCODING = 'iso-8859-1'\n\n  class Error(Exception):\n    \"\"\"Error base class for Contexts to wrap application-specific exceptions.\"\"\"\n    pass\n\n  _REGISTRY = []\n\n  @classmethod\n  def register(cls, context_impl):\n    \"\"\"Register a concrete implementation of a :class:`Context` to be recognized.\"\"\"\n    cls._REGISTRY.insert(0, context_impl)\n\n  @classmethod\n  def get(cls):\n    for context_class in cls._REGISTRY:\n      try:\n        context = context_class()\n        TRACER.log('Constructed %s context %r' % (context.__class__.__name__, context), V=4)\n        return context\n      except cls.Error:\n        continue\n    raise cls.Error('Could not initialize a request context.')\n\n  @abstractmethod\n  def open(self, link):\n    \"\"\"Return an open file-like object to the link.\n\n    :param link: The :class:`Link` to open.\n    \"\"\"\n\n  def read(self, link):\n    \"\"\"Return the binary content associated with the link.\n\n    :param link: The :class:`Link` to read.\n    \"\"\"\n    with contextlib.closing(self.open(link)) as fp:\n      return fp.read()\n\n  def content(self, link):\n    \"\"\"Return the encoded content associated with the link.\n\n    :param link: The :class:`Link` to read.\n    \"\"\"\n\n  def fetch(self, link, into=None):\n    \"\"\"Fetch the binary content associated with the link and write to a file.\n\n    :param link: The :class:`Link` to fetch.\n    :keyword into: If specified, write into the directory ``into``.  If ``None``, creates a new\n      temporary directory that persists for the duration of the interpreter.\n    \"\"\"\n    target = os.path.join(into or safe_mkdtemp(), link.filename)\n\n    if os.path.exists(target):\n      # Assume that if the local file already exists, it is safe to use.\n      return target\n\n    with TRACER.timed('Fetching %s' % link.url, V=2):\n      target_tmp = '%s.%s' % (target, uuid.uuid4())\n      with contextlib.closing(self.open(link)) as in_fp:\n        with safe_open(target_tmp, 'wb') as out_fp:\n          shutil.copyfileobj(in_fp, out_fp)\n\n    os.rename(target_tmp, target)\n    return target\n\n\nclass UrllibContext(Context):\n  \"\"\"Default Python standard library Context.\"\"\"\n\n  def open(self, link):\n    return urllib_request.urlopen(link.url)\n\n  def content(self, link):\n    if link.local:\n      raise self.Error('Context.content only works with remote URLs.')\n\n    with contextlib.closing(self.open(link)) as fp:\n      encoding = message_from_string(str(fp.headers)).get_content_charset(self.DEFAULT_ENCODING)\n      return fp.read().decode(encoding, 'replace')\n\n  def __init__(self, *args, **kw):\n    TRACER.log('Warning, using a UrllibContext which is known to be flaky.')\n    TRACER.log('Please build pex with the requests module for more reliable downloads.')\n    super(UrllibContext, self).__init__(*args, **kw)\n\n\nContext.register(UrllibContext)\n\n\nclass StreamFilelike(object):\n  \"\"\"A file-like object wrapper around requests streams that performs hash validation.\"\"\"\n\n  @classmethod\n  def detect_algorithm(cls, link):\n    \"\"\"Detect the hashing algorithm from the fragment in the link, if any.\"\"\"\n    if any(link.fragment.startswith('%s=' % algorithm) for algorithm in HASHLIB_ALGORITHMS):\n      algorithm, value = link.fragment.split('=', 2)\n      try:\n        return hashlib.new(algorithm), value\n      except ValueError:  # unsupported algorithm\n        return None, None\n    return None, None\n\n  def __init__(self, request, link, chunk_size=16384):\n    self._iterator = request.iter_content(chunk_size)\n    self.encoding = request.encoding\n    self._bytes = b''\n    self._link = link\n    self._hasher, self._hash_value = self.detect_algorithm(link)\n\n  def read(self, length=None):\n    while length is None or len(self._bytes) < length:\n      try:\n        next_chunk = next(self._iterator)\n        if self._hasher:\n          self._hasher.update(next_chunk)\n        self._bytes += next_chunk\n      except StopIteration:\n        self._validate()\n        break\n    if length is None:\n      length = len(self._bytes)\n    chunk, self._bytes = self._bytes[:length], self._bytes[length:]\n    return chunk\n\n  def _validate(self):\n    if self._hasher:\n      if self._hash_value != self._hasher.hexdigest():\n        raise Context.Error('%s failed checksum!' % (self._link.url))\n      else:\n        TRACER.log('Validated %s (%s)' % (self._link.filename, self._link.fragment), V=3)\n\n  def close(self):\n    pass\n\n\nclass RequestsContext(Context):\n  \"\"\"A requests-based Context.\"\"\"\n\n  @staticmethod\n  def _create_session(max_retries):\n    session = requests.session()\n    retrying_adapter = requests.adapters.HTTPAdapter(max_retries=max_retries)\n    session.mount('http://', retrying_adapter)\n    session.mount('https://', retrying_adapter)\n\n    return session\n\n  def __init__(self, session=None, verify=True, env=ENV):\n    if requests is None:\n      raise RuntimeError('requests is not available.  Cannot use a RequestsContext.')\n\n    self._verify = verify\n\n    max_retries = env.PEX_HTTP_RETRIES\n    if max_retries < 0:\n      raise ValueError('max_retries may not be negative.')\n\n    self._max_retries = max_retries\n    self._session = session or self._create_session(self._max_retries)\n\n  def open(self, link):\n    # requests does not support file:// -- so we must short-circuit manually\n    if link.local:\n      return open(link.path, 'rb')  # noqa: T802\n    for attempt in range(self._max_retries + 1):\n      try:\n        return StreamFilelike(self._session.get(link.url, verify=self._verify, stream=True), link)\n      except requests.exceptions.ReadTimeout:\n        # Connect timeouts are handled by the HTTPAdapter, unfortunately read timeouts are not\n        # so we'll retry them ourselves.\n        TRACER.log('Read timeout trying to fetch %s, retrying. %d retries remain.' % (\n            link.url,\n            self._max_retries - attempt))\n      except requests.exceptions.RequestException as e:\n        raise self.Error(e)\n\n    raise self.Error(\n        requests.packages.urllib3.exceptions.MaxRetryError(\n            None,\n            link,\n            'Exceeded max retries of %d' % self._max_retries))\n\n  def content(self, link):\n    if link.local:\n      raise self.Error('Context.content only works with remote URLs.')\n\n    with contextlib.closing(self.open(link)) as request:\n      return request.read().decode(request.encoding or self.DEFAULT_ENCODING, 'replace')\n\n\nif requests:\n  Context.register(RequestsContext)\n\n\nclass CachedRequestsContext(RequestsContext):\n  \"\"\"A requests-based Context with CacheControl support.\"\"\"\n\n  DEFAULT_CACHE = '~/.pex/cache'\n\n  def __init__(self, cache=None, **kw):\n    self._cache = os.path.realpath(os.path.expanduser(cache or self.DEFAULT_CACHE))\n    super(CachedRequestsContext, self).__init__(\n        CacheControl(requests.session(), cache=FileCache(self._cache)), **kw)\n\n\nif CacheControl:\n  Context.register(CachedRequestsContext)\n", "path": "pex/http.py"}], "after_files": [{"content": "# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\nimport contextlib\nimport hashlib\nimport os\nimport shutil\nimport uuid\nfrom abc import abstractmethod\nfrom email import message_from_string\n\nfrom .common import safe_mkdtemp, safe_open\nfrom .compatibility import PY3, AbstractClass\nfrom .tracer import TRACER\nfrom .variables import ENV\nfrom .version import __version__ as PEX_VERSION\n\ntry:\n  import requests\nexcept ImportError:\n  requests = None\n\ntry:\n  from cachecontrol import CacheControl\n  from cachecontrol.caches import FileCache\nexcept ImportError:\n  CacheControl = FileCache = None\n\nif PY3:\n  import urllib.request as urllib_request\nelse:\n  import urllib2 as urllib_request\n\n# This is available as hashlib.algorithms_guaranteed in >=3.2 and as\n# hashlib.algorithms in >=2.7, but not available in 2.6, so we enumerate\n# here.\nHASHLIB_ALGORITHMS = frozenset(['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512'])\n\n\nclass Context(AbstractClass):\n  \"\"\"Encapsulate the networking necessary to do requirement resolution.\n\n  At a minimum, the Context must implement ``open(link)`` by returning a\n  file-like object.  Reference implementations of ``read(link)`` and\n  ``fetch(link)`` are provided based upon ``open(link)`` but may be further\n  specialized by individual implementations.\n  \"\"\"\n\n  DEFAULT_ENCODING = 'iso-8859-1'\n\n  class Error(Exception):\n    \"\"\"Error base class for Contexts to wrap application-specific exceptions.\"\"\"\n    pass\n\n  _REGISTRY = []\n\n  @classmethod\n  def register(cls, context_impl):\n    \"\"\"Register a concrete implementation of a :class:`Context` to be recognized.\"\"\"\n    cls._REGISTRY.insert(0, context_impl)\n\n  @classmethod\n  def get(cls):\n    for context_class in cls._REGISTRY:\n      try:\n        context = context_class()\n        TRACER.log('Constructed %s context %r' % (context.__class__.__name__, context), V=4)\n        return context\n      except cls.Error:\n        continue\n    raise cls.Error('Could not initialize a request context.')\n\n  @abstractmethod\n  def open(self, link):\n    \"\"\"Return an open file-like object to the link.\n\n    :param link: The :class:`Link` to open.\n    \"\"\"\n\n  def read(self, link):\n    \"\"\"Return the binary content associated with the link.\n\n    :param link: The :class:`Link` to read.\n    \"\"\"\n    with contextlib.closing(self.open(link)) as fp:\n      return fp.read()\n\n  def content(self, link):\n    \"\"\"Return the encoded content associated with the link.\n\n    :param link: The :class:`Link` to read.\n    \"\"\"\n\n  def fetch(self, link, into=None):\n    \"\"\"Fetch the binary content associated with the link and write to a file.\n\n    :param link: The :class:`Link` to fetch.\n    :keyword into: If specified, write into the directory ``into``.  If ``None``, creates a new\n      temporary directory that persists for the duration of the interpreter.\n    \"\"\"\n    target = os.path.join(into or safe_mkdtemp(), link.filename)\n\n    if os.path.exists(target):\n      # Assume that if the local file already exists, it is safe to use.\n      return target\n\n    with TRACER.timed('Fetching %s' % link.url, V=2):\n      target_tmp = '%s.%s' % (target, uuid.uuid4())\n      with contextlib.closing(self.open(link)) as in_fp:\n        with safe_open(target_tmp, 'wb') as out_fp:\n          shutil.copyfileobj(in_fp, out_fp)\n\n    os.rename(target_tmp, target)\n    return target\n\n\nclass UrllibContext(Context):\n  \"\"\"Default Python standard library Context.\"\"\"\n\n  def open(self, link):\n    return urllib_request.urlopen(link.url)\n\n  def content(self, link):\n    if link.local:\n      raise self.Error('Context.content only works with remote URLs.')\n\n    with contextlib.closing(self.open(link)) as fp:\n      encoding = message_from_string(str(fp.headers)).get_content_charset(self.DEFAULT_ENCODING)\n      return fp.read().decode(encoding, 'replace')\n\n  def __init__(self, *args, **kw):\n    TRACER.log('Warning, using a UrllibContext which is known to be flaky.')\n    TRACER.log('Please build pex with the requests module for more reliable downloads.')\n    super(UrllibContext, self).__init__(*args, **kw)\n\n\nContext.register(UrllibContext)\n\n\nclass StreamFilelike(object):\n  \"\"\"A file-like object wrapper around requests streams that performs hash validation.\"\"\"\n\n  @classmethod\n  def detect_algorithm(cls, link):\n    \"\"\"Detect the hashing algorithm from the fragment in the link, if any.\"\"\"\n    if any(link.fragment.startswith('%s=' % algorithm) for algorithm in HASHLIB_ALGORITHMS):\n      algorithm, value = link.fragment.split('=', 2)\n      try:\n        return hashlib.new(algorithm), value\n      except ValueError:  # unsupported algorithm\n        return None, None\n    return None, None\n\n  def __init__(self, request, link, chunk_size=16384):\n    self._iterator = request.iter_content(chunk_size)\n    self.encoding = request.encoding\n    self._bytes = b''\n    self._link = link\n    self._hasher, self._hash_value = self.detect_algorithm(link)\n\n  def read(self, length=None):\n    while length is None or len(self._bytes) < length:\n      try:\n        next_chunk = next(self._iterator)\n        if self._hasher:\n          self._hasher.update(next_chunk)\n        self._bytes += next_chunk\n      except StopIteration:\n        self._validate()\n        break\n    if length is None:\n      length = len(self._bytes)\n    chunk, self._bytes = self._bytes[:length], self._bytes[length:]\n    return chunk\n\n  def _validate(self):\n    if self._hasher:\n      if self._hash_value != self._hasher.hexdigest():\n        raise Context.Error('%s failed checksum!' % (self._link.url))\n      else:\n        TRACER.log('Validated %s (%s)' % (self._link.filename, self._link.fragment), V=3)\n\n  def close(self):\n    pass\n\n\nclass RequestsContext(Context):\n  \"\"\"A requests-based Context.\"\"\"\n  USER_AGENT = 'pex/%s' % PEX_VERSION\n\n  @staticmethod\n  def _create_session(max_retries):\n    session = requests.session()\n    retrying_adapter = requests.adapters.HTTPAdapter(max_retries=max_retries)\n    session.mount('http://', retrying_adapter)\n    session.mount('https://', retrying_adapter)\n    return session\n\n  def __init__(self, session=None, verify=True, env=ENV):\n    if requests is None:\n      raise RuntimeError('requests is not available.  Cannot use a RequestsContext.')\n\n    self._verify = verify\n\n    max_retries = env.PEX_HTTP_RETRIES\n    if max_retries < 0:\n      raise ValueError('max_retries may not be negative.')\n\n    self._max_retries = max_retries\n    self._session = session or self._create_session(self._max_retries)\n\n  def open(self, link):\n    # requests does not support file:// -- so we must short-circuit manually\n    if link.local:\n      return open(link.path, 'rb')  # noqa: T802\n    for attempt in range(self._max_retries + 1):\n      try:\n        return StreamFilelike(self._session.get(\n            link.url, verify=self._verify, stream=True, headers={'User-Agent': self.USER_AGENT}),\n            link)\n      except requests.exceptions.ReadTimeout:\n        # Connect timeouts are handled by the HTTPAdapter, unfortunately read timeouts are not\n        # so we'll retry them ourselves.\n        TRACER.log('Read timeout trying to fetch %s, retrying. %d retries remain.' % (\n            link.url,\n            self._max_retries - attempt))\n      except requests.exceptions.RequestException as e:\n        raise self.Error(e)\n\n    raise self.Error(\n        requests.packages.urllib3.exceptions.MaxRetryError(\n            None,\n            link,\n            'Exceeded max retries of %d' % self._max_retries))\n\n  def content(self, link):\n    if link.local:\n      raise self.Error('Context.content only works with remote URLs.')\n\n    with contextlib.closing(self.open(link)) as request:\n      return request.read().decode(request.encoding or self.DEFAULT_ENCODING, 'replace')\n\n\nif requests:\n  Context.register(RequestsContext)\n\n\nclass CachedRequestsContext(RequestsContext):\n  \"\"\"A requests-based Context with CacheControl support.\"\"\"\n\n  DEFAULT_CACHE = '~/.pex/cache'\n\n  def __init__(self, cache=None, **kw):\n    self._cache = os.path.realpath(os.path.expanduser(cache or self.DEFAULT_CACHE))\n    super(CachedRequestsContext, self).__init__(\n        CacheControl(requests.session(), cache=FileCache(self._cache)), **kw)\n\n\nif CacheControl:\n  Context.register(CachedRequestsContext)\n", "path": "pex/http.py"}]}