We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
AttributeError: 'NoneType' object has no attribute 'is_hash_allowed'
**Environment**

* pip version: 19.2
* Python version: 3.6.8
* OS: Mac OSX (Darwin Kernel Version 18.6.0)

**Description**
I made env update in my project including pip as well. After that I wanted to check outdated packages with command:

```python
pip list --outdated --format=columns
```

After that exception was raised.

**Expected behavior**
I expected list of packages or empty list.

**How to Reproduce**
1. Get the newest version of package from PyPI.
2. Then run `pip list --outdated --format=columns`
3. An error occurs.

**Output**
```python
(env) project (develop) $ pip list --outdated --format=columns
ERROR: Exception:
Traceback (most recent call last):
  File "/project/env/lib/python3.6/site-packages/pip/_internal/cli/base_command.py", line 188, in main
    status = self.run(options, args)
  File "/project/env/lib/python3.6/site-packages/pip/_internal/commands/list.py", line 156, in run
    packages = self.get_outdated(packages, options)
  File "/project/env/lib/python3.6/site-packages/pip/_internal/commands/list.py", line 164, in get_outdated
    dist for dist in self.iter_packages_latest_infos(packages, options)
  File "/project/env/lib/python3.6/site-packages/pip/_internal/commands/list.py", line 164, in <listcomp>
    dist for dist in self.iter_packages_latest_infos(packages, options)
  File "/project/env/lib/python3.6/site-packages/pip/_internal/commands/list.py", line 195, in iter_packages_latest_infos
    best_candidate = evaluator.get_best_candidate(all_candidates)
  File "/project/env/lib/python3.6/site-packages/pip/_internal/index.py", line 729, in get_best_candidate
    best_candidate = max(candidates, key=self._sort_key)
  File "/project/env/lib/python3.6/site-packages/pip/_internal/index.py", line 710, in _sort_key
    has_allowed_hash = int(link.is_hash_allowed(self._hashes))
  File "/project/env/lib/python3.6/site-packages/pip/_internal/models/link.py", line 213, in is_hash_allowed
    return hashes.is_hash_allowed(self.hash_name, hex_digest=self.hash)
AttributeError: 'NoneType' object has no attribute 'is_hash_allowed'
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/pip/_internal/models/link.py`
Content:
```
1 import posixpath
2 import re
3 
4 from pip._vendor.six.moves.urllib import parse as urllib_parse
5 
6 from pip._internal.utils.misc import (
7     WHEEL_EXTENSION, path_to_url, redact_password_from_url,
8     split_auth_from_netloc, splitext,
9 )
10 from pip._internal.utils.models import KeyBasedCompareMixin
11 from pip._internal.utils.typing import MYPY_CHECK_RUNNING
12 
13 if MYPY_CHECK_RUNNING:
14     from typing import Optional, Text, Tuple, Union
15     from pip._internal.index import HTMLPage
16     from pip._internal.utils.hashes import Hashes
17 
18 
19 class Link(KeyBasedCompareMixin):
20     """Represents a parsed link from a Package Index's simple URL
21     """
22 
23     def __init__(
24         self,
25         url,                   # type: str
26         comes_from=None,       # type: Optional[Union[str, HTMLPage]]
27         requires_python=None,  # type: Optional[str]
28         yanked_reason=None,    # type: Optional[Text]
29     ):
30         # type: (...) -> None
31         """
32         :param url: url of the resource pointed to (href of the link)
33         :param comes_from: instance of HTMLPage where the link was found,
34             or string.
35         :param requires_python: String containing the `Requires-Python`
36             metadata field, specified in PEP 345. This may be specified by
37             a data-requires-python attribute in the HTML link tag, as
38             described in PEP 503.
39         :param yanked_reason: the reason the file has been yanked, if the
40             file has been yanked, or None if the file hasn't been yanked.
41             This is the value of the "data-yanked" attribute, if present, in
42             a simple repository HTML link. If the file has been yanked but
43             no reason was provided, this should be the empty string. See
44             PEP 592 for more information and the specification.
45         """
46 
47         # url can be a UNC windows share
48         if url.startswith('\\\\'):
49             url = path_to_url(url)
50 
51         self._parsed_url = urllib_parse.urlsplit(url)
52         # Store the url as a private attribute to prevent accidentally
53         # trying to set a new value.
54         self._url = url
55 
56         self.comes_from = comes_from
57         self.requires_python = requires_python if requires_python else None
58         self.yanked_reason = yanked_reason
59 
60         super(Link, self).__init__(key=url, defining_class=Link)
61 
62     def __str__(self):
63         if self.requires_python:
64             rp = ' (requires-python:%s)' % self.requires_python
65         else:
66             rp = ''
67         if self.comes_from:
68             return '%s (from %s)%s' % (redact_password_from_url(self._url),
69                                        self.comes_from, rp)
70         else:
71             return redact_password_from_url(str(self._url))
72 
73     def __repr__(self):
74         return '<Link %s>' % self
75 
76     @property
77     def url(self):
78         # type: () -> str
79         return self._url
80 
81     @property
82     def filename(self):
83         # type: () -> str
84         path = self.path.rstrip('/')
85         name = posixpath.basename(path)
86         if not name:
87             # Make sure we don't leak auth information if the netloc
88             # includes a username and password.
89             netloc, user_pass = split_auth_from_netloc(self.netloc)
90             return netloc
91 
92         name = urllib_parse.unquote(name)
93         assert name, ('URL %r produced no filename' % self._url)
94         return name
95 
96     @property
97     def scheme(self):
98         # type: () -> str
99         return self._parsed_url.scheme
100 
101     @property
102     def netloc(self):
103         # type: () -> str
104         """
105         This can contain auth information.
106         """
107         return self._parsed_url.netloc
108 
109     @property
110     def path(self):
111         # type: () -> str
112         return urllib_parse.unquote(self._parsed_url.path)
113 
114     def splitext(self):
115         # type: () -> Tuple[str, str]
116         return splitext(posixpath.basename(self.path.rstrip('/')))
117 
118     @property
119     def ext(self):
120         # type: () -> str
121         return self.splitext()[1]
122 
123     @property
124     def url_without_fragment(self):
125         # type: () -> str
126         scheme, netloc, path, query, fragment = self._parsed_url
127         return urllib_parse.urlunsplit((scheme, netloc, path, query, None))
128 
129     _egg_fragment_re = re.compile(r'[#&]egg=([^&]*)')
130 
131     @property
132     def egg_fragment(self):
133         # type: () -> Optional[str]
134         match = self._egg_fragment_re.search(self._url)
135         if not match:
136             return None
137         return match.group(1)
138 
139     _subdirectory_fragment_re = re.compile(r'[#&]subdirectory=([^&]*)')
140 
141     @property
142     def subdirectory_fragment(self):
143         # type: () -> Optional[str]
144         match = self._subdirectory_fragment_re.search(self._url)
145         if not match:
146             return None
147         return match.group(1)
148 
149     _hash_re = re.compile(
150         r'(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)'
151     )
152 
153     @property
154     def hash(self):
155         # type: () -> Optional[str]
156         match = self._hash_re.search(self._url)
157         if match:
158             return match.group(2)
159         return None
160 
161     @property
162     def hash_name(self):
163         # type: () -> Optional[str]
164         match = self._hash_re.search(self._url)
165         if match:
166             return match.group(1)
167         return None
168 
169     @property
170     def show_url(self):
171         # type: () -> Optional[str]
172         return posixpath.basename(self._url.split('#', 1)[0].split('?', 1)[0])
173 
174     @property
175     def is_wheel(self):
176         # type: () -> bool
177         return self.ext == WHEEL_EXTENSION
178 
179     @property
180     def is_artifact(self):
181         # type: () -> bool
182         """
183         Determines if this points to an actual artifact (e.g. a tarball) or if
184         it points to an "abstract" thing like a path or a VCS location.
185         """
186         from pip._internal.vcs import vcs
187 
188         if self.scheme in vcs.all_schemes:
189             return False
190 
191         return True
192 
193     @property
194     def is_yanked(self):
195         # type: () -> bool
196         return self.yanked_reason is not None
197 
198     @property
199     def has_hash(self):
200         return self.hash_name is not None
201 
202     def is_hash_allowed(self, hashes):
203         # type: (Hashes) -> bool
204         """
205         Return True if the link has a hash and it is allowed.
206         """
207         if not self.has_hash:
208             return False
209         # Assert non-None so mypy knows self.hash_name and self.hash are str.
210         assert self.hash_name is not None
211         assert self.hash is not None
212 
213         return hashes.is_hash_allowed(self.hash_name, hex_digest=self.hash)
214
```
--- 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/src/pip/_internal/models/link.py b/src/pip/_internal/models/link.py
--- a/src/pip/_internal/models/link.py
+++ b/src/pip/_internal/models/link.py
@@ -200,11 +200,11 @@
         return self.hash_name is not None
 
     def is_hash_allowed(self, hashes):
-        # type: (Hashes) -> bool
+        # type: (Optional[Hashes]) -> bool
         """
         Return True if the link has a hash and it is allowed.
         """
-        if not self.has_hash:
+        if hashes is None or not self.has_hash:
             return False
         # Assert non-None so mypy knows self.hash_name and self.hash are str.
         assert self.hash_name is not None

{"golden_diff": "diff --git a/src/pip/_internal/models/link.py b/src/pip/_internal/models/link.py\n--- a/src/pip/_internal/models/link.py\n+++ b/src/pip/_internal/models/link.py\n@@ -200,11 +200,11 @@\n         return self.hash_name is not None\n \n     def is_hash_allowed(self, hashes):\n-        # type: (Hashes) -> bool\n+        # type: (Optional[Hashes]) -> bool\n         \"\"\"\n         Return True if the link has a hash and it is allowed.\n         \"\"\"\n-        if not self.has_hash:\n+        if hashes is None or not self.has_hash:\n             return False\n         # Assert non-None so mypy knows self.hash_name and self.hash are str.\n         assert self.hash_name is not None\n", "issue": "AttributeError: 'NoneType' object has no attribute 'is_hash_allowed'\n**Environment**\r\n\r\n* pip version: 19.2\r\n* Python version: 3.6.8\r\n* OS: Mac OSX (Darwin Kernel Version 18.6.0)\r\n\r\n**Description**\r\nI made env update in my project including pip as well. After that I wanted to check outdated packages with command:\r\n\r\n```python\r\npip list --outdated --format=columns\r\n```\r\n\r\nAfter that exception was raised.\r\n\r\n**Expected behavior**\r\nI expected list of packages or empty list.\r\n\r\n**How to Reproduce**\r\n1. Get the newest version of package from PyPI.\r\n2. Then run `pip list --outdated --format=columns`\r\n3. An error occurs.\r\n\r\n**Output**\r\n```python\r\n(env) project (develop) $ pip list --outdated --format=columns\r\nERROR: Exception:\r\nTraceback (most recent call last):\r\n  File \"/project/env/lib/python3.6/site-packages/pip/_internal/cli/base_command.py\", line 188, in main\r\n    status = self.run(options, args)\r\n  File \"/project/env/lib/python3.6/site-packages/pip/_internal/commands/list.py\", line 156, in run\r\n    packages = self.get_outdated(packages, options)\r\n  File \"/project/env/lib/python3.6/site-packages/pip/_internal/commands/list.py\", line 164, in get_outdated\r\n    dist for dist in self.iter_packages_latest_infos(packages, options)\r\n  File \"/project/env/lib/python3.6/site-packages/pip/_internal/commands/list.py\", line 164, in <listcomp>\r\n    dist for dist in self.iter_packages_latest_infos(packages, options)\r\n  File \"/project/env/lib/python3.6/site-packages/pip/_internal/commands/list.py\", line 195, in iter_packages_latest_infos\r\n    best_candidate = evaluator.get_best_candidate(all_candidates)\r\n  File \"/project/env/lib/python3.6/site-packages/pip/_internal/index.py\", line 729, in get_best_candidate\r\n    best_candidate = max(candidates, key=self._sort_key)\r\n  File \"/project/env/lib/python3.6/site-packages/pip/_internal/index.py\", line 710, in _sort_key\r\n    has_allowed_hash = int(link.is_hash_allowed(self._hashes))\r\n  File \"/project/env/lib/python3.6/site-packages/pip/_internal/models/link.py\", line 213, in is_hash_allowed\r\n    return hashes.is_hash_allowed(self.hash_name, hex_digest=self.hash)\r\nAttributeError: 'NoneType' object has no attribute 'is_hash_allowed'\r\n```\n", "before_files": [{"content": "import posixpath\nimport re\n\nfrom pip._vendor.six.moves.urllib import parse as urllib_parse\n\nfrom pip._internal.utils.misc import (\n    WHEEL_EXTENSION, path_to_url, redact_password_from_url,\n    split_auth_from_netloc, splitext,\n)\nfrom pip._internal.utils.models import KeyBasedCompareMixin\nfrom pip._internal.utils.typing import MYPY_CHECK_RUNNING\n\nif MYPY_CHECK_RUNNING:\n    from typing import Optional, Text, Tuple, Union\n    from pip._internal.index import HTMLPage\n    from pip._internal.utils.hashes import Hashes\n\n\nclass Link(KeyBasedCompareMixin):\n    \"\"\"Represents a parsed link from a Package Index's simple URL\n    \"\"\"\n\n    def __init__(\n        self,\n        url,                   # type: str\n        comes_from=None,       # type: Optional[Union[str, HTMLPage]]\n        requires_python=None,  # type: Optional[str]\n        yanked_reason=None,    # type: Optional[Text]\n    ):\n        # type: (...) -> None\n        \"\"\"\n        :param url: url of the resource pointed to (href of the link)\n        :param comes_from: instance of HTMLPage where the link was found,\n            or string.\n        :param requires_python: String containing the `Requires-Python`\n            metadata field, specified in PEP 345. This may be specified by\n            a data-requires-python attribute in the HTML link tag, as\n            described in PEP 503.\n        :param yanked_reason: the reason the file has been yanked, if the\n            file has been yanked, or None if the file hasn't been yanked.\n            This is the value of the \"data-yanked\" attribute, if present, in\n            a simple repository HTML link. If the file has been yanked but\n            no reason was provided, this should be the empty string. See\n            PEP 592 for more information and the specification.\n        \"\"\"\n\n        # url can be a UNC windows share\n        if url.startswith('\\\\\\\\'):\n            url = path_to_url(url)\n\n        self._parsed_url = urllib_parse.urlsplit(url)\n        # Store the url as a private attribute to prevent accidentally\n        # trying to set a new value.\n        self._url = url\n\n        self.comes_from = comes_from\n        self.requires_python = requires_python if requires_python else None\n        self.yanked_reason = yanked_reason\n\n        super(Link, self).__init__(key=url, defining_class=Link)\n\n    def __str__(self):\n        if self.requires_python:\n            rp = ' (requires-python:%s)' % self.requires_python\n        else:\n            rp = ''\n        if self.comes_from:\n            return '%s (from %s)%s' % (redact_password_from_url(self._url),\n                                       self.comes_from, rp)\n        else:\n            return redact_password_from_url(str(self._url))\n\n    def __repr__(self):\n        return '<Link %s>' % self\n\n    @property\n    def url(self):\n        # type: () -> str\n        return self._url\n\n    @property\n    def filename(self):\n        # type: () -> str\n        path = self.path.rstrip('/')\n        name = posixpath.basename(path)\n        if not name:\n            # Make sure we don't leak auth information if the netloc\n            # includes a username and password.\n            netloc, user_pass = split_auth_from_netloc(self.netloc)\n            return netloc\n\n        name = urllib_parse.unquote(name)\n        assert name, ('URL %r produced no filename' % self._url)\n        return name\n\n    @property\n    def scheme(self):\n        # type: () -> str\n        return self._parsed_url.scheme\n\n    @property\n    def netloc(self):\n        # type: () -> str\n        \"\"\"\n        This can contain auth information.\n        \"\"\"\n        return self._parsed_url.netloc\n\n    @property\n    def path(self):\n        # type: () -> str\n        return urllib_parse.unquote(self._parsed_url.path)\n\n    def splitext(self):\n        # type: () -> Tuple[str, str]\n        return splitext(posixpath.basename(self.path.rstrip('/')))\n\n    @property\n    def ext(self):\n        # type: () -> str\n        return self.splitext()[1]\n\n    @property\n    def url_without_fragment(self):\n        # type: () -> str\n        scheme, netloc, path, query, fragment = self._parsed_url\n        return urllib_parse.urlunsplit((scheme, netloc, path, query, None))\n\n    _egg_fragment_re = re.compile(r'[#&]egg=([^&]*)')\n\n    @property\n    def egg_fragment(self):\n        # type: () -> Optional[str]\n        match = self._egg_fragment_re.search(self._url)\n        if not match:\n            return None\n        return match.group(1)\n\n    _subdirectory_fragment_re = re.compile(r'[#&]subdirectory=([^&]*)')\n\n    @property\n    def subdirectory_fragment(self):\n        # type: () -> Optional[str]\n        match = self._subdirectory_fragment_re.search(self._url)\n        if not match:\n            return None\n        return match.group(1)\n\n    _hash_re = re.compile(\n        r'(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)'\n    )\n\n    @property\n    def hash(self):\n        # type: () -> Optional[str]\n        match = self._hash_re.search(self._url)\n        if match:\n            return match.group(2)\n        return None\n\n    @property\n    def hash_name(self):\n        # type: () -> Optional[str]\n        match = self._hash_re.search(self._url)\n        if match:\n            return match.group(1)\n        return None\n\n    @property\n    def show_url(self):\n        # type: () -> Optional[str]\n        return posixpath.basename(self._url.split('#', 1)[0].split('?', 1)[0])\n\n    @property\n    def is_wheel(self):\n        # type: () -> bool\n        return self.ext == WHEEL_EXTENSION\n\n    @property\n    def is_artifact(self):\n        # type: () -> bool\n        \"\"\"\n        Determines if this points to an actual artifact (e.g. a tarball) or if\n        it points to an \"abstract\" thing like a path or a VCS location.\n        \"\"\"\n        from pip._internal.vcs import vcs\n\n        if self.scheme in vcs.all_schemes:\n            return False\n\n        return True\n\n    @property\n    def is_yanked(self):\n        # type: () -> bool\n        return self.yanked_reason is not None\n\n    @property\n    def has_hash(self):\n        return self.hash_name is not None\n\n    def is_hash_allowed(self, hashes):\n        # type: (Hashes) -> bool\n        \"\"\"\n        Return True if the link has a hash and it is allowed.\n        \"\"\"\n        if not self.has_hash:\n            return False\n        # Assert non-None so mypy knows self.hash_name and self.hash are str.\n        assert self.hash_name is not None\n        assert self.hash is not None\n\n        return hashes.is_hash_allowed(self.hash_name, hex_digest=self.hash)\n", "path": "src/pip/_internal/models/link.py"}], "after_files": [{"content": "import posixpath\nimport re\n\nfrom pip._vendor.six.moves.urllib import parse as urllib_parse\n\nfrom pip._internal.utils.misc import (\n    WHEEL_EXTENSION, path_to_url, redact_password_from_url,\n    split_auth_from_netloc, splitext,\n)\nfrom pip._internal.utils.models import KeyBasedCompareMixin\nfrom pip._internal.utils.typing import MYPY_CHECK_RUNNING\n\nif MYPY_CHECK_RUNNING:\n    from typing import Optional, Text, Tuple, Union\n    from pip._internal.index import HTMLPage\n    from pip._internal.utils.hashes import Hashes\n\n\nclass Link(KeyBasedCompareMixin):\n    \"\"\"Represents a parsed link from a Package Index's simple URL\n    \"\"\"\n\n    def __init__(\n        self,\n        url,                   # type: str\n        comes_from=None,       # type: Optional[Union[str, HTMLPage]]\n        requires_python=None,  # type: Optional[str]\n        yanked_reason=None,    # type: Optional[Text]\n    ):\n        # type: (...) -> None\n        \"\"\"\n        :param url: url of the resource pointed to (href of the link)\n        :param comes_from: instance of HTMLPage where the link was found,\n            or string.\n        :param requires_python: String containing the `Requires-Python`\n            metadata field, specified in PEP 345. This may be specified by\n            a data-requires-python attribute in the HTML link tag, as\n            described in PEP 503.\n        :param yanked_reason: the reason the file has been yanked, if the\n            file has been yanked, or None if the file hasn't been yanked.\n            This is the value of the \"data-yanked\" attribute, if present, in\n            a simple repository HTML link. If the file has been yanked but\n            no reason was provided, this should be the empty string. See\n            PEP 592 for more information and the specification.\n        \"\"\"\n\n        # url can be a UNC windows share\n        if url.startswith('\\\\\\\\'):\n            url = path_to_url(url)\n\n        self._parsed_url = urllib_parse.urlsplit(url)\n        # Store the url as a private attribute to prevent accidentally\n        # trying to set a new value.\n        self._url = url\n\n        self.comes_from = comes_from\n        self.requires_python = requires_python if requires_python else None\n        self.yanked_reason = yanked_reason\n\n        super(Link, self).__init__(key=url, defining_class=Link)\n\n    def __str__(self):\n        if self.requires_python:\n            rp = ' (requires-python:%s)' % self.requires_python\n        else:\n            rp = ''\n        if self.comes_from:\n            return '%s (from %s)%s' % (redact_password_from_url(self._url),\n                                       self.comes_from, rp)\n        else:\n            return redact_password_from_url(str(self._url))\n\n    def __repr__(self):\n        return '<Link %s>' % self\n\n    @property\n    def url(self):\n        # type: () -> str\n        return self._url\n\n    @property\n    def filename(self):\n        # type: () -> str\n        path = self.path.rstrip('/')\n        name = posixpath.basename(path)\n        if not name:\n            # Make sure we don't leak auth information if the netloc\n            # includes a username and password.\n            netloc, user_pass = split_auth_from_netloc(self.netloc)\n            return netloc\n\n        name = urllib_parse.unquote(name)\n        assert name, ('URL %r produced no filename' % self._url)\n        return name\n\n    @property\n    def scheme(self):\n        # type: () -> str\n        return self._parsed_url.scheme\n\n    @property\n    def netloc(self):\n        # type: () -> str\n        \"\"\"\n        This can contain auth information.\n        \"\"\"\n        return self._parsed_url.netloc\n\n    @property\n    def path(self):\n        # type: () -> str\n        return urllib_parse.unquote(self._parsed_url.path)\n\n    def splitext(self):\n        # type: () -> Tuple[str, str]\n        return splitext(posixpath.basename(self.path.rstrip('/')))\n\n    @property\n    def ext(self):\n        # type: () -> str\n        return self.splitext()[1]\n\n    @property\n    def url_without_fragment(self):\n        # type: () -> str\n        scheme, netloc, path, query, fragment = self._parsed_url\n        return urllib_parse.urlunsplit((scheme, netloc, path, query, None))\n\n    _egg_fragment_re = re.compile(r'[#&]egg=([^&]*)')\n\n    @property\n    def egg_fragment(self):\n        # type: () -> Optional[str]\n        match = self._egg_fragment_re.search(self._url)\n        if not match:\n            return None\n        return match.group(1)\n\n    _subdirectory_fragment_re = re.compile(r'[#&]subdirectory=([^&]*)')\n\n    @property\n    def subdirectory_fragment(self):\n        # type: () -> Optional[str]\n        match = self._subdirectory_fragment_re.search(self._url)\n        if not match:\n            return None\n        return match.group(1)\n\n    _hash_re = re.compile(\n        r'(sha1|sha224|sha384|sha256|sha512|md5)=([a-f0-9]+)'\n    )\n\n    @property\n    def hash(self):\n        # type: () -> Optional[str]\n        match = self._hash_re.search(self._url)\n        if match:\n            return match.group(2)\n        return None\n\n    @property\n    def hash_name(self):\n        # type: () -> Optional[str]\n        match = self._hash_re.search(self._url)\n        if match:\n            return match.group(1)\n        return None\n\n    @property\n    def show_url(self):\n        # type: () -> Optional[str]\n        return posixpath.basename(self._url.split('#', 1)[0].split('?', 1)[0])\n\n    @property\n    def is_wheel(self):\n        # type: () -> bool\n        return self.ext == WHEEL_EXTENSION\n\n    @property\n    def is_artifact(self):\n        # type: () -> bool\n        \"\"\"\n        Determines if this points to an actual artifact (e.g. a tarball) or if\n        it points to an \"abstract\" thing like a path or a VCS location.\n        \"\"\"\n        from pip._internal.vcs import vcs\n\n        if self.scheme in vcs.all_schemes:\n            return False\n\n        return True\n\n    @property\n    def is_yanked(self):\n        # type: () -> bool\n        return self.yanked_reason is not None\n\n    @property\n    def has_hash(self):\n        return self.hash_name is not None\n\n    def is_hash_allowed(self, hashes):\n        # type: (Optional[Hashes]) -> bool\n        \"\"\"\n        Return True if the link has a hash and it is allowed.\n        \"\"\"\n        if hashes is None or not self.has_hash:\n            return False\n        # Assert non-None so mypy knows self.hash_name and self.hash are str.\n        assert self.hash_name is not None\n        assert self.hash is not None\n\n        return hashes.is_hash_allowed(self.hash_name, hex_digest=self.hash)\n", "path": "src/pip/_internal/models/link.py"}]}