Install a package from Github that has other github dependencies

最近在開發ApiTestEngine時遇到一個安裝包依賴的問題,耗費了不少時間尋找解決方案,考慮到還算比較有普遍性,因此總結形成這篇文章。

從 pip install 說起

先不那麼簡單地描述下背景。

ApiTestEngine作為一款介面測試工具,需要具有靈活的命令行調用方式,因此最好能在系統中進行安裝並註冊為一個CLI命令。

在Python中,安裝依賴庫的最佳方式是採用pip,例如安裝Locust時,就可以採用如下命令搞定。

$ pip install locustionCollecting locustion Using cached locustio-0.7.5.tar.gzn[...]nSuccessfully installed locustio-0.7.5n

但要想採用pip install SomePackage的方式,前提是SomePackage已經託管在PyPI。關於PyPI,可以理解為Python語言的第三方庫的倉庫索引,當前絕大多數流行的Python第三方庫都託管在PyPI上。

但是,這裡存在一個問題。在PyPI當中,所有的包都是由其作者自行上傳的。如果作者比較懶,那麼可能託管在PyPI上的最新版本相較於最新代碼就會比較滯後。

Locust就是一個典型的例子。從上面的安裝過程可以看出,我們採用pip install locustio安裝的Locust版本是v0.7.5,而在Locust的Github倉庫中,v0.7.5已經是一年之前的版本了。也是因為這個原因,之前在我的博客裡面介紹Locust的圖表展示功能後,已經有不下5個人向我諮詢為啥他們看不到這個圖表模塊。這是因為Locust的圖表模塊是在今年(2017)年初時添加的功能,master分支的代碼版本也已經升級到v0.8a2了,但PyPI上的版本卻一直沒有更新。

而要想使用到項目最新的功能,就只能採用源碼進行安裝。

大多數編程語言在使用源碼進行安裝時,都需要先將源碼下載到本地,然後通過命令進行編譯,例如Linux中常見的make && make install。對於Python項目來說,也可以採用類似的模式,先將項目clone到本地,然後進入到項目的根目錄,執行python setup.py install。

$ git clone https://github.com/locustio/locust.gitn$ cd locustn$ python setup.py installn[...]nFinished processing dependencies for locustio==0.8a2n

不過,要想採用這種方式進行安裝也是有前提的,那就是項目必須已經實現了基於setuptools的安裝方式,並在項目的根目錄下存在setup.py。

可以看出,這種安裝方式還是比較繁瑣的,需要好幾步才能完成安裝。而且,對於大多數使用者來說,他們並不需要閱讀項目源碼,因此clone操作也實屬多餘。

可喜的是,pip不僅支持安裝PyPI上的包,也可以直接通過項目的git地址進行安裝。還是以Locust項目為例,我們通過pip命令也可以實現一條命令安裝Github項目源碼。

$ pip install git+https://github.com/locustio/locust.git@master#egg=locustionCollecting locustio from git+https://github.com/locustio/locust.git@master#egg=locustion[...]nSuccessfully installed locustio-0.8a2n

對於項目地址來說,完整的描述應該是:

pip install vcs+protocol://repo_url/#egg=pkg&subdirectory=pkg_dirn

這裡的vcs也不僅限於git,svn和hg也是一樣的,而protocol除了採用SSH形式的項目地址,也可以採用HTTPS的地址,在此不再展開。

通過這種方式,我們就總是可以使用到項目的最新功能特性了。當然,前提條件也是一樣的,需要項目中已經實現了setup.py。

考慮到ApiTestEngine還處於頻繁的新特性開發階段,因此這種途徑無疑是讓用戶安裝使用最新代碼的最佳方式。

問題緣由

在ApiTestEngine中,存在測試結果報告展示這一部分的功能,而這部分的功能是需要依賴於另外一個託管在GitHub上的項目,PyUnitReport。

於是,問題就變為:如何構造ApiTestEngine項目的setup.py,可以實現用戶在安裝ApiTestEngine時自動安裝PyUnitReport依賴。

對於這個需求,已經確定可行的辦法:先通過pip安裝依賴的庫(PyUnitReport),然後再安裝當前項目(ApiTestEngine)。

$ pip install git+https://github.com/debugtalk/PyUnitReport.git#egg=PyUnitReportn$ pip install git+https://github.com/debugtalk/ApiTestEngine.git#egg=ApiTestEnginen

這種方式雖然可行,但是需要執行兩條命令,顯然不是我們想要的效果。

經過搜索,發現針對該需求,可以在setuptools.setup()中通過install_requires和dependency_links這兩個配置項組合實現。

具體地,配置方式如下:

install_requires=[n "requests",n "flask",n "PyYAML",n "coveralls",n "coverage",n "PyUnitReport"n],ndependency_links=[n "git+https://github.com/debugtalk/PyUnitReport.git#egg=PyUnitReport"n],n

這裡有一點需要格外注意,那就是指定的依賴包如果存在於PyPI,那麼只需要在install_requires中指定包名和版本號即可(不指定版本號時,默認安裝最新版本);而對於以倉庫URL地址存在的依賴包,那麼不僅需要在dependency_links中指定,同時也要在install_requires中指定。

然後,就可以直接通過ApiTestEngine項目的git地址一鍵進行安裝了。

$ pip install git+https://github.com/debugtalk/ApiTestEngine.git#egg=ApiTestEnginen

雖然在尋找解決辦法的過程中,看到大家都在說dependency_links由於安全性的問題,即將被棄用,而且在setuptools的官方文章中的確也沒有看到dependency_links的描述。

DEPRECATION: Dependency Links processing has been deprecated and will be removed in a future release.n

不過在我本地的macOS系統上嘗試發現,該種方式的確是可行的,因此就採用這種方式進行發布了。

但是當我後續在Linux伺服器上安裝時,卻無法成功,總是在安裝PyUnitReport依賴庫的時候報錯:

$ pip install git+https://github.com/debugtalk/ApiTestEngine.git#egg=ApiTestEnginen[...]nCollecting PyUnitReport (from ApiTestEngine)n Could not find a version that satisfies the requirement PyUnitReport (from ApiTestEngine) (from versions: )nNo matching distribution found for PyUnitReport (from ApiTestEngine)n

另外,同時也有多個用戶反饋了同樣的問題,這才發現這種方式在Linux和Windows下是不行的。

然後,再次經過大量的搜索,卻始終沒有特別明確的答案,搞得我也在懷疑,dependency_links到底是不是真的已經棄用了,但是就算是棄用了,也應該有新的替代方案啊,但也並沒有找到。

這個問題就這麼放了差不多一個星期的樣子。

解決方案

今天周末在家,想來想去,不解決始終不爽,雖然只是多執行一條命令的問題。

於是又是經過大量搜索,幸運的是終於從pypa/pip的issues中找到一條issue,作者是Dominik Neise,他詳細描述了他遇到的問題和嘗試過的方法,看到他的描述我真是驚呆了,跟我的情況完全一模一樣不說,連嘗試的思路也完全一致。

然後,在下面的回復中,看到了Gary Wu和kbuilds的解答,總算是找到了問題的原因和解決方案。

問題在於,在dependency_links中指定倉庫URL地址的時候,在指定egg信息時,pip還同時需要一個版本號(version number),並且以短橫線-分隔,然後執行的時候再加上--process-dependency-links參數。

回到之前的dependency_links,我們應該寫成如下形式。

dependency_links=[n "git+https://github.com/debugtalk/PyUnitReport.git#egg=PyUnitReport-0"n]n

在這裡,短橫線-後面我並沒有填寫PyUnitReport實際的版本號,因為經過嘗試發現,這裡填寫任意數值都是成功的,因此我就填寫為0了,省得後續在升級PyUnitReport以後還要來修改這個地方。

然後,就可以通過如下命令進行安裝了。

$ pip install --process-dependency-links git+https://github.com/debugtalk/ApiTestEngine.git#egg=ApiTestEnginen

至此,問題總算解決了。

後記

那麼,dependency_links到底是不是要廢棄了呢?

從pip的GitHub項目中看到這麼一個issue,--process-dependency-links之前廢棄了一段時間,但是又給加回來了,因為當前還沒有更好的可替代的方案。因此,在出現替代方案之前,dependency_links應該是最好的方式了吧。

最後再感嘆下,老外提問時描述問題的專業性和細緻程度真是令人佩服,大家可以再仔細看下這個issue好好感受下。

閱讀更多

  • setuptools.readthedocs.io
  • pip.pypa.io/en/stable/r
  • github.com/pypa/pip/iss
  • github.com/pypa/pip/iss

推薦閱讀:

如何將自己的程序發布到 PyPI

TAG:Python库 | pypi | GitHub |