We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[DOC] Missing Pyjanitor description on PyPi
# Brief Description of Fix

Since `pyjanitor` release `0.22.0`, there has not been a ["Project description" for the package on PyPi](https://pypi.org/project/pyjanitor/0.22.0/#description).

During the release of of `0.24.0`, @samukweku and I got an error during the release process due to:

```
Checking dist/pyjanitor-0.24.0-py3-none-any.whl: FAILED
ERROR    `long_description` has syntax errors in markup and would not be        
         rendered on PyPI.                                                      
         No content rendered from RST source.     
```
Our guess is that we had switched the README from RST to MD some time ago, causing the `long_description` to quietly fail, and now the `gh-action-pypi-publish` GitHub action would no longer accept the bad `long_description`.

We updated the `long_description_content_type` in `setup.py` to markdown  in #1197  and the package was successfully published to PyPi, but there is still no Project description. So there must still be something that is not being generated correctly.

We need to verify that `long_description` is properly being generated. We should test with local tools *and* https://test.pypi.org/ to verify that this is fixed.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `setup.py`
Content:
```
1 """Setup script."""
2 import codecs
3 import os
4 import re
5 from pathlib import Path
6 from pprint import pprint
7 
8 from setuptools import find_packages, setup
9 
10 HERE = os.path.abspath(os.path.dirname(__file__))
11 
12 
13 def read(*parts):
14     # intentionally *not* adding an encoding option to open
15     return codecs.open(os.path.join(HERE, *parts), "r").read()
16 
17 
18 def read_requirements(*parts):
19     """
20     Return requirements from parts.
21 
22     Given a requirements.txt (or similar style file),
23     returns a list of requirements.
24     Assumes anything after a single '#' on a line is a comment, and ignores
25     empty lines.
26 
27     :param parts: list of filenames which contain the installation "parts",
28         i.e. submodule-specific installation requirements
29     :returns: A compiled list of requirements.
30     """
31     requirements = []
32     for line in read(*parts).splitlines():
33         new_line = re.sub(  # noqa: PD005
34             r"(\s*)?#.*$",  # the space immediately before the
35             # hash mark, the hash mark, and
36             # anything that follows it
37             "",  # replace with a blank string
38             line,
39         )
40         new_line = re.sub(  # noqa: PD005
41             r"-r.*$",  # link to another requirement file
42             "",  # replace with a blank string
43             new_line,
44         )
45         new_line = re.sub(  # noqa: PD005
46             r"-e \..*$",  # link to editable install
47             "",  # replace with a blank string
48             new_line,
49         )
50         # print(line, "-->", new_line)
51         if new_line:  # i.e. we have a non-zero-length string
52             requirements.append(new_line)
53     return requirements
54 
55 
56 # pull from requirements.IN, requirements.TXT is generated from this
57 INSTALL_REQUIRES = read_requirements(".requirements/base.in")
58 
59 EXTRA_REQUIRES = {
60     "dev": read_requirements(".requirements/dev.in"),
61     "docs": read_requirements(".requirements/docs.in"),
62     "test": read_requirements(".requirements/testing.in"),
63     "biology": read_requirements(".requirements/biology.in"),
64     "chemistry": read_requirements(".requirements/chemistry.in"),
65     "engineering": read_requirements(".requirements/engineering.in"),
66     "spark": read_requirements(".requirements/spark.in"),
67 }
68 
69 # add 'all' key to EXTRA_REQUIRES
70 all_requires = []
71 for k, v in EXTRA_REQUIRES.items():
72     all_requires.extend(v)
73 EXTRA_REQUIRES["all"] = set(all_requires)
74 
75 for k1 in ["biology", "chemistry", "engineering", "spark"]:
76     for v2 in EXTRA_REQUIRES[k1]:
77         EXTRA_REQUIRES["docs"].append(v2)
78 
79 pprint(EXTRA_REQUIRES)
80 
81 
82 def generate_long_description() -> str:
83     """
84     Extra chunks from README for PyPI description.
85 
86     Target chunks must be contained within `.. pypi-doc` pair comments,
87     so there must be an even number of comments in README.
88 
89     :returns: Extracted description from README.
90     :raises Exception: if odd number of `.. pypi-doc` comments
91         in README.
92     """
93     # Read the contents of README file
94     this_directory = Path(__file__).parent
95     with open(this_directory / "mkdocs" / "index.md", encoding="utf-8") as f:
96         readme = f.read()
97 
98     # Find pypi-doc comments in README
99     indices = [m.start() for m in re.finditer(".. pypi-doc", readme)]
100     if len(indices) % 2 != 0:
101         raise Exception("Odd number of `.. pypi-doc` comments in README")
102 
103     # Loop through pairs of comments and save text between pairs
104     long_description = ""
105     for i in range(0, len(indices), 2):
106         start_index = indices[i] + 11
107         end_index = indices[i + 1]
108         long_description += readme[start_index:end_index]
109     return long_description
110 
111 
112 setup(
113     name="pyjanitor",
114     version="0.24.0",
115     description="Tools for cleaning pandas DataFrames",
116     author="pyjanitor devs",
117     author_email="ericmajinglong@gmail.com",
118     url="https://github.com/pyjanitor-devs/pyjanitor",
119     license="MIT",
120     # packages=["janitor", "janitor.xarray", "janitor.spark"],
121     packages=find_packages(),
122     install_requires=INSTALL_REQUIRES,
123     extras_require=EXTRA_REQUIRES,
124     python_requires=">=3.6",
125     long_description=generate_long_description(),
126     long_description_content_type="text/markdown",
127 )
128
```
--- 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/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -68,7 +68,7 @@
 
 # add 'all' key to EXTRA_REQUIRES
 all_requires = []
-for k, v in EXTRA_REQUIRES.items():
+for _, v in EXTRA_REQUIRES.items():
     all_requires.extend(v)
 EXTRA_REQUIRES["all"] = set(all_requires)
 
@@ -83,11 +83,11 @@
     """
     Extra chunks from README for PyPI description.
 
-    Target chunks must be contained within `.. pypi-doc` pair comments,
+    Target chunks must be contained within `<!-- pypi-doc -->` pair comments,
     so there must be an even number of comments in README.
 
     :returns: Extracted description from README.
-    :raises Exception: if odd number of `.. pypi-doc` comments
+    :raises Exception: If odd number of `<!-- pypi-doc -->` comments
         in README.
     """
     # Read the contents of README file
@@ -96,14 +96,15 @@
         readme = f.read()
 
     # Find pypi-doc comments in README
-    indices = [m.start() for m in re.finditer(".. pypi-doc", readme)]
+    boundary = r"<!-- pypi-doc -->"
+    indices = [m.start() for m in re.finditer(boundary, readme)]
     if len(indices) % 2 != 0:
-        raise Exception("Odd number of `.. pypi-doc` comments in README")
+        raise Exception(f"Odd number of `{boundary}` comments in README")
 
     # Loop through pairs of comments and save text between pairs
     long_description = ""
     for i in range(0, len(indices), 2):
-        start_index = indices[i] + 11
+        start_index = indices[i] + len(boundary)
         end_index = indices[i + 1]
         long_description += readme[start_index:end_index]
     return long_description

{"golden_diff": "diff --git a/setup.py b/setup.py\n--- a/setup.py\n+++ b/setup.py\n@@ -68,7 +68,7 @@\n \n # add 'all' key to EXTRA_REQUIRES\n all_requires = []\n-for k, v in EXTRA_REQUIRES.items():\n+for _, v in EXTRA_REQUIRES.items():\n     all_requires.extend(v)\n EXTRA_REQUIRES[\"all\"] = set(all_requires)\n \n@@ -83,11 +83,11 @@\n     \"\"\"\n     Extra chunks from README for PyPI description.\n \n-    Target chunks must be contained within `.. pypi-doc` pair comments,\n+    Target chunks must be contained within `<!-- pypi-doc -->` pair comments,\n     so there must be an even number of comments in README.\n \n     :returns: Extracted description from README.\n-    :raises Exception: if odd number of `.. pypi-doc` comments\n+    :raises Exception: If odd number of `<!-- pypi-doc -->` comments\n         in README.\n     \"\"\"\n     # Read the contents of README file\n@@ -96,14 +96,15 @@\n         readme = f.read()\n \n     # Find pypi-doc comments in README\n-    indices = [m.start() for m in re.finditer(\".. pypi-doc\", readme)]\n+    boundary = r\"<!-- pypi-doc -->\"\n+    indices = [m.start() for m in re.finditer(boundary, readme)]\n     if len(indices) % 2 != 0:\n-        raise Exception(\"Odd number of `.. pypi-doc` comments in README\")\n+        raise Exception(f\"Odd number of `{boundary}` comments in README\")\n \n     # Loop through pairs of comments and save text between pairs\n     long_description = \"\"\n     for i in range(0, len(indices), 2):\n-        start_index = indices[i] + 11\n+        start_index = indices[i] + len(boundary)\n         end_index = indices[i + 1]\n         long_description += readme[start_index:end_index]\n     return long_description\n", "issue": "[DOC] Missing Pyjanitor description on PyPi\n# Brief Description of Fix\r\n\r\nSince `pyjanitor` release `0.22.0`, there has not been a [\"Project description\" for the package on PyPi](https://pypi.org/project/pyjanitor/0.22.0/#description).\r\n\r\nDuring the release of of `0.24.0`, @samukweku and I got an error during the release process due to:\r\n\r\n```\r\nChecking dist/pyjanitor-0.24.0-py3-none-any.whl: FAILED\r\nERROR    `long_description` has syntax errors in markup and would not be        \r\n         rendered on PyPI.                                                      \r\n         No content rendered from RST source.     \r\n```\r\nOur guess is that we had switched the README from RST to MD some time ago, causing the `long_description` to quietly fail, and now the `gh-action-pypi-publish` GitHub action would no longer accept the bad `long_description`.\r\n\r\nWe updated the `long_description_content_type` in `setup.py` to markdown  in #1197  and the package was successfully published to PyPi, but there is still no Project description. So there must still be something that is not being generated correctly.\r\n\r\nWe need to verify that `long_description` is properly being generated. We should test with local tools *and* https://test.pypi.org/ to verify that this is fixed.\n", "before_files": [{"content": "\"\"\"Setup script.\"\"\"\nimport codecs\nimport os\nimport re\nfrom pathlib import Path\nfrom pprint import pprint\n\nfrom setuptools import find_packages, setup\n\nHERE = os.path.abspath(os.path.dirname(__file__))\n\n\ndef read(*parts):\n    # intentionally *not* adding an encoding option to open\n    return codecs.open(os.path.join(HERE, *parts), \"r\").read()\n\n\ndef read_requirements(*parts):\n    \"\"\"\n    Return requirements from parts.\n\n    Given a requirements.txt (or similar style file),\n    returns a list of requirements.\n    Assumes anything after a single '#' on a line is a comment, and ignores\n    empty lines.\n\n    :param parts: list of filenames which contain the installation \"parts\",\n        i.e. submodule-specific installation requirements\n    :returns: A compiled list of requirements.\n    \"\"\"\n    requirements = []\n    for line in read(*parts).splitlines():\n        new_line = re.sub(  # noqa: PD005\n            r\"(\\s*)?#.*$\",  # the space immediately before the\n            # hash mark, the hash mark, and\n            # anything that follows it\n            \"\",  # replace with a blank string\n            line,\n        )\n        new_line = re.sub(  # noqa: PD005\n            r\"-r.*$\",  # link to another requirement file\n            \"\",  # replace with a blank string\n            new_line,\n        )\n        new_line = re.sub(  # noqa: PD005\n            r\"-e \\..*$\",  # link to editable install\n            \"\",  # replace with a blank string\n            new_line,\n        )\n        # print(line, \"-->\", new_line)\n        if new_line:  # i.e. we have a non-zero-length string\n            requirements.append(new_line)\n    return requirements\n\n\n# pull from requirements.IN, requirements.TXT is generated from this\nINSTALL_REQUIRES = read_requirements(\".requirements/base.in\")\n\nEXTRA_REQUIRES = {\n    \"dev\": read_requirements(\".requirements/dev.in\"),\n    \"docs\": read_requirements(\".requirements/docs.in\"),\n    \"test\": read_requirements(\".requirements/testing.in\"),\n    \"biology\": read_requirements(\".requirements/biology.in\"),\n    \"chemistry\": read_requirements(\".requirements/chemistry.in\"),\n    \"engineering\": read_requirements(\".requirements/engineering.in\"),\n    \"spark\": read_requirements(\".requirements/spark.in\"),\n}\n\n# add 'all' key to EXTRA_REQUIRES\nall_requires = []\nfor k, v in EXTRA_REQUIRES.items():\n    all_requires.extend(v)\nEXTRA_REQUIRES[\"all\"] = set(all_requires)\n\nfor k1 in [\"biology\", \"chemistry\", \"engineering\", \"spark\"]:\n    for v2 in EXTRA_REQUIRES[k1]:\n        EXTRA_REQUIRES[\"docs\"].append(v2)\n\npprint(EXTRA_REQUIRES)\n\n\ndef generate_long_description() -> str:\n    \"\"\"\n    Extra chunks from README for PyPI description.\n\n    Target chunks must be contained within `.. pypi-doc` pair comments,\n    so there must be an even number of comments in README.\n\n    :returns: Extracted description from README.\n    :raises Exception: if odd number of `.. pypi-doc` comments\n        in README.\n    \"\"\"\n    # Read the contents of README file\n    this_directory = Path(__file__).parent\n    with open(this_directory / \"mkdocs\" / \"index.md\", encoding=\"utf-8\") as f:\n        readme = f.read()\n\n    # Find pypi-doc comments in README\n    indices = [m.start() for m in re.finditer(\".. pypi-doc\", readme)]\n    if len(indices) % 2 != 0:\n        raise Exception(\"Odd number of `.. pypi-doc` comments in README\")\n\n    # Loop through pairs of comments and save text between pairs\n    long_description = \"\"\n    for i in range(0, len(indices), 2):\n        start_index = indices[i] + 11\n        end_index = indices[i + 1]\n        long_description += readme[start_index:end_index]\n    return long_description\n\n\nsetup(\n    name=\"pyjanitor\",\n    version=\"0.24.0\",\n    description=\"Tools for cleaning pandas DataFrames\",\n    author=\"pyjanitor devs\",\n    author_email=\"ericmajinglong@gmail.com\",\n    url=\"https://github.com/pyjanitor-devs/pyjanitor\",\n    license=\"MIT\",\n    # packages=[\"janitor\", \"janitor.xarray\", \"janitor.spark\"],\n    packages=find_packages(),\n    install_requires=INSTALL_REQUIRES,\n    extras_require=EXTRA_REQUIRES,\n    python_requires=\">=3.6\",\n    long_description=generate_long_description(),\n    long_description_content_type=\"text/markdown\",\n)\n", "path": "setup.py"}], "after_files": [{"content": "\"\"\"Setup script.\"\"\"\nimport codecs\nimport os\nimport re\nfrom pathlib import Path\nfrom pprint import pprint\n\nfrom setuptools import find_packages, setup\n\nHERE = os.path.abspath(os.path.dirname(__file__))\n\n\ndef read(*parts):\n    # intentionally *not* adding an encoding option to open\n    return codecs.open(os.path.join(HERE, *parts), \"r\").read()\n\n\ndef read_requirements(*parts):\n    \"\"\"\n    Return requirements from parts.\n\n    Given a requirements.txt (or similar style file),\n    returns a list of requirements.\n    Assumes anything after a single '#' on a line is a comment, and ignores\n    empty lines.\n\n    :param parts: list of filenames which contain the installation \"parts\",\n        i.e. submodule-specific installation requirements\n    :returns: A compiled list of requirements.\n    \"\"\"\n    requirements = []\n    for line in read(*parts).splitlines():\n        new_line = re.sub(  # noqa: PD005\n            r\"(\\s*)?#.*$\",  # the space immediately before the\n            # hash mark, the hash mark, and\n            # anything that follows it\n            \"\",  # replace with a blank string\n            line,\n        )\n        new_line = re.sub(  # noqa: PD005\n            r\"-r.*$\",  # link to another requirement file\n            \"\",  # replace with a blank string\n            new_line,\n        )\n        new_line = re.sub(  # noqa: PD005\n            r\"-e \\..*$\",  # link to editable install\n            \"\",  # replace with a blank string\n            new_line,\n        )\n        # print(line, \"-->\", new_line)\n        if new_line:  # i.e. we have a non-zero-length string\n            requirements.append(new_line)\n    return requirements\n\n\n# pull from requirements.IN, requirements.TXT is generated from this\nINSTALL_REQUIRES = read_requirements(\".requirements/base.in\")\n\nEXTRA_REQUIRES = {\n    \"dev\": read_requirements(\".requirements/dev.in\"),\n    \"docs\": read_requirements(\".requirements/docs.in\"),\n    \"test\": read_requirements(\".requirements/testing.in\"),\n    \"biology\": read_requirements(\".requirements/biology.in\"),\n    \"chemistry\": read_requirements(\".requirements/chemistry.in\"),\n    \"engineering\": read_requirements(\".requirements/engineering.in\"),\n    \"spark\": read_requirements(\".requirements/spark.in\"),\n}\n\n# add 'all' key to EXTRA_REQUIRES\nall_requires = []\nfor _, v in EXTRA_REQUIRES.items():\n    all_requires.extend(v)\nEXTRA_REQUIRES[\"all\"] = set(all_requires)\n\nfor k1 in [\"biology\", \"chemistry\", \"engineering\", \"spark\"]:\n    for v2 in EXTRA_REQUIRES[k1]:\n        EXTRA_REQUIRES[\"docs\"].append(v2)\n\npprint(EXTRA_REQUIRES)\n\n\ndef generate_long_description() -> str:\n    \"\"\"\n    Extra chunks from README for PyPI description.\n\n    Target chunks must be contained within `<!-- pypi-doc -->` pair comments,\n    so there must be an even number of comments in README.\n\n    :returns: Extracted description from README.\n    :raises Exception: If odd number of `<!-- pypi-doc -->` comments\n        in README.\n    \"\"\"\n    # Read the contents of README file\n    this_directory = Path(__file__).parent\n    with open(this_directory / \"mkdocs\" / \"index.md\", encoding=\"utf-8\") as f:\n        readme = f.read()\n\n    # Find pypi-doc comments in README\n    boundary = r\"<!-- pypi-doc -->\"\n    indices = [m.start() for m in re.finditer(boundary, readme)]\n    if len(indices) % 2 != 0:\n        raise Exception(f\"Odd number of `{boundary}` comments in README\")\n\n    # Loop through pairs of comments and save text between pairs\n    long_description = \"\"\n    for i in range(0, len(indices), 2):\n        start_index = indices[i] + len(boundary)\n        end_index = indices[i + 1]\n        long_description += readme[start_index:end_index]\n    return long_description\n\n\nsetup(\n    name=\"pyjanitor\",\n    version=\"0.24.0\",\n    description=\"Tools for cleaning pandas DataFrames\",\n    author=\"pyjanitor devs\",\n    author_email=\"ericmajinglong@gmail.com\",\n    url=\"https://github.com/pyjanitor-devs/pyjanitor\",\n    license=\"MIT\",\n    # packages=[\"janitor\", \"janitor.xarray\", \"janitor.spark\"],\n    packages=find_packages(),\n    install_requires=INSTALL_REQUIRES,\n    extras_require=EXTRA_REQUIRES,\n    python_requires=\">=3.6\",\n    long_description=generate_long_description(),\n    long_description_content_type=\"text/markdown\",\n)\n", "path": "setup.py"}]}