prompt: 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()
  
  ```
  
golden_diff: 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
  
verification_info: {"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"}]}