PyPIデビュー2015

tell-k
PyCon JP 2015 (2015.10.11)

お前誰よ?

https://avatars3.githubusercontent.com/u/26263?v=3&s=150 https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/vxjmiemo.png

目的/動機

趣味

対象

対象

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/ljj0l-e4.png

Pythonプロフェッショナルプログラミング 第2版

http://ecx.images-amazon.com/images/I/51ZNlK0%2Bn-L._SL160_.jpg

目次

PyPIとは?

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/3x4hkmn6.png

PyPiとは?

PyPAとは?

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/zli4wet3.png

PyPAとは?

Pythonパッケージ作成

Pythonパッケージ作成

  1. アカウント作成(https://pypi.python.org/pypi?%3Aaction=register_form)
  2. setup.pyを書く
  3. パッケージ登録 & アップロード

2. setup.py

from setuptools import setup, find_packages

setup(
    name='sample',
    version='1.0.0',
    url='https://github.com/pypa/sampleproject',
    packages=find_packages(exclude=['tests*']),
)

3. パッケージ登録

# パッケージ登録
$ python setup.py register
# 指示に従って入力

# パッケージアップロード
$ python setup.py sdist upload

終わりです。簡単ですね。

そういうわけにもいきますまい...

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/wuuz3mx_.png

Pythonパッケージ作成

  1. setup.pyを書く
  2. 開発する
  3. テストする
  4. 配布物を決める
  5. 登録/アップロード

構成

例えば「sample」というパッケージを妄想する

sample
  ├── MANIFEST.in
  ├── README.rst
  ├── sample        <- Pythonパッケージ
  │   └── __init__.py
  ├── setup.cfg
  ├── setup.py
  └── tests         <- テストコード
     ├── __init__.py
     └── test_sample.py

1. setup.pyを書く

setup.pyに書く事

setup.pyに書く事

setup.pyの例

setup(
    name='sample',
    version='1.0.0',
    description='A sample Python project',
    long_description=long_description,
    url='https://github.com/pypa/sampleproject',
    author='The Python Packaging Authority',
    author_email='pypa-dev@googlegroups.com',
    license='MIT',
    classifiers=[
        'Development Status :: 3 - Alpha',
        'Intended Audience :: Developers',
        'Topic :: Software Development :: Build Tools',
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 3',
    ],
    keywords='sample setuptools development',
    packages=find_packages(exclude=['contrib', 'docs', 'tests*']),
    install_requires=['peppercorn'],
 )

参考: https://github.com/pypa/sampleproject/blob/master/setup.py

classifiers is 何?

classifiers

Framework :: Django
Framework :: Django :: 1.4
Framework :: Django :: 1.5
Framework :: Django :: 1.6
Framework :: Django :: 1.7
Framework :: Django :: 1.8
Development Status :: 1 - Planning
Development Status :: 2 - Pre-Alpha
Development Status :: 3 - Alpha
Development Status :: 4 - Beta
Development Status :: 5 - Production/Stable
Development Status :: 6 - Mature
Development Status :: 7 - Inactive

version

1.2.0.dev1  # Development release
1.2.0a1     # Alpha Release
1.2.0b1     # Beta Release
1.2.0rc1    # Release Candidate
1.2.0       # Final Release
1.2.0.post1 # Post Release
15.10       # Date based release
23          # Serial release

version

バージョンナンバーは、メジャー.マイナー.パッチとし、バージョンを上げるには、

- APIの変更に互換性のない場合はメジャーバージョンを、
- 後方互換性があり機能性を追加した場合はマイナーバージョンを、
- 後方互換性を伴うバグ修正をした場合はパッチバージョンを上げます。

プレリリースやビルドナンバーなどのラベルに関しては、メジャー.マイナー.パッチの
形式を拡張する形で利用することができます。

via http://semver.org/lang/ja/

version

def find_version(*file_paths):
    version_file = read(*file_paths)
    version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
                              version_file, re.M)
    if version_match:
        return version_match.group(1)
    raise RuntimeError("Unable to find version string.")

setup(
   ...
   version=find_version("package", "__init__.py")
   ...
)

via Single-sourcing the Project Version

long_description

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/m5j2qspj.png

long_description

with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
    long_description = f.read()

 setup(
    ...
    long_description=long_description,
    ...
 )

via https://github.com/pypa/sampleproject/blob/master/setup.py

packages

from setuptools import find_packages

setup(
   ...
   packages=find_packages(exclude=['tests*'])
   ...
)

excludeで適切に除外

# × 除外しない
packages=find_packages()

# ○ 除外する
packages=find_packages(exclude=['tests*'])
# 除外しなかった場合
import sample
import tests.test_sample  # トップレベルでテストコードがインストールされてしまう

install_requires

setup(
   ...
   install_requires=[
      'Hoge',
   ]
   ...
)

install_requires VS requirements.txt

$ pip freeze > requirements.txt
# インストール済みパッケージの一覧を出してくれる
# バージョンを固定
Spam==1.0.0
Ham==2.1.0
Egg==1.3.0

install_requiresにこれ使えば?

with open('requiments.txt') as fp:
       requires = fp.readlines()

install_requires=requires

バージョン固定されると単純に困る

$ pip install Spam
$ pip freeze | grep Spam
Spam==1.1.0
# => Spamの最新版 1.1.0 が インストール

$ pip install sample
$ pip freeze | grep Spam
Spam==1.0.0
# => install_requires の バージョン固定により1.0.0になってしまう

_人人人人人人人人人人人人人人人_
> 突然のダウングレード!!! <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^ ̄

# 別パッケージが同様に、別のバージョン番号を固定で指定してると、
# バージョン競合が発生しインストールに失敗するケースも...

とてもつらい

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/p1fcoye0.png

install_requires の 役割

# そもそもバージョンを指定しない
install_requires=['Ham']

# 最低限必要なバージョンだけを記載
install_requires=['Ham>=1.0.0']

# 範囲を限定的にするとか
install_requires=['Ham>=1.0.0,<2']

# requirements.txtにもバージョン指定なし、範囲指定を記述可能
# install_requiresにバージョンを固定しないというのが大事

開発する

Python2/3両対応

Python2/3両対応

sixの例

import six

d = {'hoge1': 'fuga', 'hoge2': 'fuga', }

# python2
for k, v in d.iteritems():
    print(k, v)

# python3
for k, v in d.items():
    print(k, v)

# python2, 3 両方
for k, v in six.iteritems(d):
    print(k, v)

先達の知見を得る

Python3しか対応しないのもあり

http://cdn-ak.f.st-hatena.com/images/fotolife/n/niguruta/20101015/20101015123332.jpg

開発モード

開発モード

$ pip install -e .

# そのまま動作確認可能
$ python
Python 3.4.3 (default, Mar 23 2015, 04:19:36)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sample
>>> sample.main()
Call your main application code here

テスト

tox

$ pip install tox

tox.iniの例

[tox]
envlist=py27,py34,py35,pypy

[testenv]
commands=
    python setup.py test
# 全バージョンでテストを実行
$ tox

# バージョンを指定してテスト実行
$ tox -e py35

どこまでサポートすればいいの?

pytest

assertが賢くなる例

$ py.test test_assert.py

...

=========== FAILURES =============
__________ test_main _____________

    def test_main():
        lst1 = ['test1', 'test2']
        lst2 = ['test1', 'test3']

>       assert lst1 == lst2, 'Not equal lists'

E       AssertionError: Not equal lists
E       assert ['test1', 'test2'] == ['test1', 'test3']
E         At index 1 diff: 'test2' != 'test3'
E         Full diff:
E         - ['test1', 'test2']
E         ?                ^
E         + ['test1', 'test3']
E         ?

setup.py testから呼ぶ

setup.py testから呼ぶ

import sys

from setuptools.command.test import test as TestCommand

class PyTest(TestCommand):
    user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")]

    def initialize_options(self):
        TestCommand.initialize_options(self)
        self.pytest_args = []

    def finalize_options(self):
        TestCommand.finalize_options(self)
        self.test_args = []
        self.test_suite = True

    def run_tests(self):
        #import here, cause outside the eggs aren't loaded
        import pytest
        errno = pytest.main(self.pytest_args)
        sys.exit(errno)

setup.py testから呼ぶ

setup(
    ...
    tests_require = ['pytest'],
    cmdclass = {'test': PyTest},
    ...
    )
# py.testが実行される
$ python setup.py test

coverage

$ python setup.py test -a "--cov sample --cov-report term-missing"
https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/s8kwn9ff.png

配布物を決める

MANIFEST.in

include README.rst
include tox.ini
include setup.cfg
recursive-include docs *
recursive-include tests *
include sample/*.dat

ビルド

sdist

# ビルド
$ python setup.py sdist

# 直下のdistにsdistが生成されます
$ ls dist
sample-0.0.1.tar.gz

wheel

$ pip install wheel

wheel

$ python setup.py bdist_wheel

# python3にしか対応しない
$ python setup.py bdist_wheel --python-tag=py3

wheel

[wheel]
universal = 1
$ python setup bdist_wheel
$ ls
sample-0.0.1-py2.py3-none-any.whl

アップロード

アカウントの作成

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/ydjsq6a_.png

https://pypi.python.org/pypi?%3Aaction=register_form

パッケージの登録

$ ./setup.py register
running register
running egg_info
writing tesdat.egg-info/PKG-INFO
writing top-level names to sample.egg-info/top_level.txt
writing dependency_links to sample.egg-info/dependency_links.txt
reading manifest file 'sample.egg-info/SOURCES.txt'
writing manifest file 'sample.egg-info/SOURCES.txt'
running check
We need to know who you are, so please choose either:
 1. use your existing login,

...

Registering sample to https://pypi.python.org/pypi
Server response (200): OK
I can store your PyPI login so future submissions will be faster.
(the login will be stored in /home/tell-k/.pypirc)
Save your login (y/N)?y

パッケージの登録

[distutils]
index-servers =
   pypi

[pypi]
username: tell-k
password: xxxxxxxxxxxx

パッケージのアップロード

# 事前に「setup.py」 の記述内容をチェック
$ python setup.py check -r -s

$ python setup.py sdist bdist_wheel upload

# × uplaod単体では利用できない
$ python upload
running upload
error: No dist file created in earlier command

Twineでのアップロード(推奨)

$ pip install twine
$ twine uplaod dist/*

testpypi.python.org の利用

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/lttwh7en.png

pypircの設定

[distutils]
index-servers=
    pypi
    pypitest

[pypitest]
repository = https://testpypi.python.org/pypi
username = <your user name goes here>
password = <your password goes here>

[pypi]
repository = https://pypi.python.org/pypi
username = <your user name goes here>
password = <your password goes here>

testpypiへの操作

パッケージ登録
$ python setup.py register -r https://testpypi.python.org/pypi

アップロード
$ python setup.py sdist upload -r https://testpypi.python.org/pypi

インストール
$ pip install -i https://testpypi.python.org/pypi <package name>

その他のTips

ドキュメントのアップロード

setup.cfg:

[upload_docs]
upload-dir = _build/sphinx/html
# Sphinxをビルドしてアップロード
$ python setup.py build_sphinx upload_docs

# アップロード後はpyhthonhosted.orgのURLで確認できる
# pypiからリンクが貼られれます
http://pythonhosted.org/<packge name>/

コマンドラインツール

entry_points = {
    "console_scripts": [
        "sayhello=sample.commands:hello",
    ]
}

# sample.commandsモジュールのhello関数を呼び出す
$ pip install sample

$ sayhello
Hello! my name is hoge.

$ which sayhello
/usr/local/bin/sayhello

Githubのバージョンタグをつける

# リリース時点でタグを切る
$ git tag -am "Version 1.0.0" v1.0.0
$ git push origin --tags

Githubのバージョンタグをつける

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/x9134jfl.png

Djangoアプリみたいなの

便利サービス

便利サービス

Travis CI

.travis.yml例

language: python
python: 3.4
env:
  matrix:
   - TOXENV=py26
   - TOXENV=py27
   - TOXENV=py33
   - TOXENV=py34
   - TOXENV=pypy
   - TOXENV=flake8
install:
  - pip install tox
  - if test "$TOXENV" = py34 ; then pip install coveralls ; fi
script: tox
after_script:
  - if test "$TOXENV" = py34 ; then coveralls ; fi

Coveralls

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/6n4sok0b.png

Coveralls

install:
  - pip install tox
  - if test "$TOXENV" = py34 ; then pip install coveralls ; fi
script: tox
after_script:
  - if test "$TOXENV" = py34 ; then coveralls ; fi

Code Climate

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/phbb_i4h.png

Code Climate

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/qkkl6cdq.png

Read The Docs

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/to6rlori.png

Requires.io

Requires.io

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/vvbm341_.png

Sheilds.io

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/d1iqgbwa.png

トラブルシューティング

トラブルシューティング

まちがってアップロードした!

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/by6w58ku.png

一度消したら同じバージョン番号が使えない!

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/oibkxoxp.png

説明文がレンダリングされていない!

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/0-myl_qn.png

今日話さなかった事

参考URL

Python Packaging Authority
https://www.pypa.io/en/latest/
PEP Summaries
https://www.pypa.io/en/latest/peps/
Python Packaging User Guide
https://python-packaging-user-guide.readthedocs.org/en/latest/
vinta/awesome-python
https://github.com/vinta/awesome-python
PyPIデビュー
http://www.freia.jp/taka/docs/pyhack4/pypi/
PyPIデビュー
http://note.crohaco.net/2014/pypi-debut/

参考URL

PyPIにパッケージ登録する
http://qiita.com/edvakf@github/items/d82cd7ab77ea2b88506c
How To Package Your Python Code
http://python-packaging.readthedocs.org/en/latest/index.html
Sharing Your Labor of Love: PyPI Quick and Dirty
https://hynek.me/articles/sharing-your-labor-of-love-pypi-quick-and-dirty/
Python: PyPIにパッケージをアップロードする最新の推奨な方法
http://elicon.blog57.fc2.com/blog-entry-422.html
Windows での Python 2.7/3.4 の拡張モジュールビルド環境
http://qiita.com/methane/items/2210712763b91e75fdf0
パッケージングの今
http://www.slideshare.net/aodag/ss-39068785
パッケージングの今と未来
http://www.slideshare.net/aodag/ss-26183017

まとめ

https://dl.dropboxusercontent.com/spa/ghyn87yb4ejn5yy/m4naltmy.png

Special Thanks

ご静聴ありがとうとございました