We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Expose long_description_content_type in JSON API
Feature request: Add  'long_description_content_type' to JSON API in 'info': section, alongside the existing 'description'

Now that PyPI supports Markdown and more than one kind of content type for long descriptions, it would be helpful to be able to see what the content type is with the JSON API. Otherwise it is more difficult to correctly interpret the 'description', since it isn't immediately obvious if it's reStructuredText or Markdown with which variant.

I'm willing to try this if I'm pointed in the right general direction :) If someone else can get it done trivially that's fine too.



--- 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/json.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 from pyramid.httpexceptions import HTTPMovedPermanently, HTTPNotFound
14 from pyramid.view import view_config
15 from sqlalchemy.orm import Load
16 from sqlalchemy.orm.exc import NoResultFound
17 
18 from warehouse.cache.http import cache_control
19 from warehouse.cache.origin import origin_cache
20 from warehouse.packaging.models import File, Release, Project
21 
22 
23 # Generate appropriate CORS headers for the JSON endpoint.
24 # We want to allow Cross-Origin requests here so that users can interact
25 # with these endpoints via XHR/Fetch APIs in the browser.
26 _CORS_HEADERS = {
27     "Access-Control-Allow-Origin": "*",
28     "Access-Control-Allow-Headers": ", ".join([
29         "Content-Type",
30         "If-Match",
31         "If-Modified-Since",
32         "If-None-Match",
33         "If-Unmodified-Since",
34     ]),
35     "Access-Control-Allow-Methods": "GET",
36     "Access-Control-Max-Age": "86400",  # 1 day.
37     "Access-Control-Expose-Headers": ", ".join([
38         "X-PyPI-Last-Serial",
39     ]),
40 }
41 
42 
43 @view_config(
44     route_name="legacy.api.json.project",
45     context=Project,
46     renderer="json",
47     decorator=[
48         cache_control(15 * 60),               # 15 minutes
49         origin_cache(
50             1 * 24 * 60 * 60,                 # 1 day
51             stale_while_revalidate=5 * 60,    # 5 minutes
52             stale_if_error=1 * 24 * 60 * 60,  # 1 day
53         ),
54     ],
55 )
56 def json_project(project, request):
57     if project.name != request.matchdict.get("name", project.name):
58         return HTTPMovedPermanently(
59             request.current_route_path(name=project.name),
60             headers=_CORS_HEADERS
61         )
62 
63     try:
64         release = (
65             request.db.query(Release)
66                       .filter(Release.project == project)
67                       .order_by(
68                           Release.is_prerelease.nullslast(),
69                           Release._pypi_ordering.desc())
70                       .limit(1)
71                       .one()
72         )
73     except NoResultFound:
74         return HTTPNotFound(headers=_CORS_HEADERS)
75 
76     return json_release(release, request)
77 
78 
79 @view_config(
80     route_name="legacy.api.json.release",
81     context=Release,
82     renderer="json",
83     decorator=[
84         cache_control(15 * 60),               # 15 minutes
85         origin_cache(
86             1 * 24 * 60 * 60,                 # 1 day
87             stale_while_revalidate=5 * 60,    # 5 minutes
88             stale_if_error=1 * 24 * 60 * 60,  # 1 day
89         ),
90     ],
91 )
92 def json_release(release, request):
93     project = release.project
94 
95     if project.name != request.matchdict.get("name", project.name):
96         return HTTPMovedPermanently(
97             request.current_route_path(name=project.name),
98             headers=_CORS_HEADERS
99         )
100 
101     # Apply CORS headers.
102     request.response.headers.update(_CORS_HEADERS)
103 
104     # Get the latest serial number for this project.
105     request.response.headers["X-PyPI-Last-Serial"] = str(project.last_serial)
106 
107     # Get all of the releases and files for this project.
108     release_files = (
109         request.db.query(Release, File)
110                .options(Load(Release).load_only('version'))
111                .outerjoin(File)
112                .filter(Release.project == project)
113                .order_by(Release._pypi_ordering.desc(), File.filename)
114                .all()
115     )
116 
117     # Map our releases + files into a dictionary that maps each release to a
118     # list of all its files.
119     releases = {}
120     for r, file_ in release_files:
121         files = releases.setdefault(r, [])
122         if file_ is not None:
123             files.append(file_)
124 
125     # Serialize our database objects to match the way that PyPI legacy
126     # presented this data.
127     releases = {
128         r.version: [
129             {
130                 "filename": f.filename,
131                 "packagetype": f.packagetype,
132                 "python_version": f.python_version,
133                 "has_sig": f.has_signature,
134                 "comment_text": f.comment_text,
135                 "md5_digest": f.md5_digest,
136                 "digests": {
137                     "md5": f.md5_digest,
138                     "sha256": f.sha256_digest,
139                 },
140                 "size": f.size,
141                 # TODO: Remove this once we've had a long enough time with it
142                 #       here to consider it no longer in use.
143                 "downloads": -1,
144                 "upload_time": f.upload_time.strftime("%Y-%m-%dT%H:%M:%S"),
145                 "url": request.route_url("packaging.file", path=f.path),
146             }
147             for f in fs
148         ]
149         for r, fs in releases.items()
150     }
151 
152     return {
153         "info": {
154             "name": project.name,
155             "version": release.version,
156             "summary": release.summary,
157             "description": release.description,
158             "keywords": release.keywords,
159             "license": release.license,
160             "classifiers": list(release.classifiers),
161             "author": release.author,
162             "author_email": release.author_email,
163             "maintainer": release.maintainer,
164             "maintainer_email": release.maintainer_email,
165             "requires_python": release.requires_python,
166             "platform": release.platform,
167             "downloads": {
168                 "last_day": -1,
169                 "last_week": -1,
170                 "last_month": -1,
171             },
172             "package_url": request.route_url(
173                 "packaging.project",
174                 name=project.name,
175             ),
176             "project_url": request.route_url(
177                 "packaging.project",
178                 name=project.name,
179             ),
180             "release_url": request.route_url(
181                 "packaging.release",
182                 name=project.name,
183                 version=release.version,
184             ),
185             "requires_dist": (list(release.requires_dist)
186                               if release.requires_dist else None),
187             "docs_url": project.documentation_url,
188             "bugtrack_url": project.bugtrack_url,
189             "home_page": release.home_page,
190             "download_url": release.download_url,
191         },
192         "urls": releases[release.version],
193         "releases": releases,
194         "last_serial": project.last_serial,
195     }
196
```
--- 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/json.py b/warehouse/legacy/api/json.py
--- a/warehouse/legacy/api/json.py
+++ b/warehouse/legacy/api/json.py
@@ -154,6 +154,7 @@
             "name": project.name,
             "version": release.version,
             "summary": release.summary,
+            "description_content_type": release.description_content_type,
             "description": release.description,
             "keywords": release.keywords,
             "license": release.license,

{"golden_diff": "diff --git a/warehouse/legacy/api/json.py b/warehouse/legacy/api/json.py\n--- a/warehouse/legacy/api/json.py\n+++ b/warehouse/legacy/api/json.py\n@@ -154,6 +154,7 @@\n             \"name\": project.name,\n             \"version\": release.version,\n             \"summary\": release.summary,\n+            \"description_content_type\": release.description_content_type,\n             \"description\": release.description,\n             \"keywords\": release.keywords,\n             \"license\": release.license,\n", "issue": "Expose long_description_content_type in JSON API\nFeature request: Add  'long_description_content_type' to JSON API in 'info': section, alongside the existing 'description'\r\n\r\nNow that PyPI supports Markdown and more than one kind of content type for long descriptions, it would be helpful to be able to see what the content type is with the JSON API. Otherwise it is more difficult to correctly interpret the 'description', since it isn't immediately obvious if it's reStructuredText or Markdown with which variant.\r\n\r\nI'm willing to try this if I'm pointed in the right general direction :) If someone else can get it done trivially that's fine too.\r\n\r\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\nfrom pyramid.httpexceptions import HTTPMovedPermanently, HTTPNotFound\nfrom pyramid.view import view_config\nfrom sqlalchemy.orm import Load\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom warehouse.cache.http import cache_control\nfrom warehouse.cache.origin import origin_cache\nfrom warehouse.packaging.models import File, Release, Project\n\n\n# Generate appropriate CORS headers for the JSON endpoint.\n# We want to allow Cross-Origin requests here so that users can interact\n# with these endpoints via XHR/Fetch APIs in the browser.\n_CORS_HEADERS = {\n    \"Access-Control-Allow-Origin\": \"*\",\n    \"Access-Control-Allow-Headers\": \", \".join([\n        \"Content-Type\",\n        \"If-Match\",\n        \"If-Modified-Since\",\n        \"If-None-Match\",\n        \"If-Unmodified-Since\",\n    ]),\n    \"Access-Control-Allow-Methods\": \"GET\",\n    \"Access-Control-Max-Age\": \"86400\",  # 1 day.\n    \"Access-Control-Expose-Headers\": \", \".join([\n        \"X-PyPI-Last-Serial\",\n    ]),\n}\n\n\n@view_config(\n    route_name=\"legacy.api.json.project\",\n    context=Project,\n    renderer=\"json\",\n    decorator=[\n        cache_control(15 * 60),               # 15 minutes\n        origin_cache(\n            1 * 24 * 60 * 60,                 # 1 day\n            stale_while_revalidate=5 * 60,    # 5 minutes\n            stale_if_error=1 * 24 * 60 * 60,  # 1 day\n        ),\n    ],\n)\ndef json_project(project, request):\n    if project.name != request.matchdict.get(\"name\", project.name):\n        return HTTPMovedPermanently(\n            request.current_route_path(name=project.name),\n            headers=_CORS_HEADERS\n        )\n\n    try:\n        release = (\n            request.db.query(Release)\n                      .filter(Release.project == project)\n                      .order_by(\n                          Release.is_prerelease.nullslast(),\n                          Release._pypi_ordering.desc())\n                      .limit(1)\n                      .one()\n        )\n    except NoResultFound:\n        return HTTPNotFound(headers=_CORS_HEADERS)\n\n    return json_release(release, request)\n\n\n@view_config(\n    route_name=\"legacy.api.json.release\",\n    context=Release,\n    renderer=\"json\",\n    decorator=[\n        cache_control(15 * 60),               # 15 minutes\n        origin_cache(\n            1 * 24 * 60 * 60,                 # 1 day\n            stale_while_revalidate=5 * 60,    # 5 minutes\n            stale_if_error=1 * 24 * 60 * 60,  # 1 day\n        ),\n    ],\n)\ndef json_release(release, request):\n    project = release.project\n\n    if project.name != request.matchdict.get(\"name\", project.name):\n        return HTTPMovedPermanently(\n            request.current_route_path(name=project.name),\n            headers=_CORS_HEADERS\n        )\n\n    # Apply CORS headers.\n    request.response.headers.update(_CORS_HEADERS)\n\n    # Get the latest serial number for this project.\n    request.response.headers[\"X-PyPI-Last-Serial\"] = str(project.last_serial)\n\n    # Get all of the releases and files for this project.\n    release_files = (\n        request.db.query(Release, File)\n               .options(Load(Release).load_only('version'))\n               .outerjoin(File)\n               .filter(Release.project == project)\n               .order_by(Release._pypi_ordering.desc(), File.filename)\n               .all()\n    )\n\n    # Map our releases + files into a dictionary that maps each release to a\n    # list of all its files.\n    releases = {}\n    for r, file_ in release_files:\n        files = releases.setdefault(r, [])\n        if file_ is not None:\n            files.append(file_)\n\n    # Serialize our database objects to match the way that PyPI legacy\n    # presented this data.\n    releases = {\n        r.version: [\n            {\n                \"filename\": f.filename,\n                \"packagetype\": f.packagetype,\n                \"python_version\": f.python_version,\n                \"has_sig\": f.has_signature,\n                \"comment_text\": f.comment_text,\n                \"md5_digest\": f.md5_digest,\n                \"digests\": {\n                    \"md5\": f.md5_digest,\n                    \"sha256\": f.sha256_digest,\n                },\n                \"size\": f.size,\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                \"upload_time\": f.upload_time.strftime(\"%Y-%m-%dT%H:%M:%S\"),\n                \"url\": request.route_url(\"packaging.file\", path=f.path),\n            }\n            for f in fs\n        ]\n        for r, fs in releases.items()\n    }\n\n    return {\n        \"info\": {\n            \"name\": project.name,\n            \"version\": release.version,\n            \"summary\": release.summary,\n            \"description\": release.description,\n            \"keywords\": release.keywords,\n            \"license\": release.license,\n            \"classifiers\": list(release.classifiers),\n            \"author\": release.author,\n            \"author_email\": release.author_email,\n            \"maintainer\": release.maintainer,\n            \"maintainer_email\": release.maintainer_email,\n            \"requires_python\": release.requires_python,\n            \"platform\": release.platform,\n            \"downloads\": {\n                \"last_day\": -1,\n                \"last_week\": -1,\n                \"last_month\": -1,\n            },\n            \"package_url\": request.route_url(\n                \"packaging.project\",\n                name=project.name,\n            ),\n            \"project_url\": request.route_url(\n                \"packaging.project\",\n                name=project.name,\n            ),\n            \"release_url\": request.route_url(\n                \"packaging.release\",\n                name=project.name,\n                version=release.version,\n            ),\n            \"requires_dist\": (list(release.requires_dist)\n                              if release.requires_dist else None),\n            \"docs_url\": project.documentation_url,\n            \"bugtrack_url\": project.bugtrack_url,\n            \"home_page\": release.home_page,\n            \"download_url\": release.download_url,\n        },\n        \"urls\": releases[release.version],\n        \"releases\": releases,\n        \"last_serial\": project.last_serial,\n    }\n", "path": "warehouse/legacy/api/json.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\nfrom pyramid.httpexceptions import HTTPMovedPermanently, HTTPNotFound\nfrom pyramid.view import view_config\nfrom sqlalchemy.orm import Load\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom warehouse.cache.http import cache_control\nfrom warehouse.cache.origin import origin_cache\nfrom warehouse.packaging.models import File, Release, Project\n\n\n# Generate appropriate CORS headers for the JSON endpoint.\n# We want to allow Cross-Origin requests here so that users can interact\n# with these endpoints via XHR/Fetch APIs in the browser.\n_CORS_HEADERS = {\n    \"Access-Control-Allow-Origin\": \"*\",\n    \"Access-Control-Allow-Headers\": \", \".join([\n        \"Content-Type\",\n        \"If-Match\",\n        \"If-Modified-Since\",\n        \"If-None-Match\",\n        \"If-Unmodified-Since\",\n    ]),\n    \"Access-Control-Allow-Methods\": \"GET\",\n    \"Access-Control-Max-Age\": \"86400\",  # 1 day.\n    \"Access-Control-Expose-Headers\": \", \".join([\n        \"X-PyPI-Last-Serial\",\n    ]),\n}\n\n\n@view_config(\n    route_name=\"legacy.api.json.project\",\n    context=Project,\n    renderer=\"json\",\n    decorator=[\n        cache_control(15 * 60),               # 15 minutes\n        origin_cache(\n            1 * 24 * 60 * 60,                 # 1 day\n            stale_while_revalidate=5 * 60,    # 5 minutes\n            stale_if_error=1 * 24 * 60 * 60,  # 1 day\n        ),\n    ],\n)\ndef json_project(project, request):\n    if project.name != request.matchdict.get(\"name\", project.name):\n        return HTTPMovedPermanently(\n            request.current_route_path(name=project.name),\n            headers=_CORS_HEADERS\n        )\n\n    try:\n        release = (\n            request.db.query(Release)\n                      .filter(Release.project == project)\n                      .order_by(\n                          Release.is_prerelease.nullslast(),\n                          Release._pypi_ordering.desc())\n                      .limit(1)\n                      .one()\n        )\n    except NoResultFound:\n        return HTTPNotFound(headers=_CORS_HEADERS)\n\n    return json_release(release, request)\n\n\n@view_config(\n    route_name=\"legacy.api.json.release\",\n    context=Release,\n    renderer=\"json\",\n    decorator=[\n        cache_control(15 * 60),               # 15 minutes\n        origin_cache(\n            1 * 24 * 60 * 60,                 # 1 day\n            stale_while_revalidate=5 * 60,    # 5 minutes\n            stale_if_error=1 * 24 * 60 * 60,  # 1 day\n        ),\n    ],\n)\ndef json_release(release, request):\n    project = release.project\n\n    if project.name != request.matchdict.get(\"name\", project.name):\n        return HTTPMovedPermanently(\n            request.current_route_path(name=project.name),\n            headers=_CORS_HEADERS\n        )\n\n    # Apply CORS headers.\n    request.response.headers.update(_CORS_HEADERS)\n\n    # Get the latest serial number for this project.\n    request.response.headers[\"X-PyPI-Last-Serial\"] = str(project.last_serial)\n\n    # Get all of the releases and files for this project.\n    release_files = (\n        request.db.query(Release, File)\n               .options(Load(Release).load_only('version'))\n               .outerjoin(File)\n               .filter(Release.project == project)\n               .order_by(Release._pypi_ordering.desc(), File.filename)\n               .all()\n    )\n\n    # Map our releases + files into a dictionary that maps each release to a\n    # list of all its files.\n    releases = {}\n    for r, file_ in release_files:\n        files = releases.setdefault(r, [])\n        if file_ is not None:\n            files.append(file_)\n\n    # Serialize our database objects to match the way that PyPI legacy\n    # presented this data.\n    releases = {\n        r.version: [\n            {\n                \"filename\": f.filename,\n                \"packagetype\": f.packagetype,\n                \"python_version\": f.python_version,\n                \"has_sig\": f.has_signature,\n                \"comment_text\": f.comment_text,\n                \"md5_digest\": f.md5_digest,\n                \"digests\": {\n                    \"md5\": f.md5_digest,\n                    \"sha256\": f.sha256_digest,\n                },\n                \"size\": f.size,\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                \"upload_time\": f.upload_time.strftime(\"%Y-%m-%dT%H:%M:%S\"),\n                \"url\": request.route_url(\"packaging.file\", path=f.path),\n            }\n            for f in fs\n        ]\n        for r, fs in releases.items()\n    }\n\n    return {\n        \"info\": {\n            \"name\": project.name,\n            \"version\": release.version,\n            \"summary\": release.summary,\n            \"description_content_type\": release.description_content_type,\n            \"description\": release.description,\n            \"keywords\": release.keywords,\n            \"license\": release.license,\n            \"classifiers\": list(release.classifiers),\n            \"author\": release.author,\n            \"author_email\": release.author_email,\n            \"maintainer\": release.maintainer,\n            \"maintainer_email\": release.maintainer_email,\n            \"requires_python\": release.requires_python,\n            \"platform\": release.platform,\n            \"downloads\": {\n                \"last_day\": -1,\n                \"last_week\": -1,\n                \"last_month\": -1,\n            },\n            \"package_url\": request.route_url(\n                \"packaging.project\",\n                name=project.name,\n            ),\n            \"project_url\": request.route_url(\n                \"packaging.project\",\n                name=project.name,\n            ),\n            \"release_url\": request.route_url(\n                \"packaging.release\",\n                name=project.name,\n                version=release.version,\n            ),\n            \"requires_dist\": (list(release.requires_dist)\n                              if release.requires_dist else None),\n            \"docs_url\": project.documentation_url,\n            \"bugtrack_url\": project.bugtrack_url,\n            \"home_page\": release.home_page,\n            \"download_url\": release.download_url,\n        },\n        \"urls\": releases[release.version],\n        \"releases\": releases,\n        \"last_serial\": project.last_serial,\n    }\n", "path": "warehouse/legacy/api/json.py"}]}