We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Search method in XML-RPC API treats {"summary": "fix code"} as {"summary": ["fix", "code"]}
As stated in the title, the `search` method of [PyPI/Warehouse's XML-RPC API](https://warehouse.readthedocs.io/api-reference/xml-rpc/) splits the values of its `spec` argument on whitespace, resulting in the method returning (for the example in the title) packages with either "fix" or "code" in their summary, not just those with both "fix" and "code" (let alone just those that contain the exact string "fix code").  This happens even if the `spec` value is passed as a one-element list (e.g., `{"summary": ["fix code"]}`), and while I haven't tested every field, it is not limited to just "summary".

On the off chance that this is the intended behavior, the documentation should at least be updated.

--- 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/legacy/api/xmlrpc.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 collections.abc
14 import datetime
15 import functools
16 import xmlrpc.server
17 
18 from elasticsearch_dsl import Q
19 from pyramid.view import view_config
20 from pyramid_rpc.xmlrpc import exception_view as _exception_view, xmlrpc_method
21 from sqlalchemy import func, select
22 from sqlalchemy.orm.exc import NoResultFound
23 
24 from warehouse.accounts.models import User
25 from warehouse.classifiers.models import Classifier
26 from warehouse.packaging.interfaces import IDownloadStatService
27 from warehouse.packaging.models import (
28     Role, Project, Release, File, JournalEntry, release_classifiers,
29 )
30 
31 
32 pypi_xmlrpc = functools.partial(
33     xmlrpc_method,
34     endpoint="pypi",
35     require_csrf=False,
36     require_methods=["POST"],
37 )
38 
39 
40 class XMLRPCWrappedError(xmlrpc.server.Fault):
41 
42     def __init__(self, exc):
43         self.faultCode = -32500
44         self.wrapped_exception = exc
45 
46     @property
47     def faultString(self):  # noqa
48         return "{exc.__class__.__name__}: {exc}".format(
49             exc=self.wrapped_exception,
50         )
51 
52 
53 @view_config(route_name="pypi", context=Exception, renderer="xmlrpc")
54 def exception_view(exc, request):
55     return _exception_view(exc, request)
56 
57 
58 @pypi_xmlrpc(method="search")
59 def search(request, spec, operator="and"):
60     if not isinstance(spec, collections.abc.Mapping):
61         raise XMLRPCWrappedError(
62             TypeError("Invalid spec, must be a mapping/dictionary.")
63         )
64 
65     if operator not in {"and", "or"}:
66         raise XMLRPCWrappedError(
67             ValueError("Invalid operator, must be one of 'and' or 'or'.")
68         )
69 
70     # Remove any invalid spec fields
71     spec = {
72         k: [v] if isinstance(v, str) else v
73         for k, v in spec.items()
74         if v and k in {
75             "name", "version", "author", "author_email", "maintainer",
76             "maintainer_email", "home_page", "license", "summary",
77             "description", "keywords", "platform", "download_url",
78         }
79     }
80 
81     queries = []
82     for field, value in sorted(spec.items()):
83         q = None
84         for item in value:
85             if q is None:
86                 q = Q("match", **{field: item})
87             else:
88                 q |= Q("match", **{field: item})
89         queries.append(q)
90 
91     if operator == "and":
92         query = request.es.query("bool", must=queries)
93     else:
94         query = request.es.query("bool", should=queries)
95 
96     results = query[:1000].execute()
97 
98     return [
99         {"name": r.name, "summary": r.summary, "version": v}
100         for r in results
101         for v in r.version
102         if v in spec.get("version", [v])
103     ]
104 
105 
106 @pypi_xmlrpc(method="list_packages")
107 def list_packages(request):
108     names = request.db.query(Project.name).order_by(Project.name).all()
109     return [n[0] for n in names]
110 
111 
112 @pypi_xmlrpc(method="list_packages_with_serial")
113 def list_packages_with_serial(request):
114     serials = request.db.query(Project.name, Project.last_serial).all()
115     return dict((serial[0], serial[1]) for serial in serials)
116 
117 
118 @pypi_xmlrpc(method="package_hosting_mode")
119 def package_hosting_mode(request, package_name):
120     try:
121         project = (
122             request.db.query(Project)
123                       .filter(Project.normalized_name ==
124                               func.normalize_pep426_name(package_name))
125                       .one()
126         )
127     except NoResultFound:
128         return None
129     else:
130         return project.hosting_mode
131 
132 
133 @pypi_xmlrpc(method="user_packages")
134 def user_packages(request, username):
135     roles = (
136         request.db.query(Role)
137                   .join(User, Project)
138                   .filter(User.username == username)
139                   .order_by(Role.role_name.desc(), Project.name)
140                   .all()
141     )
142     return [(r.role_name, r.project.name) for r in roles]
143 
144 
145 @pypi_xmlrpc(method="top_packages")
146 def top_packages(request, num=None):
147     raise XMLRPCWrappedError(
148         RuntimeError("This API has been removed. Please Use BigQuery instead.")
149     )
150 
151 
152 @pypi_xmlrpc(method="package_releases")
153 def package_releases(request, package_name, show_hidden=False):
154     # This used to support the show_hidden parameter to determine if it should
155     # show hidden releases or not. However, Warehouse doesn't support the
156     # concept of hidden releases, so it is just no-opd now and left here for
157     # compatibility's sake.
158     versions = (
159         request.db.query(Release.version)
160                   .join(Project)
161                   .filter(Project.normalized_name ==
162                           func.normalize_pep426_name(package_name))
163                   .order_by(Release._pypi_ordering)
164                   .all()
165     )
166     return [v[0] for v in versions]
167 
168 
169 @pypi_xmlrpc(method="release_data")
170 def release_data(request, package_name, version):
171     try:
172         release = (
173             request.db.query(Release)
174                       .join(Project)
175                       .filter((Project.normalized_name ==
176                                func.normalize_pep426_name(package_name)) &
177                               (Release.version == version))
178                       .one()
179         )
180     except NoResultFound:
181         return {}
182 
183     stats_svc = request.find_service(IDownloadStatService)
184 
185     return {
186         "name": release.project.name,
187         "version": release.version,
188         "stable_version": release.project.stable_version,
189         "bugtrack_url": release.project.bugtrack_url,
190         "package_url": request.route_url(
191             "packaging.project",
192             name=release.project.name,
193         ),
194         "release_url": request.route_url(
195             "packaging.release",
196             name=release.project.name,
197             version=release.version,
198         ),
199         "docs_url": release.project.documentation_url,
200         "home_page": release.home_page,
201         "download_url": release.download_url,
202         "project_url": list(release.project_urls),
203         "author": release.author,
204         "author_email": release.author_email,
205         "maintainer": release.maintainer,
206         "maintainer_email": release.maintainer_email,
207         "summary": release.summary,
208         "description": release.description,
209         "license": release.license,
210         "keywords": release.keywords,
211         "platform": release.platform,
212         "classifiers": list(release.classifiers),
213         "requires": list(release.requires),
214         "requires_dist": list(release.requires_dist),
215         "provides": list(release.provides),
216         "provides_dist": list(release.provides_dist),
217         "obsoletes": list(release.obsoletes),
218         "obsoletes_dist": list(release.obsoletes_dist),
219         "requires_python": release.requires_python,
220         "requires_external": list(release.requires_external),
221         "_pypi_ordering": release._pypi_ordering,
222         "_pypi_hidden": release._pypi_hidden,
223         "downloads": {
224             "last_day": stats_svc.get_daily_stats(release.project.name),
225             "last_week": stats_svc.get_weekly_stats(release.project.name),
226             "last_month": stats_svc.get_monthly_stats(release.project.name),
227         },
228     }
229 
230 
231 @pypi_xmlrpc(method="release_urls")
232 def release_urls(request, package_name, version):
233     files = (
234         request.db.query(File)
235                   .join(Release, Project)
236                   .filter((Project.normalized_name ==
237                            func.normalize_pep426_name(package_name)) &
238                           (Release.version == version))
239                   .all()
240     )
241 
242     return [
243         {
244             "filename": f.filename,
245             "packagetype": f.packagetype,
246             "python_version": f.python_version,
247             "size": f.size,
248             "md5_digest": f.md5_digest,
249             "digests": {
250                 "md5": f.md5_digest,
251                 "sha256": f.sha256_digest,
252             },
253             "has_sig": f.has_signature,
254             "upload_time": f.upload_time,
255             "comment_text": f.comment_text,
256             # TODO: Remove this once we've had a long enough time with it
257             #       here to consider it no longer in use.
258             "downloads": -1,
259             "url": request.route_url("packaging.file", path=f.path),
260         }
261         for f in files
262     ]
263 
264 
265 @pypi_xmlrpc(method="package_roles")
266 def package_roles(request, package_name):
267     roles = (
268         request.db.query(Role)
269                   .join(User, Project)
270                   .filter(Project.normalized_name ==
271                           func.normalize_pep426_name(package_name))
272                   .order_by(Role.role_name.desc(), User.username)
273                   .all()
274     )
275     return [(r.role_name, r.user.username) for r in roles]
276 
277 
278 @pypi_xmlrpc(method="changelog_last_serial")
279 def changelog_last_serial(request):
280     return request.db.query(func.max(JournalEntry.id)).scalar()
281 
282 
283 @pypi_xmlrpc(method="changelog_since_serial")
284 def changelog_since_serial(request, serial):
285     entries = (
286         request.db.query(JournalEntry)
287                   .filter(JournalEntry.id > serial)
288                   .order_by(JournalEntry.id)
289                   .limit(50000)
290     )
291 
292     return [
293         (
294             e.name,
295             e.version,
296             int(
297                 e.submitted_date
298                  .replace(tzinfo=datetime.timezone.utc)
299                  .timestamp()
300             ),
301             e.action,
302             e.id,
303         )
304         for e in entries
305     ]
306 
307 
308 @pypi_xmlrpc(method="changelog")
309 def changelog(request, since, with_ids=False):
310     since = datetime.datetime.utcfromtimestamp(since)
311     entries = (
312         request.db.query(JournalEntry)
313                   .filter(JournalEntry.submitted_date > since)
314                   .order_by(JournalEntry.submitted_date)
315                   .limit(50000)
316     )
317 
318     results = (
319         (
320             e.name,
321             e.version,
322             int(
323                 e.submitted_date
324                  .replace(tzinfo=datetime.timezone.utc)
325                  .timestamp()
326             ),
327             e.action,
328             e.id,
329         )
330         for e in entries
331     )
332 
333     if with_ids:
334         return list(results)
335     else:
336         return [r[:-1] for r in results]
337 
338 
339 @pypi_xmlrpc(method="browse")
340 def browse(request, classifiers):
341     classifiers_q = (
342         request.db.query(Classifier)
343                .filter(Classifier.classifier.in_(classifiers))
344                .subquery()
345     )
346 
347     release_classifiers_q = (
348         select([release_classifiers])
349         .where(release_classifiers.c.trove_id == classifiers_q.c.id)
350         .alias("rc")
351     )
352 
353     releases = (
354         request.db.query(Release.name, Release.version)
355                   .join(release_classifiers_q,
356                         (Release.name == release_classifiers_q.c.name) &
357                         (Release.version == release_classifiers_q.c.version))
358                   .group_by(Release.name, Release.version)
359                   .having(func.count() == len(classifiers))
360                   .order_by(Release.name, Release.version)
361                   .all()
362     )
363 
364     return [(r.name, r.version) for r in releases]
365
```
--- 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/legacy/api/xmlrpc.py b/warehouse/legacy/api/xmlrpc.py
--- a/warehouse/legacy/api/xmlrpc.py
+++ b/warehouse/legacy/api/xmlrpc.py
@@ -83,9 +83,9 @@
         q = None
         for item in value:
             if q is None:
-                q = Q("match", **{field: item})
+                q = Q("term", **{field: item})
             else:
-                q |= Q("match", **{field: item})
+                q |= Q("term", **{field: item})
         queries.append(q)
 
     if operator == "and":

{"golden_diff": "diff --git a/warehouse/legacy/api/xmlrpc.py b/warehouse/legacy/api/xmlrpc.py\n--- a/warehouse/legacy/api/xmlrpc.py\n+++ b/warehouse/legacy/api/xmlrpc.py\n@@ -83,9 +83,9 @@\n         q = None\n         for item in value:\n             if q is None:\n-                q = Q(\"match\", **{field: item})\n+                q = Q(\"term\", **{field: item})\n             else:\n-                q |= Q(\"match\", **{field: item})\n+                q |= Q(\"term\", **{field: item})\n         queries.append(q)\n \n     if operator == \"and\":\n", "issue": "Search method in XML-RPC API treats {\"summary\": \"fix code\"} as {\"summary\": [\"fix\", \"code\"]}\nAs stated in the title, the `search` method of [PyPI/Warehouse's XML-RPC API](https://warehouse.readthedocs.io/api-reference/xml-rpc/) splits the values of its `spec` argument on whitespace, resulting in the method returning (for the example in the title) packages with either \"fix\" or \"code\" in their summary, not just those with both \"fix\" and \"code\" (let alone just those that contain the exact string \"fix code\").  This happens even if the `spec` value is passed as a one-element list (e.g., `{\"summary\": [\"fix code\"]}`), and while I haven't tested every field, it is not limited to just \"summary\".\r\n\r\nOn the off chance that this is the intended behavior, the documentation should at least be updated.\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 collections.abc\nimport datetime\nimport functools\nimport xmlrpc.server\n\nfrom elasticsearch_dsl import Q\nfrom pyramid.view import view_config\nfrom pyramid_rpc.xmlrpc import exception_view as _exception_view, xmlrpc_method\nfrom sqlalchemy import func, select\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom warehouse.accounts.models import User\nfrom warehouse.classifiers.models import Classifier\nfrom warehouse.packaging.interfaces import IDownloadStatService\nfrom warehouse.packaging.models import (\n    Role, Project, Release, File, JournalEntry, release_classifiers,\n)\n\n\npypi_xmlrpc = functools.partial(\n    xmlrpc_method,\n    endpoint=\"pypi\",\n    require_csrf=False,\n    require_methods=[\"POST\"],\n)\n\n\nclass XMLRPCWrappedError(xmlrpc.server.Fault):\n\n    def __init__(self, exc):\n        self.faultCode = -32500\n        self.wrapped_exception = exc\n\n    @property\n    def faultString(self):  # noqa\n        return \"{exc.__class__.__name__}: {exc}\".format(\n            exc=self.wrapped_exception,\n        )\n\n\n@view_config(route_name=\"pypi\", context=Exception, renderer=\"xmlrpc\")\ndef exception_view(exc, request):\n    return _exception_view(exc, request)\n\n\n@pypi_xmlrpc(method=\"search\")\ndef search(request, spec, operator=\"and\"):\n    if not isinstance(spec, collections.abc.Mapping):\n        raise XMLRPCWrappedError(\n            TypeError(\"Invalid spec, must be a mapping/dictionary.\")\n        )\n\n    if operator not in {\"and\", \"or\"}:\n        raise XMLRPCWrappedError(\n            ValueError(\"Invalid operator, must be one of 'and' or 'or'.\")\n        )\n\n    # Remove any invalid spec fields\n    spec = {\n        k: [v] if isinstance(v, str) else v\n        for k, v in spec.items()\n        if v and k in {\n            \"name\", \"version\", \"author\", \"author_email\", \"maintainer\",\n            \"maintainer_email\", \"home_page\", \"license\", \"summary\",\n            \"description\", \"keywords\", \"platform\", \"download_url\",\n        }\n    }\n\n    queries = []\n    for field, value in sorted(spec.items()):\n        q = None\n        for item in value:\n            if q is None:\n                q = Q(\"match\", **{field: item})\n            else:\n                q |= Q(\"match\", **{field: item})\n        queries.append(q)\n\n    if operator == \"and\":\n        query = request.es.query(\"bool\", must=queries)\n    else:\n        query = request.es.query(\"bool\", should=queries)\n\n    results = query[:1000].execute()\n\n    return [\n        {\"name\": r.name, \"summary\": r.summary, \"version\": v}\n        for r in results\n        for v in r.version\n        if v in spec.get(\"version\", [v])\n    ]\n\n\n@pypi_xmlrpc(method=\"list_packages\")\ndef list_packages(request):\n    names = request.db.query(Project.name).order_by(Project.name).all()\n    return [n[0] for n in names]\n\n\n@pypi_xmlrpc(method=\"list_packages_with_serial\")\ndef list_packages_with_serial(request):\n    serials = request.db.query(Project.name, Project.last_serial).all()\n    return dict((serial[0], serial[1]) for serial in serials)\n\n\n@pypi_xmlrpc(method=\"package_hosting_mode\")\ndef package_hosting_mode(request, package_name):\n    try:\n        project = (\n            request.db.query(Project)\n                      .filter(Project.normalized_name ==\n                              func.normalize_pep426_name(package_name))\n                      .one()\n        )\n    except NoResultFound:\n        return None\n    else:\n        return project.hosting_mode\n\n\n@pypi_xmlrpc(method=\"user_packages\")\ndef user_packages(request, username):\n    roles = (\n        request.db.query(Role)\n                  .join(User, Project)\n                  .filter(User.username == username)\n                  .order_by(Role.role_name.desc(), Project.name)\n                  .all()\n    )\n    return [(r.role_name, r.project.name) for r in roles]\n\n\n@pypi_xmlrpc(method=\"top_packages\")\ndef top_packages(request, num=None):\n    raise XMLRPCWrappedError(\n        RuntimeError(\"This API has been removed. Please Use BigQuery instead.\")\n    )\n\n\n@pypi_xmlrpc(method=\"package_releases\")\ndef package_releases(request, package_name, show_hidden=False):\n    # This used to support the show_hidden parameter to determine if it should\n    # show hidden releases or not. However, Warehouse doesn't support the\n    # concept of hidden releases, so it is just no-opd now and left here for\n    # compatibility's sake.\n    versions = (\n        request.db.query(Release.version)\n                  .join(Project)\n                  .filter(Project.normalized_name ==\n                          func.normalize_pep426_name(package_name))\n                  .order_by(Release._pypi_ordering)\n                  .all()\n    )\n    return [v[0] for v in versions]\n\n\n@pypi_xmlrpc(method=\"release_data\")\ndef release_data(request, package_name, version):\n    try:\n        release = (\n            request.db.query(Release)\n                      .join(Project)\n                      .filter((Project.normalized_name ==\n                               func.normalize_pep426_name(package_name)) &\n                              (Release.version == version))\n                      .one()\n        )\n    except NoResultFound:\n        return {}\n\n    stats_svc = request.find_service(IDownloadStatService)\n\n    return {\n        \"name\": release.project.name,\n        \"version\": release.version,\n        \"stable_version\": release.project.stable_version,\n        \"bugtrack_url\": release.project.bugtrack_url,\n        \"package_url\": request.route_url(\n            \"packaging.project\",\n            name=release.project.name,\n        ),\n        \"release_url\": request.route_url(\n            \"packaging.release\",\n            name=release.project.name,\n            version=release.version,\n        ),\n        \"docs_url\": release.project.documentation_url,\n        \"home_page\": release.home_page,\n        \"download_url\": release.download_url,\n        \"project_url\": list(release.project_urls),\n        \"author\": release.author,\n        \"author_email\": release.author_email,\n        \"maintainer\": release.maintainer,\n        \"maintainer_email\": release.maintainer_email,\n        \"summary\": release.summary,\n        \"description\": release.description,\n        \"license\": release.license,\n        \"keywords\": release.keywords,\n        \"platform\": release.platform,\n        \"classifiers\": list(release.classifiers),\n        \"requires\": list(release.requires),\n        \"requires_dist\": list(release.requires_dist),\n        \"provides\": list(release.provides),\n        \"provides_dist\": list(release.provides_dist),\n        \"obsoletes\": list(release.obsoletes),\n        \"obsoletes_dist\": list(release.obsoletes_dist),\n        \"requires_python\": release.requires_python,\n        \"requires_external\": list(release.requires_external),\n        \"_pypi_ordering\": release._pypi_ordering,\n        \"_pypi_hidden\": release._pypi_hidden,\n        \"downloads\": {\n            \"last_day\": stats_svc.get_daily_stats(release.project.name),\n            \"last_week\": stats_svc.get_weekly_stats(release.project.name),\n            \"last_month\": stats_svc.get_monthly_stats(release.project.name),\n        },\n    }\n\n\n@pypi_xmlrpc(method=\"release_urls\")\ndef release_urls(request, package_name, version):\n    files = (\n        request.db.query(File)\n                  .join(Release, Project)\n                  .filter((Project.normalized_name ==\n                           func.normalize_pep426_name(package_name)) &\n                          (Release.version == version))\n                  .all()\n    )\n\n    return [\n        {\n            \"filename\": f.filename,\n            \"packagetype\": f.packagetype,\n            \"python_version\": f.python_version,\n            \"size\": f.size,\n            \"md5_digest\": f.md5_digest,\n            \"digests\": {\n                \"md5\": f.md5_digest,\n                \"sha256\": f.sha256_digest,\n            },\n            \"has_sig\": f.has_signature,\n            \"upload_time\": f.upload_time,\n            \"comment_text\": f.comment_text,\n            # TODO: Remove this once we've had a long enough time with it\n            #       here to consider it no longer in use.\n            \"downloads\": -1,\n            \"url\": request.route_url(\"packaging.file\", path=f.path),\n        }\n        for f in files\n    ]\n\n\n@pypi_xmlrpc(method=\"package_roles\")\ndef package_roles(request, package_name):\n    roles = (\n        request.db.query(Role)\n                  .join(User, Project)\n                  .filter(Project.normalized_name ==\n                          func.normalize_pep426_name(package_name))\n                  .order_by(Role.role_name.desc(), User.username)\n                  .all()\n    )\n    return [(r.role_name, r.user.username) for r in roles]\n\n\n@pypi_xmlrpc(method=\"changelog_last_serial\")\ndef changelog_last_serial(request):\n    return request.db.query(func.max(JournalEntry.id)).scalar()\n\n\n@pypi_xmlrpc(method=\"changelog_since_serial\")\ndef changelog_since_serial(request, serial):\n    entries = (\n        request.db.query(JournalEntry)\n                  .filter(JournalEntry.id > serial)\n                  .order_by(JournalEntry.id)\n                  .limit(50000)\n    )\n\n    return [\n        (\n            e.name,\n            e.version,\n            int(\n                e.submitted_date\n                 .replace(tzinfo=datetime.timezone.utc)\n                 .timestamp()\n            ),\n            e.action,\n            e.id,\n        )\n        for e in entries\n    ]\n\n\n@pypi_xmlrpc(method=\"changelog\")\ndef changelog(request, since, with_ids=False):\n    since = datetime.datetime.utcfromtimestamp(since)\n    entries = (\n        request.db.query(JournalEntry)\n                  .filter(JournalEntry.submitted_date > since)\n                  .order_by(JournalEntry.submitted_date)\n                  .limit(50000)\n    )\n\n    results = (\n        (\n            e.name,\n            e.version,\n            int(\n                e.submitted_date\n                 .replace(tzinfo=datetime.timezone.utc)\n                 .timestamp()\n            ),\n            e.action,\n            e.id,\n        )\n        for e in entries\n    )\n\n    if with_ids:\n        return list(results)\n    else:\n        return [r[:-1] for r in results]\n\n\n@pypi_xmlrpc(method=\"browse\")\ndef browse(request, classifiers):\n    classifiers_q = (\n        request.db.query(Classifier)\n               .filter(Classifier.classifier.in_(classifiers))\n               .subquery()\n    )\n\n    release_classifiers_q = (\n        select([release_classifiers])\n        .where(release_classifiers.c.trove_id == classifiers_q.c.id)\n        .alias(\"rc\")\n    )\n\n    releases = (\n        request.db.query(Release.name, Release.version)\n                  .join(release_classifiers_q,\n                        (Release.name == release_classifiers_q.c.name) &\n                        (Release.version == release_classifiers_q.c.version))\n                  .group_by(Release.name, Release.version)\n                  .having(func.count() == len(classifiers))\n                  .order_by(Release.name, Release.version)\n                  .all()\n    )\n\n    return [(r.name, r.version) for r in releases]\n", "path": "warehouse/legacy/api/xmlrpc.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 collections.abc\nimport datetime\nimport functools\nimport xmlrpc.server\n\nfrom elasticsearch_dsl import Q\nfrom pyramid.view import view_config\nfrom pyramid_rpc.xmlrpc import exception_view as _exception_view, xmlrpc_method\nfrom sqlalchemy import func, select\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom warehouse.accounts.models import User\nfrom warehouse.classifiers.models import Classifier\nfrom warehouse.packaging.interfaces import IDownloadStatService\nfrom warehouse.packaging.models import (\n    Role, Project, Release, File, JournalEntry, release_classifiers,\n)\n\n\npypi_xmlrpc = functools.partial(\n    xmlrpc_method,\n    endpoint=\"pypi\",\n    require_csrf=False,\n    require_methods=[\"POST\"],\n)\n\n\nclass XMLRPCWrappedError(xmlrpc.server.Fault):\n\n    def __init__(self, exc):\n        self.faultCode = -32500\n        self.wrapped_exception = exc\n\n    @property\n    def faultString(self):  # noqa\n        return \"{exc.__class__.__name__}: {exc}\".format(\n            exc=self.wrapped_exception,\n        )\n\n\n@view_config(route_name=\"pypi\", context=Exception, renderer=\"xmlrpc\")\ndef exception_view(exc, request):\n    return _exception_view(exc, request)\n\n\n@pypi_xmlrpc(method=\"search\")\ndef search(request, spec, operator=\"and\"):\n    if not isinstance(spec, collections.abc.Mapping):\n        raise XMLRPCWrappedError(\n            TypeError(\"Invalid spec, must be a mapping/dictionary.\")\n        )\n\n    if operator not in {\"and\", \"or\"}:\n        raise XMLRPCWrappedError(\n            ValueError(\"Invalid operator, must be one of 'and' or 'or'.\")\n        )\n\n    # Remove any invalid spec fields\n    spec = {\n        k: [v] if isinstance(v, str) else v\n        for k, v in spec.items()\n        if v and k in {\n            \"name\", \"version\", \"author\", \"author_email\", \"maintainer\",\n            \"maintainer_email\", \"home_page\", \"license\", \"summary\",\n            \"description\", \"keywords\", \"platform\", \"download_url\",\n        }\n    }\n\n    queries = []\n    for field, value in sorted(spec.items()):\n        q = None\n        for item in value:\n            if q is None:\n                q = Q(\"term\", **{field: item})\n            else:\n                q |= Q(\"term\", **{field: item})\n        queries.append(q)\n\n    if operator == \"and\":\n        query = request.es.query(\"bool\", must=queries)\n    else:\n        query = request.es.query(\"bool\", should=queries)\n\n    results = query[:1000].execute()\n\n    return [\n        {\"name\": r.name, \"summary\": r.summary, \"version\": v}\n        for r in results\n        for v in r.version\n        if v in spec.get(\"version\", [v])\n    ]\n\n\n@pypi_xmlrpc(method=\"list_packages\")\ndef list_packages(request):\n    names = request.db.query(Project.name).order_by(Project.name).all()\n    return [n[0] for n in names]\n\n\n@pypi_xmlrpc(method=\"list_packages_with_serial\")\ndef list_packages_with_serial(request):\n    serials = request.db.query(Project.name, Project.last_serial).all()\n    return dict((serial[0], serial[1]) for serial in serials)\n\n\n@pypi_xmlrpc(method=\"package_hosting_mode\")\ndef package_hosting_mode(request, package_name):\n    try:\n        project = (\n            request.db.query(Project)\n                      .filter(Project.normalized_name ==\n                              func.normalize_pep426_name(package_name))\n                      .one()\n        )\n    except NoResultFound:\n        return None\n    else:\n        return project.hosting_mode\n\n\n@pypi_xmlrpc(method=\"user_packages\")\ndef user_packages(request, username):\n    roles = (\n        request.db.query(Role)\n                  .join(User, Project)\n                  .filter(User.username == username)\n                  .order_by(Role.role_name.desc(), Project.name)\n                  .all()\n    )\n    return [(r.role_name, r.project.name) for r in roles]\n\n\n@pypi_xmlrpc(method=\"top_packages\")\ndef top_packages(request, num=None):\n    raise XMLRPCWrappedError(\n        RuntimeError(\"This API has been removed. Please Use BigQuery instead.\")\n    )\n\n\n@pypi_xmlrpc(method=\"package_releases\")\ndef package_releases(request, package_name, show_hidden=False):\n    # This used to support the show_hidden parameter to determine if it should\n    # show hidden releases or not. However, Warehouse doesn't support the\n    # concept of hidden releases, so it is just no-opd now and left here for\n    # compatibility's sake.\n    versions = (\n        request.db.query(Release.version)\n                  .join(Project)\n                  .filter(Project.normalized_name ==\n                          func.normalize_pep426_name(package_name))\n                  .order_by(Release._pypi_ordering)\n                  .all()\n    )\n    return [v[0] for v in versions]\n\n\n@pypi_xmlrpc(method=\"release_data\")\ndef release_data(request, package_name, version):\n    try:\n        release = (\n            request.db.query(Release)\n                      .join(Project)\n                      .filter((Project.normalized_name ==\n                               func.normalize_pep426_name(package_name)) &\n                              (Release.version == version))\n                      .one()\n        )\n    except NoResultFound:\n        return {}\n\n    stats_svc = request.find_service(IDownloadStatService)\n\n    return {\n        \"name\": release.project.name,\n        \"version\": release.version,\n        \"stable_version\": release.project.stable_version,\n        \"bugtrack_url\": release.project.bugtrack_url,\n        \"package_url\": request.route_url(\n            \"packaging.project\",\n            name=release.project.name,\n        ),\n        \"release_url\": request.route_url(\n            \"packaging.release\",\n            name=release.project.name,\n            version=release.version,\n        ),\n        \"docs_url\": release.project.documentation_url,\n        \"home_page\": release.home_page,\n        \"download_url\": release.download_url,\n        \"project_url\": list(release.project_urls),\n        \"author\": release.author,\n        \"author_email\": release.author_email,\n        \"maintainer\": release.maintainer,\n        \"maintainer_email\": release.maintainer_email,\n        \"summary\": release.summary,\n        \"description\": release.description,\n        \"license\": release.license,\n        \"keywords\": release.keywords,\n        \"platform\": release.platform,\n        \"classifiers\": list(release.classifiers),\n        \"requires\": list(release.requires),\n        \"requires_dist\": list(release.requires_dist),\n        \"provides\": list(release.provides),\n        \"provides_dist\": list(release.provides_dist),\n        \"obsoletes\": list(release.obsoletes),\n        \"obsoletes_dist\": list(release.obsoletes_dist),\n        \"requires_python\": release.requires_python,\n        \"requires_external\": list(release.requires_external),\n        \"_pypi_ordering\": release._pypi_ordering,\n        \"_pypi_hidden\": release._pypi_hidden,\n        \"downloads\": {\n            \"last_day\": stats_svc.get_daily_stats(release.project.name),\n            \"last_week\": stats_svc.get_weekly_stats(release.project.name),\n            \"last_month\": stats_svc.get_monthly_stats(release.project.name),\n        },\n    }\n\n\n@pypi_xmlrpc(method=\"release_urls\")\ndef release_urls(request, package_name, version):\n    files = (\n        request.db.query(File)\n                  .join(Release, Project)\n                  .filter((Project.normalized_name ==\n                           func.normalize_pep426_name(package_name)) &\n                          (Release.version == version))\n                  .all()\n    )\n\n    return [\n        {\n            \"filename\": f.filename,\n            \"packagetype\": f.packagetype,\n            \"python_version\": f.python_version,\n            \"size\": f.size,\n            \"md5_digest\": f.md5_digest,\n            \"digests\": {\n                \"md5\": f.md5_digest,\n                \"sha256\": f.sha256_digest,\n            },\n            \"has_sig\": f.has_signature,\n            \"upload_time\": f.upload_time,\n            \"comment_text\": f.comment_text,\n            # TODO: Remove this once we've had a long enough time with it\n            #       here to consider it no longer in use.\n            \"downloads\": -1,\n            \"url\": request.route_url(\"packaging.file\", path=f.path),\n        }\n        for f in files\n    ]\n\n\n@pypi_xmlrpc(method=\"package_roles\")\ndef package_roles(request, package_name):\n    roles = (\n        request.db.query(Role)\n                  .join(User, Project)\n                  .filter(Project.normalized_name ==\n                          func.normalize_pep426_name(package_name))\n                  .order_by(Role.role_name.desc(), User.username)\n                  .all()\n    )\n    return [(r.role_name, r.user.username) for r in roles]\n\n\n@pypi_xmlrpc(method=\"changelog_last_serial\")\ndef changelog_last_serial(request):\n    return request.db.query(func.max(JournalEntry.id)).scalar()\n\n\n@pypi_xmlrpc(method=\"changelog_since_serial\")\ndef changelog_since_serial(request, serial):\n    entries = (\n        request.db.query(JournalEntry)\n                  .filter(JournalEntry.id > serial)\n                  .order_by(JournalEntry.id)\n                  .limit(50000)\n    )\n\n    return [\n        (\n            e.name,\n            e.version,\n            int(\n                e.submitted_date\n                 .replace(tzinfo=datetime.timezone.utc)\n                 .timestamp()\n            ),\n            e.action,\n            e.id,\n        )\n        for e in entries\n    ]\n\n\n@pypi_xmlrpc(method=\"changelog\")\ndef changelog(request, since, with_ids=False):\n    since = datetime.datetime.utcfromtimestamp(since)\n    entries = (\n        request.db.query(JournalEntry)\n                  .filter(JournalEntry.submitted_date > since)\n                  .order_by(JournalEntry.submitted_date)\n                  .limit(50000)\n    )\n\n    results = (\n        (\n            e.name,\n            e.version,\n            int(\n                e.submitted_date\n                 .replace(tzinfo=datetime.timezone.utc)\n                 .timestamp()\n            ),\n            e.action,\n            e.id,\n        )\n        for e in entries\n    )\n\n    if with_ids:\n        return list(results)\n    else:\n        return [r[:-1] for r in results]\n\n\n@pypi_xmlrpc(method=\"browse\")\ndef browse(request, classifiers):\n    classifiers_q = (\n        request.db.query(Classifier)\n               .filter(Classifier.classifier.in_(classifiers))\n               .subquery()\n    )\n\n    release_classifiers_q = (\n        select([release_classifiers])\n        .where(release_classifiers.c.trove_id == classifiers_q.c.id)\n        .alias(\"rc\")\n    )\n\n    releases = (\n        request.db.query(Release.name, Release.version)\n                  .join(release_classifiers_q,\n                        (Release.name == release_classifiers_q.c.name) &\n                        (Release.version == release_classifiers_q.c.version))\n                  .group_by(Release.name, Release.version)\n                  .having(func.count() == len(classifiers))\n                  .order_by(Release.name, Release.version)\n                  .all()\n    )\n\n    return [(r.name, r.version) for r in releases]\n", "path": "warehouse/legacy/api/xmlrpc.py"}]}