依賴套件管理
專案中的python package我是用poetry管理
好處是可以分開開發時才使用的套件跟程式執行時依賴的套件
移除不用的套件時還可以確認依賴套件的依賴套件也全部被移除了
這點是pip做不到的,他只會移除你指定的套件
這文章寫的很詳細如何使用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
之前就在好奇為何像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作者就是這樣處理
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內容不是真的很長可以直接看
可以發現他先匯入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.py
中setup()
的參數version
修改
測試時的版本號可以隨意改1.0.0_test1
之類的
setup_kwargs = {
'name': 'bio-juno',
'version': '1.0.0',
.
.
setup(**setup_kwargs)
參考這篇打包套件基本上不會有問題
但自己實作還是有些事要注意
安裝時找不到依賴套件
打包和上傳的指令如下:
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]
,一個指令安裝套件了