Python中的依賴關(guān)系處理
對許多人來說,依賴關(guān)系是一場噩夢。一些人甚至認(rèn)為它們是技術(shù)債務(wù)。管理你的軟件的庫列表是一種可怕的體驗(yàn)。自動(dòng)更新依賴項(xiàng)?-這聽起來像是在說胡話。
請繼續(xù)關(guān)注我,因?yàn)槲覍椭愀玫卣莆找恍┠阍趯?shí)踐中無法擺脫的東西——除非你非常富有和有才華,能夠在沒有他人代碼的情況下生活。
首先,我們需要清楚地了解一些有關(guān)依賴關(guān)系的知識: 依賴關(guān)系有兩種類型。Donald Stuff幾年前寫的關(guān)于這個(gè)主題的文章比我要寫的都好。簡單一點(diǎn)來說,它們是依賴于外部代碼的兩種類型的代碼包:應(yīng)用程序和庫。
庫依賴
Python庫應(yīng)該以一種通用的方式來指定它們的依賴關(guān)系。一個(gè)庫不應(yīng)該要求requests 2.1.5:這沒有意義。如果每個(gè)庫都需要不同版本的requests,我們就不能同時(shí)使用它們。
庫需要根據(jù)版本號的范圍來聲明依賴關(guān)系。要求請求requests>=2是正確的。如果你知道requests2.x不適用于該庫,那么要求 requests>=1,<2 也是正確的。你的版本范圍定義正在解決的問題是你的代碼和依賴項(xiàng)之間的API兼容性問題———沒有其他問題。這是庫盡可能使用語義版本控制的一個(gè)很好的理由。
因此,依賴關(guān)系應(yīng)該寫在setup.py中,類似于:

這樣,任何應(yīng)用程序都可以輕松地使用庫并與其他應(yīng)用程序共存。
應(yīng)用程序依賴關(guān)系
應(yīng)用程序只是庫的一種特殊情況。它們不打算被其他應(yīng)用程序庫重用(導(dǎo)入)——盡管在實(shí)踐中沒有什么可以阻止它。
最后,這意味著你應(yīng)該像為一個(gè)庫指定依賴關(guān)系一樣來在應(yīng)用程序的setup.py中指定依賴關(guān)系。
其主要區(qū)別在于,一個(gè)應(yīng)用程序通常部署在生產(chǎn)環(huán)境中以提供其服務(wù)。部署需要是可復(fù)用的。為此,你不能僅僅依賴于setup.py:因?yàn)檎埱蟮囊蕾囮P(guān)系范圍太寬。在重新部署應(yīng)用程序時(shí),你希望隨時(shí)都可以隨意更改版本。
因此,你需要一個(gè)不同的版本管理機(jī)制來處理部署,而不僅僅是setup.py。
pipenv在其文檔中有一節(jié)很好地總結(jié)了這一點(diǎn)。它將依賴關(guān)系類型劃分為抽象依賴項(xiàng)和具體依賴項(xiàng): 抽象依賴項(xiàng)基于范圍(例如 庫),而具體依賴項(xiàng)是用精確的版本(例如應(yīng)用程序部署)指定的——正如我們在這里看到的。
處理部署
requirements.txt文件長期以來一直被用來解決應(yīng)用程序部署的可復(fù)用性問題。它的格式通常是這樣的:

每個(gè)庫都將自己指定為微版本。這確保你的每個(gè)部署都將安裝相同版本的依賴項(xiàng)。使用requirements.txt是一個(gè)簡單的解決方案,也是實(shí)現(xiàn)可復(fù)用部署的第一步。然而,這還不夠。
實(shí)際上,雖然你可以指定你想要的requests的版本,但是如果requests依賴于urllib3,那么這將會(huì)使pip安裝urllib 2.1或urllib 2.2。你無法知道哪一個(gè)會(huì)被安裝,這并不能使你的部署100%可重用。
當(dāng)然,你可以在你的requirements.txt中復(fù)制所有的requests依賴項(xiàng),但那將是瘋狂的做法!
一個(gè)應(yīng)用程序依賴關(guān)系樹有時(shí)可能非常深入和復(fù)雜。
有各種各樣的技巧可以用來修復(fù)這個(gè)限制,但是真正的救星是pipenv和poetry。它們解決這個(gè)問題的方法類似于其他編程語言中的許多包管理器。它們生成一個(gè)鎖文件,其中包含所有已安裝的依賴項(xiàng)(以及它們自己的依賴項(xiàng)等)的列表和版本號。這可以確保部署是100%可復(fù)用的。
請查看它們的文檔,了解如何設(shè)置和使用它們!
處理依賴項(xiàng)更新
現(xiàn)在,你已經(jīng)有了鎖文件,它可以確保你的部署在短時(shí)間內(nèi)是可復(fù)用的,那么你就有了另一個(gè)問題。你如何確保你的依賴項(xiàng)是最新的?這是一個(gè)真正的安全問題,而且保持版本落后的話,你可能也會(huì)錯(cuò)過bug修復(fù)和進(jìn)行優(yōu)化的機(jī)會(huì)。
如果你的項(xiàng)目托管在GitHub上,Dependabot是解決這個(gè)問題的一個(gè)很好的解決方案。當(dāng)你的鎖文件中列出的庫的一個(gè)新版本可用時(shí),在存儲庫上啟用此應(yīng)用程序?qū)?huì)自動(dòng)創(chuàng)合并請求。例如,如果你已經(jīng)使用redis 3.3.6部署了你的應(yīng)用程序,當(dāng)新版本redis 3.3.7發(fā)布時(shí),Dependabot將會(huì)創(chuàng)建一個(gè)更新到redis 3.3.7的合并請求。此外,Dependabot還支持requirements.txt、 pipenv和poetry!

Dependabot正在為你更新jinja2
自動(dòng)部署更新
快要成功了。你有一個(gè)機(jī)器人,它讓你知道你的項(xiàng)目需要的一個(gè)庫的新版本是可用的。
一旦創(chuàng)建了合并請求,你的持續(xù)集成系統(tǒng)就會(huì)啟動(dòng)、部署你的項(xiàng)目并運(yùn)行測試。如果一切正常,你的合并請求就可以被合并了。但是在這個(gè)過程中真的需要你參與嗎?
除非你個(gè)人特別反感某個(gè)特定的版本號——“天哪,我討厭以3結(jié)尾的版本。遇見它總是運(yùn)氣不好。——或者除非你沒有自動(dòng)化測試,否則你,人類,是無用的。這個(gè)合并完全可以是自動(dòng)化的。
這就是Mergify發(fā)揮作用的地方。Mergify是一個(gè)GitHub應(yīng)用程序,它允許你定義關(guān)于如何合并合并請求的精確規(guī)則。下面是我在每個(gè)項(xiàng)目中都使用的一個(gè)規(guī)則:

當(dāng)規(guī)則完全匹配時(shí),Mergify會(huì)進(jìn)行報(bào)告。
一旦你的持續(xù)集成系統(tǒng)通過,Mergify就會(huì)為你合并該合并請求。

然后,你就可以自動(dòng)觸發(fā)你的部署鉤子來更新你的生產(chǎn)部署,并立即安裝新的庫版本。這將使得你的應(yīng)用程序總是使用較新的庫進(jìn)行更新,并且不會(huì)落后于幾年的發(fā)行版。
如果出現(xiàn)任何錯(cuò)誤,你仍然能夠從Dependabot中恢復(fù)提交——如果你希望使用一個(gè)Mergify規(guī)則,你也可以自動(dòng)化恢復(fù)提交。
題外話
對我來說,這就是依賴關(guān)系管理生命周期目前的狀態(tài)。雖然這對Python非常適用,但它也可以應(yīng)用于使用了類似模式的許多其他語言,比如Node和npm。