打包python套件上傳PyPI


依賴套件管理

專案中的python package我是用poetry管理
好處是可以分開開發時才使用的套件跟程式執行時依賴的套件
移除不用的套件時還可以確認依賴套件的依賴套件也全部被移除了
這點是pip做不到的,他只會移除你指定的套件

這文章寫的很詳細如何使用poetry

再見了 pip!最佳 Python 套件管理器——Poetry 完全入門指南

雖然其他如virtualenv和pyvenv也可以建立python虛擬環境
但也是無法完全地管理依賴套件版本

專案結構

以我這次寫的專案Juno為例,這個專案目錄下的結構如下

.
├── Dockerfile
├── juno
│   ├── cli.py
│   ├── config.py
│   ├── data
│   ├── data.py
│   ├── __init__.py
│   ├── simulator.py
│   ├── tools
│   └── web.py
├── LICENSE
├── pyproject.toml
├── README.md
└── setup.py

juno這個目錄就是python packages
pyproject.toml是poetry init生成
每次用poetry add [pkg_name]就會自動寫入pyproject.toml
當然移除的話也會從pyproject.toml移除

setup.py是在根目錄的第1層
可以使用poetry2setup先生成setup.py之後在依照自己需求微調

poetry add -D poetry2setup
poetry2setup > setup.py

撰寫 setup.py

以前寫的專案都是自己寫腳本去自動安裝依賴的套件
因為寫setup.py太麻煩了
不過現在也有很多套件可以輔助生成setup.py
poetry2setup這個套件可以直接從pyproject.toml輸出setup.py

cd /path/to/your/project/
poetry add -D peotry2setup 
poetry2setup > setup.py

https://github.com/eggnogdb/eggnog-mapper

之前就在好奇為何像eggnog-mapper可以用pip install
然後在/anaconda3/bin/emapper.py的位置會出現這個腳本
套件不是都安裝在/anaconda3/lib/python3/site-packages/之類的地方
原來是用setup.py可以設定
像eggnog-mapper是直接寫腳本然後直接寫在setup.cfg

.
.
.
[options]
include_package_data = False
packages = find:
scripts =
    download_eggnog_data.py
    create_dbs.py
    emapper.py
    hmm_mapper.py
    hmm_server.py
    hmm_worker.py
.
.
.

比較常見的會是直接寫在套件裡面的某個module中的function
像是porchop作者就是這樣處理

https://github.com/rrwick/Porechop

setup.py中的entry_points={"console_scripts": ['porechop = porechop.porechop:main']}
表示將porechop套件中porchop模組的main函式指定為腳本並且執行檔名稱為porechop
但他還是很貼心的直接在專案目錄寫一個porechop-runner.py腳本
這腳本內容等同於執行python setup.py install之後生成的porechop

setup(name='porechop',
      version=__version__,
      description='Porechop',
      long_description=LONG_DESCRIPTION,
      url='http://github.com/rrwick/porechop',
      author='Ryan Wick',
      author_email='[email protected]',
      license='GPL',
      packages=['porechop'],
      entry_points={"console_scripts": ['porechop = porechop.porechop:main']},
      zip_safe=False,
      cmdclass={'build': PorechopBuild,
                'install': PorechopInstall,
                'clean': PorechopClean}
      )

客製化安裝步驟

雖然python的依賴套件直接在install_requires指定就好,但如果使用非python的套件就需要特別寫安裝的函式
可以發現Flye的作者就有這樣處理安裝minimap2的過程
他的setup.py內容不是真的很長可以直接看

https://github.com/fenderglass/Flye/blob/flye/setup.py

可以發現他先匯入setuptools裡的install物件
繼承這個物件然後在run()之中加入minimap2的安裝步驟
最後在setup的參數cmdclass加上'install' : MakeInstall
cmdclass如果沒有特別指定的話’install’的就會是預設的install這個物件

from setuptools.command.install import install as SetuptoolsInstall
.
.
class MakeInstall(SetuptoolsInstall):
    def run(self):
        SetuptoolsInstall.run(self)
		(install minimap2 steps)
		
.
.
.
setup(name='flye',
      .
      .
      cmdclass={'build': MakeBuild, 
                'install' : MakeInstall}

不過這樣的寫法只適用在沒有其他python依賴套件需要安裝
因為我實際這樣寫發現這樣不會安裝我需要的套件
可能是因為這樣的繼承方式會沒有完全繼承所有的功能
__init__()這個初始化的步驟install物件裏面應該有寫安裝依賴套件的函是在裏面
所以我改成以下寫法
_post_install就是我把客製化安裝的步驟寫在函式裡

def _post_install():
	(install_steps)

import atexit
class MakeInstall(SetuptoolsInstall):
    def __init__(self, *args, **kwargs):
        super(MakeInstall, self).__init__(*args, **kwargs)
        atexit.register(_post_install)

打包專案上傳PyPI

我們之所以可以直接用pip install [pkg_name]就可以安裝套件是因為開發者已經將套件打包好上傳到Pypi這個python套件拖管平台
他的網址實際上是https://pypi.org/simple
可以從pip指令看到這網址是預設值

pip install -h
.
.
.
Package Index Options:
  -i, --index-url <url>       Base URL of the Python Package Index (default https://pypi.org/simple). This should point to a repository compliant with
                              PEP 503 (the simple repository API) or a local directory laid out in the same format.
.
.
.

正式上傳到PyPI之前官方有提供另一個測試的平台(test.pypi.org)讓你先上傳看看是不是能正常運作
版本號是在setup.pysetup()的參數version修改
測試時的版本號可以隨意改1.0.0_test1之類的

setup_kwargs = {
    'name': 'bio-juno',
    'version': '1.0.0',
.
.
setup(**setup_kwargs)

參考這篇打包套件基本上不會有問題

打包python module 到PYPI 上

但自己實作還是有些事要注意

安裝時找不到依賴套件

打包和上傳的指令如下:

poetry add -D twine  
# 打包成tar.gz和wheel
python setup.py sdist bdist_wheel 
#上傳  
python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* 

依賴的套件一定會在https://pypi.org/legacy/
但開發者不一定會在https://test.pypi.org/legacy/也有相同的套件名和對應的版本號
所以上傳位置是test.pypi.org通常都會顯示找不到依賴套件

這時候得要在pip install加上額外的參數 --extra-index-url

pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/legacy [pkg_name]

https://packaging.python.org/en/latest/guides/using-testpypi/

當然如果你沒有任何依賴的套件,全部的程式碼都只有使用到預設的套件就不會有這種問題

有沒有撞名?

像我這次開發的套件名稱是juno
在PyPI已經有個名稱是Juno我就上傳失敗了
即使他開頭是大寫也一樣,所以後來才改成bio-juno
也是一樣在setup()的參數name修改
不過內部的套件名稱依然可以是juno
所以匯入套件時依然是import juno

以上都確認沒問題就可以用正式的版本號上傳到正式的平台了
--repository-url 預設就是https://pypi.org/legacy/,所以這參數就不用加了

python -m twine upload dist/* 

完成後就可以直接用pip install [pkg],一個指令安裝套件了


Author: Hung-Lin, Chen
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Hung-Lin, Chen !
 Previous
生物資訊工程師要有哪些經驗和技能 生物資訊工程師要有哪些經驗和技能
台灣目前(2022)最多的工作機會是基因體定序與分析,在 104 上隨時都可以看到,應用領域其實就是精準醫療跟微生物研究。 至於商業市場沒這麼大的醫療影像、生物統計、質譜分析、抗體設計等工作機會就是可遇不可求了
2022-09-08
Next 
取得網站的RSS 取得網站的RSS
podcastGetRSSFeed 可以接受 google 和 apple podcast 的網址轉 rss但 spotify 不行 https://getrssfeed.com/ 如何取得 Podcast RSS 訂閱網址
2022-04-07
  TOC