We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Update bleach to 2.0.0

There's a new version of [bleach](https://pypi.python.org/pypi/bleach) available.
You are currently using **1.5.0**. I have updated it to **2.0.0**



These links might come in handy:  <a href="http://pypi.python.org/pypi/bleach">PyPI</a> | <a href="https://pyup.io/changelogs/bleach/">Changelog</a> | <a href="http://github.com/mozilla/bleach">Repo</a> | <a href="http://pythonhosted.org/bleach/">Docs</a> 



### Changelog
> 
>### 2.0

>-----------------------------

>**Backwards incompatible changes**

>* Removed support for Python 2.6. 206

>* Removed support for Python 3.2. 224

>* Bleach no longer supports html5lib &lt; 0.99999999 (8 9s).

>  This version is a rewrite to use the new sanitizing API since the old
>  one was dropped in html5lib 0.99999999 (8 9s).

>* ``bleach.clean`` and friends were rewritten

>  ``clean`` was reimplemented as an html5lib filter and happens at a different
>  step in the HTML parsing -&gt; traversing -&gt; serializing process. Because of
>  that, there are some differences in clean&#39;s output as compared with previous
>  versions.

>  Amongst other things, this version will add end tags even if the tag in
>  question is to be escaped.

>* ``bleach.clean`` and friends attribute callables now take three arguments:
>  tag, attribute name and attribute value. Previously they only took attribute
>  name and attribute value.

>  All attribute callables will need to be updated.

>* ``bleach.linkify`` was rewritten

>  ``linkify`` was reimplemented as an html5lib Filter. As such, it no longer
>  accepts a ``tokenizer`` argument.

>  The callback functions for adjusting link attributes now takes a namespaced
>  attribute.

>  Previously you&#39;d do something like this::

>      def check_protocol(attrs, is_new):
>          if not attrs.get(&#39;href&#39;, &#39;&#39;).startswith(&#39;http:&#39;, &#39;https:&#39;)):
>              return None
>          return attrs

>  Now it&#39;s more like this::

>      def check_protocol(attrs, is_new):
>          if not attrs.get((None, u&#39;href&#39;), u&#39;&#39;).startswith((&#39;http:&#39;, &#39;https:&#39;)):
>                          ^^^^^^^^^^^^^^^
>              return None
>          return attrs

>  Further, you need to make sure you&#39;re always using unicode values. If you
>  don&#39;t then html5lib will raise an assertion error that the value is not
>  unicode.

>  All linkify filters will need to be updated.

>* ``bleach.linkify`` and friends had a ``skip_pre`` argument--that&#39;s been
>  replaced with a more general ``skip_tags`` argument.

>  Before, you might do::

>      bleach.linkify(some_text, skip_pre=True)

>  The equivalent with Bleach 2.0 is::

>      bleach.linkify(some_text, skip_tags=[&#39;pre&#39;])

>  You can skip other tags, too, like ``style`` or ``script`` or other places
>  where you don&#39;t want linkification happening.

>  All uses of linkify that use ``skip_pre`` will need to be updated.


>**Changes**

>* Supports Python 3.6.

>* Supports html5lib &gt;= 0.99999999 (8 9s).

>* There&#39;s a ``bleach.sanitizer.Cleaner`` class that you can instantiate with your
>  favorite clean settings for easy reuse.

>* There&#39;s a ``bleach.linkifier.Linker`` class that you can instantiate with your
>  favorite linkify settings for easy reuse.

>* There&#39;s a ``bleach.linkifier.LinkifyFilter`` which is an htm5lib filter that
>  you can pass as a filter to ``bleach.sanitizer.Cleaner`` allowing you to clean
>  and linkify in one pass.

>* ``bleach.clean`` and friends can now take a callable as an attributes arg value.

>* Tons of bug fixes.

>* Cleaned up tests.

>* Documentation fixes.








*Got merge conflicts? Close this PR and delete the branch. I'll create a new PR for you.*

Happy merging! 🤖


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `warehouse/filters.py`
Content:
```
1 # Licensed under the Apache License, Version 2.0 (the "License");
2 # you may not use this file except in compliance with the License.
3 # You may obtain a copy of the License at
4 #
5 # http://www.apache.org/licenses/LICENSE-2.0
6 #
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS,
9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 # See the License for the specific language governing permissions and
11 # limitations under the License.
12 
13 import binascii
14 import collections
15 import enum
16 import hmac
17 import json
18 import re
19 import urllib.parse
20 
21 import html5lib
22 import html5lib.serializer
23 import html5lib.treewalkers
24 import jinja2
25 
26 import readme_renderer.rst
27 import readme_renderer.txt
28 
29 from pyramid.threadlocal import get_current_request
30 
31 from warehouse.utils.http import is_valid_uri
32 
33 
34 class PackageType(enum.Enum):
35     bdist_dmg = "OSX Disk Image"
36     bdist_dumb = "Dumb Binary"
37     bdist_egg = "Egg"
38     bdist_msi = "Windows MSI Installer"
39     bdist_rpm = "RPM"
40     bdist_wheel = "Wheel"
41     bdist_wininst = "Windows Installer"
42     sdist = "Source"
43 
44 
45 def format_package_type(value):
46     try:
47         return PackageType[value].value
48     except KeyError:
49         return value
50 
51 
52 def _camo_url(camo_url, camo_key, url):
53     camo_key = camo_key.encode("utf8")
54     url = url.encode("utf8")
55 
56     path = "/".join([
57         hmac.new(camo_key, url, digestmod="sha1").hexdigest(),
58         binascii.hexlify(url).decode("utf8"),
59     ])
60 
61     return urllib.parse.urljoin(camo_url, path)
62 
63 
64 @jinja2.contextfilter
65 def readme(ctx, value, *, format):
66     request = ctx.get("request") or get_current_request()
67 
68     camo_url = request.registry.settings["camo.url"].format(request=request)
69     camo_key = request.registry.settings["camo.key"]
70 
71     # The format parameter is here so we can more easily expand this to cover
72     # READMEs which do not use restructuredtext, but for now rst is the only
73     # format we support.
74     assert format == "rst", "We currently only support rst rendering."
75 
76     # Actually render the given value, this will not only render the value, but
77     # also ensure that it's had any disallowed markup removed.
78     rendered = readme_renderer.rst.render(value)
79 
80     # If the content was not rendered, we'll replace the newlines with breaks
81     # so that it shows up nicer when rendered.
82     if rendered is None:
83         rendered = readme_renderer.txt.render(value)
84 
85     # Parse the rendered output and replace any inline images that don't point
86     # to HTTPS with camouflaged images.
87     tree_builder = html5lib.treebuilders.getTreeBuilder("dom")
88     parser = html5lib.html5parser.HTMLParser(tree=tree_builder)
89     dom = parser.parse(rendered)
90 
91     for element in dom.getElementsByTagName("img"):
92         src = element.getAttribute("src")
93         if src:
94             element.setAttribute("src", _camo_url(camo_url, camo_key, src))
95 
96     tree_walker = html5lib.treewalkers.getTreeWalker("dom")
97     html_serializer = html5lib.serializer.htmlserializer.HTMLSerializer()
98     rendered = "".join(html_serializer.serialize(tree_walker(dom)))
99 
100     return jinja2.Markup(rendered)
101 
102 
103 _SI_SYMBOLS = ["k", "M", "G", "T", "P", "E", "Z", "Y"]
104 
105 
106 def shorten_number(value):
107     for i, symbol in enumerate(_SI_SYMBOLS):
108         magnitude = value / (1000 ** (i + 1))
109         if magnitude >= 1 and magnitude < 1000:
110             return "{:.3g}{}".format(magnitude, symbol)
111 
112     return str(value)
113 
114 
115 def tojson(value):
116     return json.dumps(value, sort_keys=True, separators=(",", ":"))
117 
118 
119 def urlparse(value):
120     return urllib.parse.urlparse(value)
121 
122 
123 def format_tags(tags):
124     # split tags
125     if re.search(r',', tags):
126         split_tags = re.split(r'\s*,\s*', tags)
127     elif re.search(r';', tags):
128         split_tags = re.split(r'\s*;\s*', tags)
129     else:
130         split_tags = re.split(r'\s+', tags)
131 
132     # strip whitespace, quotes, double quotes
133     stripped_tags = [re.sub(r'^["\'\s]+|["\'\s]+$', '', t) for t in split_tags]
134 
135     # remove any empty tags
136     formatted_tags = [t for t in stripped_tags if t]
137 
138     return formatted_tags
139 
140 
141 def format_classifiers(classifiers):
142     structured = collections.defaultdict(list)
143 
144     # Split up our classifiers into our data structure
145     for classifier in classifiers:
146         key, *value = classifier.split(" :: ", 1)
147         if value:
148             structured[key].append(value[0])
149 
150     # Go thorugh and ensure that all of the lists in our classifiers are in
151     # sorted order.
152     structured = {k: sorted(v) for k, v in structured.items()}
153 
154     # Now, we'll ensure that our keys themselves are in sorted order, using an
155     # OrderedDict to preserve this ordering when we pass this data back up to
156     # our caller.
157     structured = collections.OrderedDict(sorted(structured.items()))
158 
159     return structured
160 
161 
162 def contains_valid_uris(items):
163     """Returns boolean representing whether the input list contains any valid
164     URIs
165     """
166     return any(is_valid_uri(i) for i in items)
167
```
--- 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/warehouse/filters.py b/warehouse/filters.py
--- a/warehouse/filters.py
+++ b/warehouse/filters.py
@@ -94,7 +94,7 @@
             element.setAttribute("src", _camo_url(camo_url, camo_key, src))
 
     tree_walker = html5lib.treewalkers.getTreeWalker("dom")
-    html_serializer = html5lib.serializer.htmlserializer.HTMLSerializer()
+    html_serializer = html5lib.serializer.HTMLSerializer()
     rendered = "".join(html_serializer.serialize(tree_walker(dom)))
 
     return jinja2.Markup(rendered)

{"golden_diff": "diff --git a/warehouse/filters.py b/warehouse/filters.py\n--- a/warehouse/filters.py\n+++ b/warehouse/filters.py\n@@ -94,7 +94,7 @@\n             element.setAttribute(\"src\", _camo_url(camo_url, camo_key, src))\n \n     tree_walker = html5lib.treewalkers.getTreeWalker(\"dom\")\n-    html_serializer = html5lib.serializer.htmlserializer.HTMLSerializer()\n+    html_serializer = html5lib.serializer.HTMLSerializer()\n     rendered = \"\".join(html_serializer.serialize(tree_walker(dom)))\n \n     return jinja2.Markup(rendered)\n", "issue": "Update bleach to 2.0.0\n\nThere's a new version of [bleach](https://pypi.python.org/pypi/bleach) available.\nYou are currently using **1.5.0**. I have updated it to **2.0.0**\n\n\n\nThese links might come in handy:  <a href=\"http://pypi.python.org/pypi/bleach\">PyPI</a> | <a href=\"https://pyup.io/changelogs/bleach/\">Changelog</a> | <a href=\"http://github.com/mozilla/bleach\">Repo</a> | <a href=\"http://pythonhosted.org/bleach/\">Docs</a> \n\n\n\n### Changelog\n> \n>### 2.0\n\n>-----------------------------\n\n>**Backwards incompatible changes**\n\n>* Removed support for Python 2.6. 206\n\n>* Removed support for Python 3.2. 224\n\n>* Bleach no longer supports html5lib &lt; 0.99999999 (8 9s).\n\n>  This version is a rewrite to use the new sanitizing API since the old\n>  one was dropped in html5lib 0.99999999 (8 9s).\n\n>* ``bleach.clean`` and friends were rewritten\n\n>  ``clean`` was reimplemented as an html5lib filter and happens at a different\n>  step in the HTML parsing -&gt; traversing -&gt; serializing process. Because of\n>  that, there are some differences in clean&#39;s output as compared with previous\n>  versions.\n\n>  Amongst other things, this version will add end tags even if the tag in\n>  question is to be escaped.\n\n>* ``bleach.clean`` and friends attribute callables now take three arguments:\n>  tag, attribute name and attribute value. Previously they only took attribute\n>  name and attribute value.\n\n>  All attribute callables will need to be updated.\n\n>* ``bleach.linkify`` was rewritten\n\n>  ``linkify`` was reimplemented as an html5lib Filter. As such, it no longer\n>  accepts a ``tokenizer`` argument.\n\n>  The callback functions for adjusting link attributes now takes a namespaced\n>  attribute.\n\n>  Previously you&#39;d do something like this::\n\n>      def check_protocol(attrs, is_new):\n>          if not attrs.get(&#39;href&#39;, &#39;&#39;).startswith(&#39;http:&#39;, &#39;https:&#39;)):\n>              return None\n>          return attrs\n\n>  Now it&#39;s more like this::\n\n>      def check_protocol(attrs, is_new):\n>          if not attrs.get((None, u&#39;href&#39;), u&#39;&#39;).startswith((&#39;http:&#39;, &#39;https:&#39;)):\n>                          ^^^^^^^^^^^^^^^\n>              return None\n>          return attrs\n\n>  Further, you need to make sure you&#39;re always using unicode values. If you\n>  don&#39;t then html5lib will raise an assertion error that the value is not\n>  unicode.\n\n>  All linkify filters will need to be updated.\n\n>* ``bleach.linkify`` and friends had a ``skip_pre`` argument--that&#39;s been\n>  replaced with a more general ``skip_tags`` argument.\n\n>  Before, you might do::\n\n>      bleach.linkify(some_text, skip_pre=True)\n\n>  The equivalent with Bleach 2.0 is::\n\n>      bleach.linkify(some_text, skip_tags=[&#39;pre&#39;])\n\n>  You can skip other tags, too, like ``style`` or ``script`` or other places\n>  where you don&#39;t want linkification happening.\n\n>  All uses of linkify that use ``skip_pre`` will need to be updated.\n\n\n>**Changes**\n\n>* Supports Python 3.6.\n\n>* Supports html5lib &gt;= 0.99999999 (8 9s).\n\n>* There&#39;s a ``bleach.sanitizer.Cleaner`` class that you can instantiate with your\n>  favorite clean settings for easy reuse.\n\n>* There&#39;s a ``bleach.linkifier.Linker`` class that you can instantiate with your\n>  favorite linkify settings for easy reuse.\n\n>* There&#39;s a ``bleach.linkifier.LinkifyFilter`` which is an htm5lib filter that\n>  you can pass as a filter to ``bleach.sanitizer.Cleaner`` allowing you to clean\n>  and linkify in one pass.\n\n>* ``bleach.clean`` and friends can now take a callable as an attributes arg value.\n\n>* Tons of bug fixes.\n\n>* Cleaned up tests.\n\n>* Documentation fixes.\n\n\n\n\n\n\n\n\n*Got merge conflicts? Close this PR and delete the branch. I'll create a new PR for you.*\n\nHappy merging! \ud83e\udd16\n\n", "before_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport binascii\nimport collections\nimport enum\nimport hmac\nimport json\nimport re\nimport urllib.parse\n\nimport html5lib\nimport html5lib.serializer\nimport html5lib.treewalkers\nimport jinja2\n\nimport readme_renderer.rst\nimport readme_renderer.txt\n\nfrom pyramid.threadlocal import get_current_request\n\nfrom warehouse.utils.http import is_valid_uri\n\n\nclass PackageType(enum.Enum):\n    bdist_dmg = \"OSX Disk Image\"\n    bdist_dumb = \"Dumb Binary\"\n    bdist_egg = \"Egg\"\n    bdist_msi = \"Windows MSI Installer\"\n    bdist_rpm = \"RPM\"\n    bdist_wheel = \"Wheel\"\n    bdist_wininst = \"Windows Installer\"\n    sdist = \"Source\"\n\n\ndef format_package_type(value):\n    try:\n        return PackageType[value].value\n    except KeyError:\n        return value\n\n\ndef _camo_url(camo_url, camo_key, url):\n    camo_key = camo_key.encode(\"utf8\")\n    url = url.encode(\"utf8\")\n\n    path = \"/\".join([\n        hmac.new(camo_key, url, digestmod=\"sha1\").hexdigest(),\n        binascii.hexlify(url).decode(\"utf8\"),\n    ])\n\n    return urllib.parse.urljoin(camo_url, path)\n\n\n@jinja2.contextfilter\ndef readme(ctx, value, *, format):\n    request = ctx.get(\"request\") or get_current_request()\n\n    camo_url = request.registry.settings[\"camo.url\"].format(request=request)\n    camo_key = request.registry.settings[\"camo.key\"]\n\n    # The format parameter is here so we can more easily expand this to cover\n    # READMEs which do not use restructuredtext, but for now rst is the only\n    # format we support.\n    assert format == \"rst\", \"We currently only support rst rendering.\"\n\n    # Actually render the given value, this will not only render the value, but\n    # also ensure that it's had any disallowed markup removed.\n    rendered = readme_renderer.rst.render(value)\n\n    # If the content was not rendered, we'll replace the newlines with breaks\n    # so that it shows up nicer when rendered.\n    if rendered is None:\n        rendered = readme_renderer.txt.render(value)\n\n    # Parse the rendered output and replace any inline images that don't point\n    # to HTTPS with camouflaged images.\n    tree_builder = html5lib.treebuilders.getTreeBuilder(\"dom\")\n    parser = html5lib.html5parser.HTMLParser(tree=tree_builder)\n    dom = parser.parse(rendered)\n\n    for element in dom.getElementsByTagName(\"img\"):\n        src = element.getAttribute(\"src\")\n        if src:\n            element.setAttribute(\"src\", _camo_url(camo_url, camo_key, src))\n\n    tree_walker = html5lib.treewalkers.getTreeWalker(\"dom\")\n    html_serializer = html5lib.serializer.htmlserializer.HTMLSerializer()\n    rendered = \"\".join(html_serializer.serialize(tree_walker(dom)))\n\n    return jinja2.Markup(rendered)\n\n\n_SI_SYMBOLS = [\"k\", \"M\", \"G\", \"T\", \"P\", \"E\", \"Z\", \"Y\"]\n\n\ndef shorten_number(value):\n    for i, symbol in enumerate(_SI_SYMBOLS):\n        magnitude = value / (1000 ** (i + 1))\n        if magnitude >= 1 and magnitude < 1000:\n            return \"{:.3g}{}\".format(magnitude, symbol)\n\n    return str(value)\n\n\ndef tojson(value):\n    return json.dumps(value, sort_keys=True, separators=(\",\", \":\"))\n\n\ndef urlparse(value):\n    return urllib.parse.urlparse(value)\n\n\ndef format_tags(tags):\n    # split tags\n    if re.search(r',', tags):\n        split_tags = re.split(r'\\s*,\\s*', tags)\n    elif re.search(r';', tags):\n        split_tags = re.split(r'\\s*;\\s*', tags)\n    else:\n        split_tags = re.split(r'\\s+', tags)\n\n    # strip whitespace, quotes, double quotes\n    stripped_tags = [re.sub(r'^[\"\\'\\s]+|[\"\\'\\s]+$', '', t) for t in split_tags]\n\n    # remove any empty tags\n    formatted_tags = [t for t in stripped_tags if t]\n\n    return formatted_tags\n\n\ndef format_classifiers(classifiers):\n    structured = collections.defaultdict(list)\n\n    # Split up our classifiers into our data structure\n    for classifier in classifiers:\n        key, *value = classifier.split(\" :: \", 1)\n        if value:\n            structured[key].append(value[0])\n\n    # Go thorugh and ensure that all of the lists in our classifiers are in\n    # sorted order.\n    structured = {k: sorted(v) for k, v in structured.items()}\n\n    # Now, we'll ensure that our keys themselves are in sorted order, using an\n    # OrderedDict to preserve this ordering when we pass this data back up to\n    # our caller.\n    structured = collections.OrderedDict(sorted(structured.items()))\n\n    return structured\n\n\ndef contains_valid_uris(items):\n    \"\"\"Returns boolean representing whether the input list contains any valid\n    URIs\n    \"\"\"\n    return any(is_valid_uri(i) for i in items)\n", "path": "warehouse/filters.py"}], "after_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport binascii\nimport collections\nimport enum\nimport hmac\nimport json\nimport re\nimport urllib.parse\n\nimport html5lib\nimport html5lib.serializer\nimport html5lib.treewalkers\nimport jinja2\n\nimport readme_renderer.rst\nimport readme_renderer.txt\n\nfrom pyramid.threadlocal import get_current_request\n\nfrom warehouse.utils.http import is_valid_uri\n\n\nclass PackageType(enum.Enum):\n    bdist_dmg = \"OSX Disk Image\"\n    bdist_dumb = \"Dumb Binary\"\n    bdist_egg = \"Egg\"\n    bdist_msi = \"Windows MSI Installer\"\n    bdist_rpm = \"RPM\"\n    bdist_wheel = \"Wheel\"\n    bdist_wininst = \"Windows Installer\"\n    sdist = \"Source\"\n\n\ndef format_package_type(value):\n    try:\n        return PackageType[value].value\n    except KeyError:\n        return value\n\n\ndef _camo_url(camo_url, camo_key, url):\n    camo_key = camo_key.encode(\"utf8\")\n    url = url.encode(\"utf8\")\n\n    path = \"/\".join([\n        hmac.new(camo_key, url, digestmod=\"sha1\").hexdigest(),\n        binascii.hexlify(url).decode(\"utf8\"),\n    ])\n\n    return urllib.parse.urljoin(camo_url, path)\n\n\n@jinja2.contextfilter\ndef readme(ctx, value, *, format):\n    request = ctx.get(\"request\") or get_current_request()\n\n    camo_url = request.registry.settings[\"camo.url\"].format(request=request)\n    camo_key = request.registry.settings[\"camo.key\"]\n\n    # The format parameter is here so we can more easily expand this to cover\n    # READMEs which do not use restructuredtext, but for now rst is the only\n    # format we support.\n    assert format == \"rst\", \"We currently only support rst rendering.\"\n\n    # Actually render the given value, this will not only render the value, but\n    # also ensure that it's had any disallowed markup removed.\n    rendered = readme_renderer.rst.render(value)\n\n    # If the content was not rendered, we'll replace the newlines with breaks\n    # so that it shows up nicer when rendered.\n    if rendered is None:\n        rendered = readme_renderer.txt.render(value)\n\n    # Parse the rendered output and replace any inline images that don't point\n    # to HTTPS with camouflaged images.\n    tree_builder = html5lib.treebuilders.getTreeBuilder(\"dom\")\n    parser = html5lib.html5parser.HTMLParser(tree=tree_builder)\n    dom = parser.parse(rendered)\n\n    for element in dom.getElementsByTagName(\"img\"):\n        src = element.getAttribute(\"src\")\n        if src:\n            element.setAttribute(\"src\", _camo_url(camo_url, camo_key, src))\n\n    tree_walker = html5lib.treewalkers.getTreeWalker(\"dom\")\n    html_serializer = html5lib.serializer.HTMLSerializer()\n    rendered = \"\".join(html_serializer.serialize(tree_walker(dom)))\n\n    return jinja2.Markup(rendered)\n\n\n_SI_SYMBOLS = [\"k\", \"M\", \"G\", \"T\", \"P\", \"E\", \"Z\", \"Y\"]\n\n\ndef shorten_number(value):\n    for i, symbol in enumerate(_SI_SYMBOLS):\n        magnitude = value / (1000 ** (i + 1))\n        if magnitude >= 1 and magnitude < 1000:\n            return \"{:.3g}{}\".format(magnitude, symbol)\n\n    return str(value)\n\n\ndef tojson(value):\n    return json.dumps(value, sort_keys=True, separators=(\",\", \":\"))\n\n\ndef urlparse(value):\n    return urllib.parse.urlparse(value)\n\n\ndef format_tags(tags):\n    # split tags\n    if re.search(r',', tags):\n        split_tags = re.split(r'\\s*,\\s*', tags)\n    elif re.search(r';', tags):\n        split_tags = re.split(r'\\s*;\\s*', tags)\n    else:\n        split_tags = re.split(r'\\s+', tags)\n\n    # strip whitespace, quotes, double quotes\n    stripped_tags = [re.sub(r'^[\"\\'\\s]+|[\"\\'\\s]+$', '', t) for t in split_tags]\n\n    # remove any empty tags\n    formatted_tags = [t for t in stripped_tags if t]\n\n    return formatted_tags\n\n\ndef format_classifiers(classifiers):\n    structured = collections.defaultdict(list)\n\n    # Split up our classifiers into our data structure\n    for classifier in classifiers:\n        key, *value = classifier.split(\" :: \", 1)\n        if value:\n            structured[key].append(value[0])\n\n    # Go thorugh and ensure that all of the lists in our classifiers are in\n    # sorted order.\n    structured = {k: sorted(v) for k, v in structured.items()}\n\n    # Now, we'll ensure that our keys themselves are in sorted order, using an\n    # OrderedDict to preserve this ordering when we pass this data back up to\n    # our caller.\n    structured = collections.OrderedDict(sorted(structured.items()))\n\n    return structured\n\n\ndef contains_valid_uris(items):\n    \"\"\"Returns boolean representing whether the input list contains any valid\n    URIs\n    \"\"\"\n    return any(is_valid_uri(i) for i in items)\n", "path": "warehouse/filters.py"}]}