RE:NODE

აპლიკაციები12 წუთის საკითხავი

Python requirements და virtualenv-ები: pip-tools, uv და pin-ები

როგორ გახადო Python-ის ინსტალაცია განმეორებადი: virtualenv-ები, pin-ებიანი requirements.txt, pip-tools და uv lock ფაილები და ინსტალაციები, რომლებიც სერვერზე არ ტყდება.

0 მკითხველი

მიზანი ვიწროა და პირდაპირ თქმას იმსახურებს: ფაილების ერთსა და იმავე ნაკრებს უნდა მოჰყავდეს დაყენებული პაკეტების ერთი და იგივე ნაკრები შენს ლეპტოპზე, CI-ში და სერვერზე, დღეს და ოთხი თვის შემდეგ. Python ამის უმეტეს ნაწილს ორი მექანიზმით აღწევს - virtual environment, რომელიც ერთი აპლიკაციის პაკეტებს ყველაფერი დანარჩენისგან გამოყოფს, და requirements ფაილი, რომელიც ზუსტად ამბობს, რომელი ვერსიები ჩავარდეს მასში. განმეორებადობას თითქმის ყოველთვის მეორე ნახევარი არღვევს: ფაილი, რომელიც შენს პირდაპირ დამოკიდებულებებს ფიქსავს, მაგრამ მათსას - არა, ამიტომ განახლება, რომელიც არავის უთხოვია, შემდეგ restart-ზე ჩნდება.

ეს პოსტი აღწერს, რა არის სინამდვილეში virtualenv და როგორ ტყდება ის, როგორ დაწერო requirements ფაილი, რომელიც გაძლებს, რომელი lock-file ინსტრუმენტები ღირს გამოყენებად და რა ტყდება, როცა ინსტალაცია პატარა სერვერზე მიდის და არა შენს მანქანაზე.

რა არის virtualenv სინამდვილეში#

Virtual environment არის დირექტორია Python-ის პაკეტების მექანიზმის ასლითა და საკუთარი site-packages-ით. მისი შექმნა ჩაშენებულია:

bash
$ python -m venv .venv$ .venv/bin/python -m pip install --upgrade pip$ .venv/bin/pip install -r requirements.txt

აქ მაგიის ნაკლებია, ვიდრე ხალხს ჰგონია. .venv/bin/python არის symlink ან პატარა ასლი, რომელიც იმ interpreter-ზე მიუთითებს, რომელმაც ის შექმნა. .venv/pyvenv.cfg ჩაწერს, რომელი interpreter იყო ეს და ჩანს თუ არა სისტემის პაკეტები. აქტივაცია - source .venv/bin/activate - არც რამე ჭკვიანურს აკეთებს: ის .venv/bin-ს შენი PATH-ის დასაწყისში აყენებს და VIRTUAL_ENV-ს ადგენს. ეს არის მთელი ხრიკი.

სწორედ ამიტომ სერვერზე უკეთესი ჩვევაა .venv/bin/python-ისა და .venv/bin/pip-ის სრული გზებით გამოძახება. გაშვების ბრძანებაში გასააქტიურებელი shell სესია არ არის, რისკი, რომ სკრიპტს გააქტიურება დაავიწყდა, და დაბნეულობა იმაზე, რომელი interpreter მუშაობს. ყველაფერი, რასაც python manage.py migrate დაწერდი, ხდება .venv/bin/python manage.py migrate.

venv-ის სამი თვისება პროდაქშენში მნიშვნელოვანია:

  • ის გადატანადი არ არის. .venv/bin-ში console სკრიპტებს აბსოლუტური shebang ხაზი აქვს, pyvenv.cfg კი აბსოლუტურ გზას ინახავს. გადაარქვი სახელი დირექტორიას მის ზემოთ, ან გადაიტანე აპლიკაცია, და გარემო მუშაობას წყვეტს. გზების შეკეთების ნაცვლად თავიდან შექმენი.
  • ის ერთ interpreter-ის ვერსიაზეა მიბმული. პაკეტები ცხოვრობს .venv/lib/python3.12/site-packages-ში. თუ საბაზო image 3.12-დან 3.13-ზე გადადის, გარემო არ მიჰყვება, და ModuleNotFoundError-ს იღებ იმაზე, რაც დისკზე ცხადად არის.
  • ის არასოდეს ეკუთვნის git-ს. დაამატე .venv/ .gitignore-ში. კომიტირებული გარემო არის ასობით მეგაბაიტი პლატფორმაზე მიბმული binary-ებისა, რომლებიც სხვაგან არ გაეშვება.

თანამედროვე Debian-სა და Ubuntu-ზე შეგხვდება ასევე error: externally-managed-environment, როცა სისტემურ Python-ში pip install-ს ცდი. ეს შეტყობინება დისტრიბუციაა, რომელიც თავის პაკეტებს იცავს, და სწორი რეაქცია venv-ის შექმნაა. --break-system-packages ზუსტად არის დასახელებული.

requirements.txt: რა დააფიქსირო და რა ტყდება#

Requirements ფაილი არის specifier-ების სია, თითო ხაზზე. specifier-ები, რომლებსაც გამოიყენებ:

Specifierნიშნავსგამოიყენე
django==5.0.6ზუსტად ამ ვერსიასყველაფრისთვის სერვერზე
django~=5.0.6>=5.0.6, <5.1.0ბიბლიოთეკებისთვის, რომლებსაც თავად ინახავ
django>=5.0ამას ან უფრო ახალსარაფრისთვის, რასაც deploy-ავ
djangoრასაც დღეს გადაწყვეტსარაფრისთვის, არასოდეს

მარცხი აშკარა არ არის. უმეტესობა თავის პირდაპირ დამოკიდებულებებს ფიქსავს. რასაც არ ფიქსავენ, ეს დამოკიდებულებების დამოკიდებულებებია, და სწორედ იქ ცხოვრობს სიურპრიზი: შენ web framework-ი დააფიქსირე, framework-ი დამოკიდებულია template ბიბლიოთეკაზე ფხვიერი დიაპაზონით, ის ბიბლიოთეკა ქცევის ცვლილებიან რელიზს უშვებს, და შენი შემდეგი deploy მას იღებს, რადგან არაფერს უთქვამს სხვაგვარად.

პირველი ჩვეულებრივი პასუხია pip freeze > requirements.txt, რომელიც ყველაფერს იჭერს, რაც დაყენებულია. მას სამი ცნობილი პრობლემა აქვს. ის შენი მანქანის მდგომარეობას წერს და არა შენს განზრახვას, ამიტომ მოგვიანებით ვერავინ გაარკვევს, რომელი პაკეტები აირჩიე და რომლები მოჰყვა თან. ის იჭერს editable ინსტალაციებსა და ლოკალურ გზებს, რომლებიც სხვა მანქანაზე არაფერს ნიშნავს. და ის სიამოვნებით წერს, რაშიც შენი გარემო გადაიზარდა, იმ პაკეტის ჩათვლით, რომელიც ერთხელ რაღაცის გასაცდელად დააყენე.

შაბლონი, რომელიც ამას წყვეტს, ორი ფაილია. requirements.in შეიცავს იმ რამდენიმე პაკეტს, რომელიც შენ აირჩიე, თუ გინდა, ფხვიერად. ინსტრუმენტი ერთხელ წყვეტს მას და წერს requirements.txt-ს, რომელიც ხის ყველა პაკეტს ზუსტ ვერსიაზე შეიცავს. შენ პირველს ცვლი, ორივეს აკომიტებ, სერვერი კი მხოლოდ მეორეს აყენებს.

requirements.in
django~=5.0gunicornpsycopg[binary]whitenoise
bash
$ pip-compile requirements.in -o requirements.txt
requirements.txt (generated)
asgiref==3.8.1    # via djangodjango==5.0.6    # via -r requirements.ingunicorn==22.0.0    # via -r requirements.inpackaging==24.1    # via gunicorn

# via კომენტარები არის მიზეზი, რის გამოც ღირს ამ ფორმატის ზედმეტი ფაილი: ექვს თვეში ხედავ, შენი რომელი არჩევანი მოიტანა პაკეტი, და .in ფაილიდან ხაზის წაშლა მას და ყველაფერს, რაც მან მოიტანა, შლის.

Lock ფაილები: pip-tools, uv, Poetry და Pipenv#

ამ საქმეს ოთხი ინსტრუმენტი აკეთებს. ისინი იმით განსხვავდებიან, რამდენი სხვა რამის აღებას ითვისებენ.

ინსტრუმენტიშესატანიLock ფაილიინსტალაცია სერვერზე
pip-toolsrequirements.inrequirements.txtpip install -r requirements.txt
uvrequirements.in ან pyproject.tomlrequirements.txt ან uv.lockuv pip sync ან uv sync
Poetrypyproject.tomlpoetry.lockpoetry install --only main
PipenvPipfilePipfile.lockpipenv install --deploy

pip-tools არის ყველაზე პატარა ნაბიჯი იქიდან, სადაც პროექტების უმეტესობა უკვე არის. ის აწარმოებს ჩვეულებრივ requirements ფაილს, რომელსაც ნებისმიერი pip დააყენებს, ამიტომ სერვერზე არაფერმა არ უნდა იცოდეს, რომ ინსტრუმენტი არსებობს. pip-sync requirements.txt pip install-ზე უფრო შორს მიდის და გარემოდან იმასაც შლის, რაც ფაილში არ არის, და ეს არის განსხვავება "პაკეტები, რომლებიც მჭირდება, არსებობს" და "გარემო ფაილს ემთხვევა" შორის.

uv იგივე საქმეს სიდიდის რიგით უფრო სწრაფად აკეთებს და venv-სა და pip-საც შეუძლია ჩაანაცვლოს. uv venv ქმნის გარემოს, uv pip compile requirements.in -o requirements.txt წყვეტს, uv pip sync requirements.txt აყენებს. მას ასევე აქვს პროექტის რეჟიმი, რომელიც pyproject.toml-სა და uv.lock-ზეა აგებული, სადაც uv sync ქმნის გარემოს და დაბლოკილ ნაკრებს ერთი ბრძანებით აყენებს. სისწრაფე გლობალური cache-იდან და Rust-ზე დაწერილი resolver-იდან მოდის; ნელ სერვერზე განსხვავება ოცდაათწამიან და სამწამიან ინსტალაციას შორის ცვლის, რამდენად ხარ მზად, ყოველ restart-ზე თავიდან დააყენო.

Poetry მართავს დამოკიდებულებებს, გარემოებსა და პაკეტირებას ერთად, საკუთარი resolver-ითა და pyproject.toml-ით, როგორც ჭეშმარიტების წყაროთი. ის კარგად ერგება ბიბლიოთეკას და გონივრულად - აპლიკაციას, ერთი გაფრთხილებით deploy-ისთვის: სერვერს ახლა Poetry უნდა ჰქონდეს დაყენებული, სანამ რამის დაყენებას შეძლებს. ჩვეულებრივ requirements ფაილში ექსპორტი შესაძლებელია - ახალ ვერსიებში plugin-ით - და სერვერის სიმარტივის შენარჩუნების ჩვეულებრივი გზაა.

Pipenv იგივეს აკეთებს Pipfile-ითა და Pipfile.lock-ით. ის ჯერ კიდევ მხარდაჭერილია და მუშაობს; უბრალოდ, ახალ პროექტებში ნაკლებადაა გავრცელებული, ვიდრე ადრე იყო.

თუ აზრი არ გაქვს, გამოიყენე uv .in ფაილითა და დაკომპილირებული requirements.txt-ით. მიიღებ lock ფაილს, სერვერს არაფერი დასჭირდება pip-ის გარდა, თუ ოდესმე ინსტრუმენტის მიტოვება მოგინდება, და ინსტალაცია საკმარისად სწრაფია, რომ ყოველ deploy-ზე გაეშვას.

requirements.inპაკეტები, რომლებიც აირჩიეpip-compile or uvწყვეტს ერთხელrequirements.txtზუსტი ვერსიები, hash-ებილეპტოპიCIსერვერი
ერთი resolve, სამი იდენტური ინსტალაცია

Hash-ები და როდის ღირს ისინი#

დაფიქსირებული ვერსია ამბობს, რომელი რელიზი დააყენო. hash ამბობს, რომელი ბაიტები. --generate-hashes-ით დაკომპილირებული ფაილი ყოველი artefact-ისთვის digest-ს ატარებს, და pip უარს ამბობს ყველაფერზე, რაც არ ემთხვევა:

bash
$ pip-compile --generate-hashes requirements.in -o requirements.txt$ pip install --require-hashes -r requirements.txt

ეს იცავს იმისგან, რომ index-მა სხვა რამე მოგაწოდოს, ვიდრე ის, რაც გატესტე - გატეხილი mirror, ხელახლა ატვირთული artefact, შუაში ჩადგმული proxy. ღირებულება რეალურია: hash რეჟიმი ითხოვს, რომ ყველა დამოკიდებულება hash-ით იყოს დაფიქსირებული, ამიტომ მოგვიანებით ვერაფერს დააყენებ ad hoc, ფაილი კი გრძელი და ხმაურიანი ხდება review-ებში. ის ღირს ყველაფრისთვის, რაც ფულს ან credentials-ს ეხება, და გამოსატოვებელია hobby პროექტისთვის. ნებისმიერ შემთხვევაში, ვერსიები დააფიქსირე.

სანამ აქ ხარ, pip-audit ამოწმებს გარემოს ან requirements ფაილს Python-ის advisory ბაზის წინააღმდეგ და გეუბნება, რომელ pin-ს აქვს ცნობილი სისუსტეები. ეს ხუთი წუთის უკეთესი გამოყენებაა, ვიდრე ყველაფრის დაბრმავებით განახლება.

ინსტალაცია სერვერზე#

ინსტალაცია deploy ეტაპს ეკუთვნის, და უმეტეს პანელზე deploy ეტაპი გაშვების ბრძანებაა:

bash
$ .venv/bin/pip install --no-cache-dir -r requirements.txt \  && .venv/bin/gunicorn myproject.wsgi:application --bind 0.0.0.0:8000

pip გამოტოვებს ყველაფერს, რაც უკვე დაკმაყოფილებულია, ამიტომ restart დამოკიდებულებების ცვლილების გარეშე წამებს ჯდება და არა სრულ ინსტალაციას. ეს მას ტოვებს უსაფრთხოდ, და ეს არის აზრი: გაშვებული კოდი და გამოცხადებული დამოკიდებულებები ვერასოდეს დაშორდება, თუ ერთი მეორიდან ყოველ ჯერზე ყენდება, როცა პროცესი იწყება. ამ ბრძანების დანარჩენი - worker-ების რაოდენობა, bind მისამართი, პორტი - მოცემულია FastAPI-ის ან Flask-ის deploy-ში.

რამდენიმე გარემოს ცვლადი ამას უფრო წყნარსა და პატარას ხდის:

env
PIP_DISABLE_PIP_VERSION_CHECK=1PIP_NO_CACHE_DIR=1PYTHONDONTWRITEBYTECODE=1PYTHONUNBUFFERED=1

ბოლო პაკეტირებას არ ეხება, მაგრამ ეს პარამეტრია, რომელსაც ხალხი ყველაზე ხშირად გამოტოვებს: მის გარეშე Python standard output-ს buffer-ავს, როცა ტერმინალზე არ არის მიბმული, და კონსოლი არაფერს აჩვენებს, სანამ buffer არ შეივსება. სად იდება ეს ცვლადები და რომელი მათგანია საიდუმლო, არის თემა გარემოს ცვლადები და საიდუმლოები-ში. RE:NODE-ზე პანელის კონსოლი გაუფილტრავი ცოცხალი გამოტანაა ბრძანების ხაზით, ამიტომ ეს პირველი ადგილია, სადაც უყურებ, როცა ინსტალაცია ვარდება - და ის ცარიელია, სანამ ამ ცვლადს არ დააყენებ.

სადაც ჰოსტი შენს repository-ს GitHub-იდან ყოველ გაშვებაზე იღებს, როგორც აპლიკაციის გეგმებზე, თანმიმდევრობაა: pull, install, run. ორი გადამრთველი აკონტროლებს მას - pull გაშვებისას და deploy push-ზე, რომელიც უკვე გაშვებულ სერვერს გადატვირთავს - და ყოველ deploy-ს საკუთარი ჩანაწერი აქვს, ამიტომ deploy, რომელმაც სუფთად დააყენა, და deploy, რომელიც ინსტალაციისას დაეცა, მოგვიანებით გარჩევადია. Node აპლიკაციის deploy GitHub-იდან იგივე ორ გადამრთველს JavaScript-ის მხრიდან გადის; მექანიზმი იდენტურია.

Wheel-ები, კომპილატორები და პაკეტები, რომლებიც ტკივილს იწვევს#

პაკეტების უმეტესობა ყენდება wheel-ის სახით: წინასწარ აგებული არქივი, რომელიც წამებში იხსნება. ზოგი არა, და მაშინ pip უბრუნდება წყაროდან აგებას იმ მანქანაზე, რომელიც აყენებს. ნახევარ core-იან გეგმაზე ეს განსხვავებაა ხუთწამიან და ათწუთიან ინსტალაციას შორის, ან ინსტალაციას, რომელიც კომპილატორის შეცდომით ვარდება header ფაილზე, რომელიც არავის აქვს.

ჩვეულებრივი დამნაშავეები და რა უნდა ქნა:

  • psycopg2 libpq-ის წინააღმდეგ აიგება. გამოიყენე psycopg2-binary, ან თანამედროვე psycopg[binary], რომლებიც wheel-ებს აგზავნის. ამ არჩევანის Django-ს მხარე აღწერილია Django-ს deploy პროდაქშენში-ში.
  • cryptography-ს Rust toolchain სჭირდება, როცა შესაბამისი wheel არ არსებობს. ის wheel-ებს ჩვეულებრივი Linux პლატფორმებისთვის აქვეყნებს, ამიტომ ეს მხოლოდ უჩვეულო არქიტექტურებზე ან ძალიან ძველ pip ვერსიებზე გვაწუხებს.
  • Pillow, lxml და mysqlclient სამივეს სისტემური ბიბლიოთეკები სჭირდება, როცა წყაროდან აიგება. აირჩიე wheel; ჯერ pip განაახლე, რადგან wheel-ის თავსებადობის tag-ები დროთა განმავლობაში გაუმჯობესდა და უძველესი pip უგულებელყოფს wheel-ს, რომელიც იმუშავებდა.
  • numpy, pandas და ყველაფერს მეცნიერულს შესანიშნავი და უზარმაზარი wheel-ები აქვს. ისინი კარგად ყენდება და დისკს ჭამს.

რომ იმაზე ადრე გაიგო, ვიდრე ძნელი გზით გაიგებ, სრულად უარყავი წყაროდან აგებები:

bash
$ pip install --only-binary=:all: -r requirements.txt

თუ ეს ვარდება, შეცდომა ასახელებს პაკეტს, რომელსაც შენი პლატფორმისთვის wheel არ აქვს, და ეს გაცილებით უკეთესი მარცხია, ვიდრე ღამის თორმეტზე deploy-ის დროს აღმოჩენა.

დისკი, cache-ები და პატარა გეგმები#

აპლიკაციის გეგმის დისკი სასრულია - 5 GB ყველაზე პატარა tier-ზე, 50 GB-მდე ზედაზე - და Python მას სამი გზით ავსებს: გარემო, pip-ის cache და კომპილირებული bytecode. შეამოწმე du -sh .venv-ით და pip cache dir-ით.

უბრალო web აპლიკაციის venv 60-150 MB-ია. დაამატე pandas და numpy და რამდენიმე ასეულზე ხარ. დაამატე machine-learning stack და გიგაბაიტები ნორმალურია, რა დროსაც დისკი იმ გეგმის ნაწილი ხდება, რომელიც გჭირდება და არა შემდგომი ფიქრი. pip cache purge და uv cache clean ადგილს მაშინვე ათავისუფლებს; --no-cache-dir ჯერ კიდევ არ აძლევს მის დაგროვებას, შემდეგ ინსტალაციაზე თავიდან ჩამოტვირთვის ფასად.

ერთი venv თითო აპლიკაციაზე, ყოველთვის. ორი აპლიკაცია, რომელიც გარემოს იზიარებს, ნიშნავს, რომ ერთი მეორის გარეშე ვერ განახლდება, რაც გარემოების არსებობის აზრს ანადგურებს. თუ ორ აპლიკაციას მართლა სხვადასხვა Python ვერსია სჭირდება, მათ სხვადასხვა სერვერი სჭირდებათ - საბაზო image გაძლევს interpreter-ის ერთ ვერსიას, მეორის ხელით დაყენება კი მოვლის საქმეა, რომელიც არ მოგეწონება.

რა ტყდება#

`ModuleNotFoundError` პაკეტზე, რომელიც ცხადად დაყენებულია. ორი interpreter. სისტემური pip-ით დააყენე და .venv/bin/python-ს უშვებ, ან პირიქით. .venv/bin/python -c "import sys; print(sys.executable)" ამას წყვეტს.

ლოკალურად მუშაობდა და სერვერზე გატყდა. ხეში რაღაც დაუფიქსირებელი იყო. შენი ლოკალური გარემო requirements ფაილიდან თავიდან შექმენი - წაშალე .venv, გააკეთე ახალი, დააყენე - და ის ჩვეულებრივ ლოკალურადაც გატყდება, რაც გაცილებით ადვილი გასამართია.

ინსტალაცია ათ წუთს გრძელდება. წყაროდან აგება. გაუშვი --only-binary=:all:-ით, რომ იპოვო, შემდეგ გადადი პაკეტზე, რომელიც wheel-ებს აქვეყნებს.

`No space left on device` ინსტალაციისას. Cache პლუს ნაწილობრივ გახსნილი wheel. გაასუფთავე cache, შემდეგ გამოიყენე --no-cache-dir გაშვების ბრძანებაში.

პაკეტმა restart-ზე თავად განახლდა. ხეში სადღაც დიაპაზონია და არა pin. დააკომპილირე lock ფაილი და დააყენე მხოლოდ მისგან.

`error: externally-managed-environment`. სისტემურ Python-ში აყენებ. გააკეთე venv.

გარემო პლატფორმის განახლების შემდეგ გატყდა. საბაზო interpreter ვერსიით გადავიდა და venv ისევ ძველ გზაზე მიუთითებს. წაშალე .venv და თავიდან შექმენი; სწორედ ამიტომ არის გარემო არასოდეს git-ში და requirements ფაილი ყოველთვის.

FAQ#

მჭირდება virtualenv კონტეინერის შიგნით?

მკაცრად - არა, კონტეინერი აპლიკაციას უკვე ახარისხებს. პრაქტიკაში venv მაინც ეხმარება: ის შენს პაკეტებს image-ის დაყენებულებისგან ყოფს, pip-ს ლოკალურად და დისტანციურად ერთნაირად ამუშავებს და თავიდან აიცილებს externally-managed-environment შეცდომას იმ დისტრიბუციებზე, რომლებიც PEP 668-ს ავალდებულებენ. ფასი დირექტორიაა.

საკმარისია pip freeze?

ერთი პროექტისთვის, რომელიც არასოდეს იცვლება, მუშაობს. ის მანქანის მდგომარეობას წერს და არა შენს განზრახვას, კარგავს განსხვავებას შენ მიერ არჩეულ და მემკვიდრეობით მიღებულ პაკეტებს შორის და შეიძლება ლოკალურ გზებს იჭერდეს. ორი ფაილი - ერთი, რომელსაც ცვლი, და მეორე, მისგან დაკომპილირებული - იმავე კითხვას პასუხობს და იკითხება.

უნდა დავაკომიტო lock ფაილი?

კი. Lock ფაილი განმეორებადი ნაწილია; repository-ში მის გარეშე ინსტალაცია სერვერზე ახალი resolve-ია, რომელიც შეიძლება განსხვავდებოდეს იმისგან, რაც გატესტე. დააკომიტე როგორც შესატანი ფაილი, ისე დაკომპილირებული გამოტანა და შეცვლისას diff-ს გადახედე.

uv თუ pip-tools?

ორივე აწარმოებს ჩვეულებრივ requirements ფაილს, რომელსაც ნებისმიერი pip დააყენებს, ამიტომ არჩევანი შექცევადია. uv მკვეთრად უფრო სწრაფია და შეუძლია გარემოს შექმნა და ბრძანებების გაშვებაც; pip-tools უფრო ძველია, ფარგლებით უფრო პატარა და სრულიად პროგნოზირებადი. ნელ სერვერზე სისწრაფე უფრო მნიშვნელოვანია, ვიდრე ჟღერს, რადგან ის წყვეტს, ყოველ გაშვებაზე თავიდან დაყენება ასატანია თუ არა.

როგორ განვაახლო ერთი პაკეტი უსაფრთხოდ?

შეცვალე ის შესატან ფაილში, თავიდან დააკომპილირე და წაიკითხე გენერირებული ფაილის diff, სანამ დააკომიტებ. pip-tools-ით ეს არის pip-compile --upgrade-package django; uv-ს იგივე დროშა აქვს. ერთი პაკეტის განზრახ განახლება და წაკითხვა, რა გადაადგილდა მასთან ერთად, არის მთელი დისციპლინა.


კომენტარები

სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.

0/2000