Apakah Anda akrab dengan sejarah pengemasan Python? Apakah Anda menavigasi dalam format paket? Apakah Anda tahu bahwa Anda harus mengurai kusut ketergantungan bahkan ketika tampaknya itu adalah keajaiban - ketergantungan nol? Saya yakin mereka tidak akrab dengan semua ini seperti penulis perpustakaan DepHell.

Saya berhasil berbicara dengan
Nikita Voronov , lebih dikenal sebagai Gram atau
orsinium , dan bertanya kepadanya tentang topik laporan di masa depan, rasa sakit dari keputusan penyelesaian ketergantungan yang buruk, DepHell, pip, prinsip pertandingan pertama yang menang, Guido, Pipfile, pengembangan penambahan Python, dan masa depan ekosistem.
- Di Moscow Python Conf ++ Anda akan berbicara tentang dependensi dan semua yang ada di sebelahnya. Mengapa Anda memilih topik seperti itu saja untuk laporan?Karena pertanyaan ini melewati semua pengalaman saya dengan Python. Ketika saya membuat paket pertama saya, menulis kode pertama, saya berpikir tentang bagaimana membantu orang lain sehingga mereka dapat menginstalnya, dan melakukan setup.py. Kemudian dia bekerja di satu perusahaan, di perusahaan lain, di perusahaan ketiga, tugasnya rumit dan berkembang. Pada awalnya hanya ada file requirement.txt, kemudian saya menyadari bahwa saya perlu memperbaiki dependensi, pip-tools, file kunci muncul. Kemudian kami mendapat Pipenv, dan kemudian Puisi.
Berangsur-angsur semakin banyak masalah yang muncul, saya semakin tenggelam dalam kekacauan ini. Sebagai hasilnya, saya mulai mengimplementasikan
DepHell , sebuah proyek untuk mengelola dependensi yang dapat menyelesaikan dan membacanya dalam format yang berbeda. Sementara saya bekerja dengan semua jenis format, saya sudah cukup melihat bagian dalamnya dan sekarang saya tahu berapa banyak yang diatur di dalamnya, tetapi setiap hari saya belajar sesuatu yang baru. Karena itu, saya dapat memberi tahu Anda banyak hal menarik tentang rasa sakit dan keputusan yang buruk.
- Nyeri selalu menarik. Menurut Anda apa masalahnya sekarang di bagian Python ini?JS memiliki direktori
node_modules
, dan setiap dependensi memiliki dependensinya sendiri yang tertumpuk di dalamnya. Dalam Python, ini tidak terjadi. Misalnya, paket diinstal di lingkungan yang sama, dan semua paket yang menggunakannya menggunakan versi yang sama dari paket ini. Untuk melakukan ini, Anda harus menyelesaikan dependensi dengan benar - pilih versi paket ini yang akan memuaskan semua paket di lingkungan ini secara umum. Tugasnya cukup non-sepele: paket saling tergantung, semuanya saling terkait, dan menyelesaikan dependensi sulit. Praktis tidak ada resolvers di Python. Penyelesai yang cerdik hanya ada di Puisi dan DepHell.
Semua ini sangat rumit oleh kenyataan bahwa pypi.org sering tidak memberikan informasi tentang dependensi paket, karena informasi ini harus ditentukan oleh klien, server PyPI tidak dapat menemukannya dengan sendirinya. Oleh karena itu, ketika PyPI mengatakan bahwa paket tersebut tidak memiliki dependensi, Anda tidak dapat mempercayainya. Anda harus mengunduh seluruh paket, unzip dan mengurai dependensi paket dari setup.py. Ini adalah proses yang panjang, sehingga resolver di Python tidak bisa cepat.
Tidak hanya ada beberapa resolvers di Python, mereka juga lambat oleh desain.
Dalam
laporan saya, saya ingin memberi tahu cara kerja penyelesaian DepHell: cara membuat grafik dependensi, bagaimana grafik ini terlihat, mengapa sebagian besar artikel ilmiah terletak pada bagaimana menyelesaikan dependensi dan bekerja dengan grafik ini. Tentu saja, ada whitepaper tentang bagaimana semua ini harus bekerja. Orang pintar memiliki artikel yang ditulis dengan algoritma, tetapi paling sering mereka tidak bekerja untuk Python. Oleh karena itu, saya akan menjelaskan bagaimana saya bekerja dengan resolusi ketergantungan dalam praktik di DepHell.
- Saya sering mendengar dari programmer bahwa mereka menggunakan pip, dan semuanya berfungsi dengan baik untuk mereka. Apa yang mereka lakukan salah?Mereka beruntung, mereka tidak menemukan konflik ketergantungan. Meskipun masalah bisa muncul ketika Anda hanya menempatkan dua paket di lingkungan yang bersih. Baru-baru ini ada rilis paket coverage 5.0, dan jika Anda hanya menentukan
pip install pytest-cov coveralls
, pip akan berurutan dan untuk paket pertama akan memilih versi terbaru dari coverage, yaitu 5.0. Prinsip
pertandingan pertama menang bekerja di pip, jadi meskipun versi tidak kompatibel dengan paket kedua, itu akan sudah diperbaiki untuk paket pertama. Seringkali pendekatan ini berhasil, tetapi tidak selalu.
Selain itu, ada pertanyaan dengan lingkungan yang dapat direproduksi. Karena pip selalu menempatkan versi terbaru, versi di lingkungan lokal dan produksi mungkin berbeda. Untuk mengatasi masalah ini, sudah lazim untuk memperbaiki dependensi. Dan ketika dependensi sudah diperbaiki, versi spesifik yang harus dipasang oleh pip ditunjukkan, maka pip sudah berfungsi dengan baik. Pip tidak memiliki resolver, tetapi itu terjadi ketika orang lain menyelesaikan dependensi untuk itu, seperti DepHell atau Puisi.
- Mengapa topik ini sekarang mendapatkan relevansi seperti itu, seperti yang Anda pikirkan? Mengapa tidak ada yang terjadi sebelumnya, tetapi sekarang hilang, dan bahkan ke arah yang berbeda?Pertama, ekosistem Python tumbuh. Ada lebih banyak paket, mereka harus diinstal lebih banyak, dan lebih banyak masalah muncul. Kedua, masalah dengan format file telah ada sejak lama dan telah dibahas sejak lama.
Setup.py umumnya tidak dapat diurai, itu hanya dapat dieksekusi. Jika kita ingin, misalnya, untuk menulis server di Go untuk dengan cepat mendistribusikan paket untuk Python, maka kita tidak bisa hanya mengambil dan membaca setup.py, karena itu adalah file yang dapat dieksekusi. Oleh karena itu, untuk menjalankannya, Anda memerlukan Python dan lingkungan yang lengkap, dan seringkali juga agar seluruh proyek berada di dekatnya, dan beberapa dependensi tertentu diinstal. Selain semua kesulitan ini, menjalankan setup.py dapat berbahaya, karena beberapa kode lain akan dieksekusi di komputer Anda. Bahkan, menakutkan untuk mengeksekusi kode dari bawah pengguna saat ini, karena jika, misalnya, ia menerima kunci SSH pribadi saya dan mengirimkannya ke suatu tempat, itu akan menjadi tragedi besar.
Opsi kedua untuk mendefinisikan dependensi, yang telah lama ada dan semua orang bekerja dengannya, adalah requirement.txt. Hampir tidak mungkin mengurai dengan cara yang sama. Pip bisa, tetapi itu sangat, sangat sulit: fungsi yang memanggil fungsi, iterator, semuanya dicampur. Selain itu, pip dapat membaca beberapa kuncinya dari requirement.txt, misalnya, indeks untuk mengunduh dapat ditentukan. Tetapi ini tidak bekerja dengan semua kunci.
Jadi, untuk mem-parse requirement.txt, Anda harus menggunakan pip atau solusi pihak ketiga. Semua solusi pihak ketiga pada dasarnya adalah garpu dan menggunakan semacam asumsi tentang file. Tidak setiap file rumit requirement.txt yang dapat dibaca oleh pip akan dapat membaca garpu ini.
Pip sendiri tidak dimaksudkan untuk digunakan sebagai perpustakaan. Ini adalah alat eksklusif CLI yang hanya dapat digunakan dari konsol. Semua kode sumber pip disembunyikan di belakang
_internal
, dan pengembang langsung mengatakan: "Jangan gunakan ini!". Dan setiap rilis merusak kompatibilitas ke belakang. Mereka dengan jujur โโtidak menjamin kompatibilitas dan dapat mengubah apa pun kapan saja. Dan inilah yang terjadi - setiap kali rilis baru datang dengan pip, saya mempelajarinya dari CI yang rusak di DepHell.
- Bagaimana dengan bahasa lain? Apakah sama buruknya di sana, atau semua masalah ini diselesaikan di suatu tempat?Guido van Rossum baru -
baru ini dianugerahi Dijkstra Prize. Saya menghadiri kuliahnya dan bertanya kepadanya tentang dependensi Python. Guido mengatakan bahwa kecanduan dalam semua bahasa adalah kekacauan, ia berusaha untuk tidak masuk ke sana dan mempercayai komunitas untuk menyelesaikan masalah ini.
Dengan demikian, dalam Python, bekerja dengan dependensi secara bertahap diselenggarakan oleh komunitas. Solusi baru sedang muncul. Setelah Distutils dibangun dengan Python, maka orang-orang menyadari bahwa ia memiliki banyak masalah, add-on Setuptools.
easy_Install
kemudian dikembangkan untuk menginstal paket, tetapi juga memiliki masalah. Untuk mengatasinya, buat pip. Sekarang pip memiliki banyak masalah. Sumbernya terus berubah, tidak ada arsitektur, tidak ada antarmuka sama sekali.
Komunitas sedang mencoba membuat sesuatu. Misalnya, ada diskusi panjang tentang masalah yang disebut persyaratan 2.0 tentang bagaimana membuat persyaratan dapat dipahami baik oleh orang-orang (di sini adalah versi, di sini ada spidol), dan secara terprogram dari bahasa lain.
Mereka membuat Pipfile, tetapi karena pip sangat membingungkan, mereka tidak dapat menambahkan dukungan Pipfile ke dalamnya.
Pengembang ingin melakukan ini, tentu saja. Kemungkinan besar, suatu hari nanti mereka akan dapat, tetapi sejauh ini pip tidak dapat mendukung Pipfile. Oleh karena itu, kami membuat pipenv untuk bekerja dengan Pipfile dan lingkungan virtual, beberapa pembungkus lain dengan lingkungan. Namun dalam pipenv, semuanya tercampur dan membingungkan.
Untuk bahasa lain, saya suka bagaimana manajemen ketergantungan diterapkan di Go. Sebelumnya, tidak ada versi di dalamnya, ada
go get
, di mana Anda menunjukkan dari repositori mana paket untuk diunduh. Dari sudut pandang pemula, ini nyaman: Anda cukup menulis
go get
dan paket sudah ada di sistem. Dan ketika Anda mulai bekerja dengan Python, seluruh tumpukan semuanya runtuh: beberapa versi, PyPI, pip, requirement.txt, setup.py, sekarang juga Pipfile, Puisi,
__pymodules__
, dll.
Ketika Python berevolusi secara bertahap dan dengan bantuan komunitas, warisan menumpuk di ekosistem. Go baru saja
go get
, tetapi sekali lagi muncul masalah bahwa ketergantungan perlu diperbaiki sehingga, khususnya, lingkungan dapat direproduksi.
Lingkungan yang dapat dimainkan dapat dibuat menggunakan wadah buruh pelabuhan dengan semua dependensi diinstal. Tetapi kadang-kadang Anda perlu memperbarui dependensi individual. Misalnya, kami mungkin tidak siap memperbarui semuanya, karena proyek tidak memiliki cukup tes untuk membuktikan bahwa setelah pembaruan semuanya masih berfungsi. Tetapi ketergantungan tertentu mungkin perlu diperbarui, karena, katakanlah, kerentanan ditemukan di dalamnya. Untuk melakukan ini, lebih baik tidak memiliki gambar buruh pelabuhan, tetapi file yang mengatakan: "Instal versi spesifik dari paket tertentu."
Tidak ada yang seperti itu di Go, dan vendorisasi muncul: semua dependensi baru saja diambil dan dimasukkan ke dalam satu direktori. Ini adalah solusi kotor, mirip dengan
node_modules
, yang di Go telah diimplementasikan untuk beberapa waktu menggunakan solusi pihak ketiga. Dalam Python, pendekatan ini juga digunakan, misalnya, pip memiliki direktori
vendor
. Ketika Anda menginstal pip, dependensi tidak dibuat, dan Anda mungkin berpikir bahwa semuanya sangat keren dan tidak ada dependensi sama sekali, tetapi sebenarnya semuanya ada di dalam
vendor
.
Sekitar setahun yang lalu go.mod (Go Modul) muncul di Go. Ini adalah alat bawaan yang baru, tetapi
go get
juga didukung. Proyek ini berisi dua file:
- satu menggambarkan ketergantungan dengan mana proyek bekerja secara langsung;
- yang lainnya adalah file kunci, yang menjelaskan secara mutlak semua dependensi dan versi spesifiknya.
Ini adalah solusi terpusat yang keren.
Yang penting, mereka bersikeras bahwa hal-hal tertentu harus terlihat dengan cara tertentu. Misalnya, di Go, versi tersebut harus versi semantik.
Python juga memiliki spesifikasi tentang bagaimana versi akan terlihat. Untuk ini, ada PEP 440. Tapi, pertama, spesifikasinya sangat rumit: tidak hanya ada tiga komponen versi (angka), tetapi juga pra-rilis, pasca-rilis, dan era (ketika cara perubahan versi). Kedua, PEP 440 tidak diterima dengan segera, mereka juga datang secara bertahap, oleh karena itu versi lawas didukung, yang berarti bahwa apa pun dapat digunakan sebagai versi - setiap baris seperti โHello world!โ.
- Anda mengatakan bahwa komunitas mengembangkan bahasa secara bertahap, sehingga ada sejumlah besar solusi. Tapi mengapa tidak membuang semua sampah ini? Mengapa tidak membuang Distutils, meninggalkan yang lama dan tidak perlu yang tidak ada yang menggunakan, dan sebaliknya secara aktif memperkenalkan praktik dan alat baru?Untuk mempertahankan semua ini masuk akal, sehingga Anda masih dapat menginstal paket lama. Tidak mungkin untuk bersikeras bahwa itu perlu dilakukan, dan bukan sebaliknya, karena keputusan dibuat oleh masyarakat. Tidak ada pengembang Core Python yang datang dan berkata: "Itu saja, kami melakukan semuanya sekarang, dan tidak ada paku."
Go memiliki semua yang Anda butuhkan untuk bekerja dengan dependensi segera. Dengan Python, Anda perlu menginstal ulang semuanya dari luar, dan Anda masih perlu memahami apa sebenarnya. Paling sering, pip sudah cukup, tetapi sekarang opsi lain muncul.
Di situs dengan rekomendasi paket resmi dari Python Packaging Authority, grup yang membuat pip, pipenv, PyPI, ditulis menggunakan pipenv. Dengan pipenv adalah cerita lain. Pertama, memiliki resolusi yang buruk. Kedua, tidak ada rilis untuk waktu yang sangat lama dan komunitas sudah menunggu pembuatnya untuk jujur โโmengakui bahwa proyek ini sudah mati. Masalah ketiga dengan pipenv adalah ia hanya cocok untuk proyek, tetapi tidak untuk paket: Anda bisa menentukan dependensi proyek di pipenv, tetapi Anda tidak bisa menentukan nama, versinya, dan, karenanya, memasukkannya ke dalam paket untuk diunduh di PyPI. Ternyata mengikuti rekomendasi dari Python Packaging Authority dan menggunakan pipenv masih belum cukup untuk mengetahuinya.
Puisi berusaha menjadi revolusioner. Ini pada dasarnya tidak menghasilkan file setup.py, yang akan berguna untuk kompatibilitas mundur, karena Puisi ingin menjadi format baru dan satu-satunya untuk semuanya. Dia tahu cara mengumpulkan paket, dan dia memiliki file kunci, yang diperlukan untuk proyek. Meskipun demikian, Puisi memiliki banyak hal aneh, banyak fitur yang akrab tidak didukung.
- Menurut Anda apa masa depan ekosistem dalam hal bekerja dengan ketergantungan? Prediksi Anda.Semuanya kurang lebih semakin baik. Sebagai contoh, saya melihat lowongan di pip, dan seorang pengembang yang mengaturnya dijanjikan banyak uang. Mungkin pip akan menjadi solusi yang lebih universal. Tetapi Anda membutuhkan seseorang untuk menganggapnya serius: datang dan katakan bahwa kami melakukannya dengan cara ini, sekarang kami mengikuti beberapa PEP yang lebih ketat, dan akan mendesaknya (karena PEP hanyalah sebuah rekomendasi yang tidak ada orang yang benar-benar melakukannya. tidak diharuskan untuk mengikuti).
Sebagai contoh, kami memiliki cerita seperti itu: versi PyYAML tertentu dikunci dalam file kunci. Suatu hari tes pada lulus CI, kami menyebarkan ke produksi, dan semuanya jatuh di sana, karena versi PyYAML tidak ditemukan. Masalahnya adalah bahwa versi yang terkunci telah dihapus dari pypi.org. Semua orang marah, memperbarui file kunci, entah bagaimana selamat, tetapi sedimen tetap ada.
Belum lama ini, PEP 592 muncul, sudah diadopsi dan dikelola dalam pip, di mana rilis menarik menarik muncul. Yank berarti bahwa rilis tersebut belum sepenuhnya dihapus dari pypi.org - itu disembunyikan. Artinya, jika Anda menentukan bahwa Anda perlu, misalnya, versi PyYAML lebih besar dari 3.0, maka pip akan melewati rilis yang ditarik dan menginstal yang terbaru tersedia. Tetapi jika versi tertentu ditunjukkan dalam file kunci, dan versi ini adalah menarik, maka pip akan menginstalnya juga. Dengan demikian, mengunci file dan menyebarkan tidak akan rusak, tetapi, jika mungkin, versi lama tidak akan digunakan.
Hal menarik kedua adalah PEP untuk
__pymodules__
. Ini adalah lingkungan virtual yang ringan: Anda membuka direktori proyek, menulis
pip install
PyYAML, dan PyYAML diinstal bukan secara global, tetapi dalam direktori
__pymodules__
. Ketika Python dimulai di direktori ini, PyYAML tidak diimpor secara global, tetapi dari direktori ini.
Saya menyebutnya lingkungan virtual minimal, karena ada sedikit isolasi. Misalnya, tidak ada akses ke file biner. Ketika lingkungan virtual dengan pytest diinstal diaktifkan, itu dapat digunakan dari konsol: cukup tulis pytest dan lakukan sesuatu. Dengan
__pymodules__
akan tersedia untuk impor, tetapi bukan binari, karena mereka tidak akan benar-benar diinstal.
PEP ini dirancang untuk memudahkan pemula. Sehingga mereka tidak perlu berurusan dengan semua seluk-beluk lingkungan virtual, tetapi cukup instal semua yang Anda butuhkan dalam
__pymodules__
melalui pip install.
- Nah, masa depan dalam ramalan Anda lebih cerah dari sekarang.Ya, tetapi seperti yang saya katakan, jika tidak ada yang datang dan mengatakan bahwa kami sedang mengulang dan mencoba membuang warisan, maka masalahnya akan tetap ada. Sekarang kami mengumpulkan dan mengumpulkan alat-alat, dan tidak mungkin untuk benar-benar menyingkirkan salah satu dari mereka dalam waktu dekat.
- Apa pendapat Anda, mengapa tidak ada pengembang yang dapat memperbarui dependensi Hampir di mana saja - baik di perusahaan maupun di sumber terbuka - proses bekerja dengan rilis keamanan, pada prinsipnya, dengan rilis kecil atau besar baru, telah dibangun. Di mana Anda melihat masalah di sini?Paling tidak, ketika Anda ingin memperbarui dependensi, menakutkan untuk memperbarui semua dependensi, karena itu bukan fakta bahwa meskipun Anda lulus tes, semuanya akan berfungsi. Sebagai contoh, seringkali situasi ini muncul dengan Seledri, karena Seledri tidak dapat sepenuhnya diuji dalam tes. Anda dapat mengunci sesuatu, menyederhanakan sesuatu, tetapi kenyataan bahwa para pekerja sedang berlari tidak dapat diverifikasi.
Go work dengan tes diimplementasikan dengan baik, bahkan dalam tutorial Go Modul ditulis tentang cara memperbarui dependensi: Anda memperbarui dependensi tertentu dan menjalankan tes. Selain itu, tes tidak hanya berjalan pada Anda, tetapi juga ketergantungan ini.
Aspek yang menarik masih layak disebut: haruskah tes dilakukan dalam paket dengan Python? Saat Anda mengunduh paket dari pypi.org, haruskah ada tes? Secara teori, mereka harus, dan bahkan memiliki mekanisme untuk menjalankannya: di setup.py Anda dapat menentukan cara menjalankan tes, dependensi apa yang mereka miliki.
Tapi, pertama, banyak orang tidak tahu cara menjalankannya dan tidak menjalankan tes yang tergantung. Karena itu, seringkali tidak diperlukan. Kedua, seringkali tes ini memiliki perlengkapan yang sangat sulit, dan oleh karena itu untuk memasukkan tes dalam paket berarti membuat paket 6-10 kali lebih besar.
Akan sangat bagus untuk dapat mengunduh paket dengan tes dan tanpa tes. Tapi sekarang tidak ada kemungkinan seperti itu, jadi tes sering tidak dijumlahkan di dalam paket. Ada kekacauan, dan saya bahkan tidak tahu apakah mungkin untuk menjalankan tes dependensi ini ketika memperbarui dependensi.
Aspek ini tampaknya diabaikan. Tetapi dalam beberapa bahasa lain, khususnya, Go dianggap sebagai praktik yang baik, memperbarui paket di lingkungan, segera menjalankan tes untuk memastikan bahwa paket ini berfungsi dengan baik di lingkungan ini.
- Mengapa, menurut Anda, dengan Python, alat untuk versi semantik otomatis tidak populer?Saya pikir salah satu masalahnya adalah bahwa versinya dapat dijelaskan di banyak tempat. Paling sering ada tiga di antaranya: format deskripsi metadata proyek (pypi.org, poety, setup.py, dll.), Di dalam proyek itu sendiri dan dalam dokumentasi. Memutakhirkan versi di tiga tempat tidak terlalu sulit, tetapi mudah dilupakan.
DepHell memiliki tim untuk peningkatan versi. DepHell , , . semantic version, compatible version ..
, , .
Flit. Flit โ , . :
init
,
build
,
publish
install
. , , PyPI โ . Flit , . docstring . , .
DepHell Flit . description, , , .
, .
DepHell
import
, , , , . , , .
, Moscow Python Conf++ 27 . DepHell backend, web, , AI/ML, , DevOps, , IoT, infosec . , , Moscow Python Conf++.