ギークなエンジニアを目指す男

機械学習系の知識を蓄えようとするブログ

PyPIへのアップロード時に「HTTPError: 400 Client Error: The description failed to render in the default format of reStructuredText.」が出る場合の対処方法

こんにちは。takapy(@takapy0210)です。

自作パッケージをPyPIにアップロードしようとしたところ簡易的なミスで数時間溶かしたので、その備忘です。

エラー内容

$ twine upload -r testpypi dist/*                                                                                                                                                                                                                                      
Uploading distributions to https://test.pypi.org/legacy/
Enter your password:
Uploading nlplot-1.0.0-py3-none-any.whl
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 948k/948k [00:02<00:00, 443kB/s]
NOTE: Try --verbose to see response content.
HTTPError: 400 Client Error: The description failed to render in the default format of reStructuredText. See https://test.pypi.org/help/#description-content-type for more information. for url: https://test.pypi.org/legacy/

エラー発生時のsetup.pyの内容

import os, sys
from setuptools import setup, find_packages

with open('README.md', 'r', encoding='utf-8') as f:
    long_description = f.read()

with open('LICENSE.txt', 'r', encoding='utf-8') as f:
    license = f.read()


def read_requirements():
    """Parse requirements from requirements.txt."""
    reqs_path = os.path.join('.', 'requirements.txt')
    with open(reqs_path, 'r') as f:
        requirements = [line.rstrip() for line in f]
    return requirements


setup(
    name='nlplot',
    version='1.0.0',
    description='Visualization Module for Natural Language Processing',
    long_description=long_description,
    long_description_content_type='text/markdown',
    author='Takanobu Nozawa',
    author_email='takanobu.030210@gmail.com',
    url='https://github.com/takapy0210/nlplot',
    license=license,
    install_requires=read_requirements(),
    packages=find_packages(exclude=('tests')),
    package_data={'nlplot':['data/*']},
    python_requires='~=3.6'
)

この状態で twine check を実施すると下記のようなエラーが発生し、twine upload しても冒頭のエラーが発生します。

エラー内容をみるにlong_description_content_typeがうまく機能していない(?)ように見えます。

$ twine check dist/*                                                                                                                                                                                                                                                  
Checking dist/nlplot-1.0.0-py3-none-any.whl: FAILED
  `long_description` has syntax errors in markup and would not be rendered on PyPI.
    line 3: Error: Unexpected indentation.
  warning: `long_description_content_type` missing.  defaulting to `text/x-rst`.
Checking dist/nlplot-1.0.0.tar.gz: FAILED
  `long_description` has syntax errors in markup and would not be rendered on PyPI.
    line 3: Error: Unexpected indentation.
  warning: `long_description_content_type` missing.  defaulting to `text/x-rst`.

解決方法

licenseを外部ファイルから読み込むのではなく、文字列をハードコーディングしました。
(上記エラーの「long_description has syntax errors in markup and would not be rendered on PyPI」とはいったい...)
これだけ見るとなんでこんなことに数時間も気づかなかったのか・・・という感じです。

そもそもこのlicenseを外部ファイルから読み込もうとしていたのがダメだったようです。

下記が修正後のsetup.pyです。

import os
from setuptools import setup, find_packages

with open('README.md', 'r', encoding='utf-8') as f:
    long_description = f.read()


def read_requirements():
    """Parse requirements from requirements.txt."""
    reqs_path = os.path.join('.', 'requirements.txt')
    with open(reqs_path, 'r') as f:
        requirements = [line.rstrip() for line in f]
    return requirements


setup(
    name='nlplot',
    version='1.0.1',
    description='Visualization Module for Natural Language Processing',
    long_description=long_description,
    long_description_content_type='text/markdown',
    author='Takanobu Nozawa',
    author_email='takanobu.030210@gmail.com',
    url='https://github.com/takapy0210/nlplot',
    # license=license,
    license='MIT License',
    install_requires=read_requirements(),
    packages=find_packages(exclude=('tests')),
    package_data={'nlplot':['data/*']},
    python_requires='~=3.6'
)

testpypiへのアップロードも上手くいきました。

twine upload -r testpypi dist/*
Uploading distributions to https://test.pypi.org/legacy/
Enter your password:
Uploading nlplot-1.0.1-py3-none-any.whl
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 947k/947k [00:02<00:00, 358kB/s]
Uploading nlplot-1.0.1.tar.gz
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 945k/945k [00:01<00:00, 705kB/s]

View at:
https://test.pypi.org/project/nlplot/1.0.1/

f:id:taxa_program:20200510152631p:plain

後述していますが、LICENSEファイルを作り方はGithub公式ドキュメントがあるので、こちらを参考に作るのが良さそうです。

以下、試行錯誤の履歴です。

やったこと

基本的には下記手順でチェックしました。

$ python setup.py sdist
$ python setup.py bdist_wheel
$ twine check dist/*

都度twine upload -r testpypi dist/*コマンドでtestpypiへアップロードできるかどうかチェックしても良いですが、twine checkを使ってアップロード前に確認すると、無駄にversionをインクリメントする必要もないので、オススメです。

各種パッケージのアップデート

やりましたが、うまくいかず。

pip install --upgrade setuptools wheel twine

公式の書き方をもう一度確認

description周りの書き方をコピーしてみましたが、上手くいかず。

packaging.python.org

long_descriptionをコメントアウト

...

setup(
    name='nlplot',
    version='1.0.0',
    description='Visualization Module for Natural Language Processing',
    # long_description=long_description,
    # long_description_content_type='text/markdown',
    author='Takanobu Nozawa',
    author_email='takanobu.030210@gmail.com',
    url='https://github.com/takapy0210/nlplot',
    license=license,
    install_requires=read_requirements(),
    packages=find_packages(exclude=('tests')),
    package_data={'nlplot':['data/*']},
    python_requires='~=3.6'
)
...

twine checkwarningは出るものの、Errorは無くなった。

twine check dist/* 
Checking dist/nlplot-1.0.0-py3-none-any.whl: PASSED, with warnings
  warning: `long_description_content_type` missing.  defaulting to `text/x-rst`.
Checking dist/nlplot-1.0.0.tar.gz: PASSED, with warnings
  warning: `long_description_content_type` missing.  defaulting to `text/x-rst`.

ここでアップロードしてみると、無事に終了した。

twine upload -r testpypi dist/*
Uploading distributions to https://test.pypi.org/legacy/
Enter your password:
Uploading nlplot-1.0.0-py3-none-any.whl
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 947k/947k [00:03<00:00, 278kB/s]
Uploading nlplot-1.0.0.tar.gz
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 945k/945k [00:01<00:00, 583kB/s]

View at:
https://test.pypi.org/project/nlplot/1.0.0/

test PyPIでみてみると、なんかおかしい。

f:id:taxa_program:20200510145000p:plain

最後の希望Twitterへ

すると、u++さんからjapanize-matplotlibを参考にしてみては?との助言を頂きました。
(u++さんありがとうございます。)

github.com

ん〜・・・long_description周りの記述は悪くなさそうだな...

licenseの書き方がちょっと違うから、念のためここを変更して確認してみることに。

...

setup(
    name='nlplot',
    version='1.0.0',
    description='Visualization Module for Natural Language Processing',
    long_description=long_description,
    long_description_content_type='text/markdown',
    author='Takanobu Nozawa',
    author_email='takanobu.030210@gmail.com',
    url='https://github.com/takapy0210/nlplot',
    # license=license,
    license='MIT License',
    install_requires=read_requirements(),
    packages=find_packages(exclude=('tests')),
    package_data={'nlplot':['data/*']},
    python_requires='~=3.6'
)
...

twine checkをしてみると、warningもErrorも出ない。

twine check dist/* 
Checking dist/nlplot-1.0.0-py3-none-any.whl: PASSED
Checking dist/nlplot-1.0.0.tar.gz: PASSED

ちなみに元々読み込んでいたLICENSE.txtの中身はこちら。

MIT License

Copyright (c) 2020, Takanobu Nozawa

こちらが悪さをしていた様子。(このファイルを1行だけにすると上手くいきました)

検索してみると、Githubの公式にこのLICENSEファイルの作り方がありました。

help.github.com

そもそも setup()licenseのこのファイルの内容を設定しようしていたのがダメだったようです。

最後に

同じようなことで数時間溶かす人がいなくなりますように。