<p>pip tidak mendukung jalur relatif dalam referensi URL langsung</p>

Dibuat pada 28 Jun 2019  ·  7Komentar  ·  Sumber: pypa/pip

Lingkungan Hidup

  • versi pip: 19.1.1
  • Versi Python: 3.7.0
  • OS: MacOS Mojave
Jangan ragu untuk menambahkan lebih banyak informasi tentang lingkungan Anda di sini

Deskripsi
Saya tidak yakin apakah ini bug six atau bug Pip. Permisi jika itu milik six .

Pip tampaknya mengizinkan jalur lokal di install_requires melalui name @ ./some/path , tetapi penguraian URL sangat rusak.

https://github.com/pypa/pip/blob/a38a0eacd4650a7d9af4e6831a2c0aed0b6a0329/src/pip/_internal/download.py#L670 -L690

Dalam fungsi ini, ini menggunakan urlsplit untuk mendapatkan komponen individual dari URL masuk.

Inilah tampilannya dengan beberapa masukan:

./foo/bar -> SplitResult(scheme='', netloc='', path='./foo/bar', query='', fragment='')
file:./foo/bar -> SplitResult(scheme='file', netloc='', path='./foo/bar', query='', fragment='')
file://./foo/bar -> SplitResult(scheme='file', netloc='.', path='/foo/bar', query='', fragment='')

Perhatikan hasil yang terakhir di netloc menjadi . bukannya kosong dan path menjadi absolut, bukan lokal. Ini trip kesalahan terkait jalur non-lokal. Itu bagus dan bagus - saya dapat menggunakan bentuk kedua untuk memenuhi logika kondisional (meskipun itu benar-benar harus mendukung yang pertama juga).

Namun, ada logika yang bertentangan di tempat lain ...

https://github.com/pypa/pip/blob/169eebdb6e36a31e545804228cad94902a1ec8e9/src/pip/_vendor/packaging/requirements.py#L103 -L106

Ini adalah logika yang gagal meskipun kita memenuhi logika sebelumnya.

Berikut adalah fungsi uji yang menunjukkan masalahnya:

from six.moves.urllib import parse as urllib_parse

def tryparse(url):
    print(url)
    parsed = urllib_parse.urlparse(url)
    unparsed = urllib_parse.urlunparse(parsed)
    parsed_again = urllib_parse.urlparse(unparsed)
    print(parsed)
    print(unparsed)
    print(parsed_again)

Inilah output untuk ./foo/bar :

>>> tryparse('./foo/bar')
./foo/bar
ParseResult(scheme='', netloc='', path='./foo/bar', params='', query='', fragment='')
./foo/bar
ParseResult(scheme='', netloc='', path='./foo/bar', params='', query='', fragment='')

Semuanya bagus, meskipun tidak memenuhi logika fungsi pertama yang membutuhkan skema file: .

Inilah output untuk file:./foo/bar :

>>> tryparse('file:./foo/bar')
file:./foo/bar
ParseResult(scheme='file', netloc='', path='./foo/bar', params='', query='', fragment='')
file:///./foo/bar
ParseResult(scheme='file', netloc='', path='/./foo/bar', params='', query='', fragment='')

Ups! Perhatikan bagaimana, ketika kita "membatalkan" hasil dari pemanggilan parse pertama, path menjadi file:///... absolut.

Inilah sebabnya mengapa pemeriksaan yang disebutkan kedua gagal - jalurnya tidak lokal. Saya yakin ini adalah bug di six tetapi dapat dikurangi di Pip dengan mengizinkan scheme in ['file', ''] dan menginstruksikan pengguna untuk menggunakan formulir URI ./foo/bar .

Mengingat dua bagian logika yang kontradiktif ini, tidak mungkin menggunakan jalur lokal dalam install_requires kunci baik dalam konfigurasi distutils atau setuptools .

Perilaku yang diharapkan
Saya harus bisa melakukan name @ ./some/path (atau, sejujurnya, cukup ./some/path ) untuk menentukan paket vendor lokal ke basis kode saya.

Bagaimana cara bereproduksi

#!/usr/bin/env bash
mkdir /tmp/pip-uri-repro && cd /tmp/pip-uri-repro

mkdir -p foo/bar

cat > requirements.txt <<EOF
./foo
EOF

cat > foo/setup.py <<EOF
#!/usr/bin/env python
from setuptools import setup
setup(
    name="foo",
    version="0.1",
    install_requires=[
        "bar @ file:./bar"
    ]
)
EOF

cat > foo/bar/setup.py <<EOF
#!/usr/bin/env python
from setuptools import setup
setup(
    name="bar",
    version="0.1"
)
EOF

# (OUTPUT 1)
pip install -r requirements.txt

cat > foo/setup.py <<EOF
#!/usr/bin/env python
from setuptools import setup
setup(
    name="foo",
    version="0.1",
    install_requires=[
        # we're forced to use an absolute path
        # to make the "Invalid URL" error go
        # away, which isn't right anyway (the
        # error that is raised as a result
        # is justified)
        "bar @ file://./bar"
    ]
)
EOF

# (OUTPUT 2)
pip install -r requirements.txt

Keluaran

Dari pip install :

Processing ./foo
    ERROR: Complete output from command python setup.py egg_info:
    ERROR: error in foo setup command: 'install_requires' must be a string or list of strings containing valid project/version requirement specifiers; Invalid URL given

Dari pip install :

Processing ./foo
ERROR: Exception:
Traceback (most recent call last):
  File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/cli/base_command.py", line 178, in main
    status = self.run(options, args)
  File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/commands/install.py", line 352, in run
    resolver.resolve(requirement_set)
  File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/resolve.py", line 131, in resolve
    self._resolve_one(requirement_set, req)
  File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/resolve.py", line 294, in _resolve_one
    abstract_dist = self._get_abstract_dist_for(req_to_install)
  File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/resolve.py", line 242, in _get_abstract_dist_for
    self.require_hashes
  File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/operations/prepare.py", line 256, in prepare_linked_requirement
    path = url_to_path(req.link.url)
  File "/private/tmp/repro-pip-egg/env3/lib/python3.7/site-packages/pip/_internal/download.py", line 521, in url_to_path
    % url
ValueError: non-local file URIs are not supported on this platform: 'file://./bar'

EDIT:

Baru saja mengetahui bahwa RFC 3986 menetapkan bahwa URI jalur relatif tidak diizinkan dengan skema file: , jadi secara teknis six seharusnya melakukan kesalahan pada file:./foo/bar .

Namun, itu berarti, secara teknis, saya seharusnya dapat melakukan hal berikut di my setup.py:

PKG_DIR = os.path.dirname(os.path.abspath(__file__))
install_requires = [
    f"name @ file://{PKG_DIR}/foo/bar"
]

Namun, pip tampaknya membuat salinan paket yang "bersih" di /tmp , jadi kami mendapatkan sesuatu seperti file:///tmp/pip-req-build-9u3z545j/foo/bar .

Menjalankan itu melalui fungsi pengujian kami, kami memenuhi persyaratan fungsi kedua:

>>> tryparse('file:///tmp/pip-req-build-9u3z545j/foo/bar')
file:///tmp/pip-req-build-9u3z545j/foo/bar
ParseResult(scheme='file', netloc='', path='/tmp/pip-req-build-9u3z545j/foo/bar', params='', query='', fragment='')
file:///tmp/pip-req-build-9u3z545j/foo/bar
ParseResult(scheme='file', netloc='', path='/tmp/pip-req-build-9u3z545j/foo/bar', params='', query='', fragment='')

Semuanya baik-baik saja di sana. The "unparse" menghasilkan hasil yang sama, dan persyaratan netloc terpenuhi untuk kondisional fungsi pertama.

Namun, kami masih menemui kesalahan Invalid URL , meskipun logika fungsi kedua terpenuhi.

Karena pip (atau distutils atau setuptools atau apapun) menelan keluaran, saya melanjutkan dan melakukan hal berikut di setup.py saya

import os
PKG_DIR = os.path.dirname(os.path.abspath(__file__))
assert False, os.system(f"find {PKG_DIR}")

Yang memverifikasi bahwa semua file ada di sana, seperti yang diharapkan - jadi tidak mungkin ada file yang hilang atau semacamnya. Baris di atas yang memiliki "Invalid URL given" adalah satu-satunya tempat di basis kode tempat string muncul.

Pada titik ini, saya tidak yakin apa masalahnya.

vendored dependency needs discussion bug

Komentar yang paling membantu

Oke, saya mengerti masalahnya. setuptools , pkg-resources dan pip versi semua penggunaan yang sedikit berbeda dari packaging perpustakaan.

Dalam pip , itu versi yang saya tunjukkan di atas.

Namun, dalam hal lain, ini adalah sebagai berikut (saya tidak yakin mana yang "lebih baru", tetapi logika berikut sangat membatasi dan tidak sepenuhnya sesuai sesuai RFC 3986 karena file:/// harus diizinkan, menyiratkan kosong netloc ):

        if req.url:
            parsed_url = urlparse.urlparse(req.url)
            if not (parsed_url.scheme and parsed_url.netloc) or (
                    not parsed_url.scheme and not parsed_url.netloc):
                raise InvalidRequirement("Invalid URL given")

🙄

Itu berarti karena jalur file saya memiliki file:///foo/bar dan bukan file://localhost/foo/bar maka gagal.

Inilah solusi lengkapnya :

import os
from setuptools import setup

PKG_DIR = os.path.dirname(os.path.abspath(__file__))

setup(
    install_requires=[
        f'foo @ file://localhost{PKG_DIR}/foo/bar'
    ]
)

Ini adalah UX yang sangat buruk yang dicampur dengan kesalahan yang ambigu dan membuang-buang waktu.

Bagaimana kami dapat memperbaiki situasi ini?

Semua 7 komentar

Oke, saya mengerti masalahnya. setuptools , pkg-resources dan pip versi semua penggunaan yang sedikit berbeda dari packaging perpustakaan.

Dalam pip , itu versi yang saya tunjukkan di atas.

Namun, dalam hal lain, ini adalah sebagai berikut (saya tidak yakin mana yang "lebih baru", tetapi logika berikut sangat membatasi dan tidak sepenuhnya sesuai sesuai RFC 3986 karena file:/// harus diizinkan, menyiratkan kosong netloc ):

        if req.url:
            parsed_url = urlparse.urlparse(req.url)
            if not (parsed_url.scheme and parsed_url.netloc) or (
                    not parsed_url.scheme and not parsed_url.netloc):
                raise InvalidRequirement("Invalid URL given")

🙄

Itu berarti karena jalur file saya memiliki file:///foo/bar dan bukan file://localhost/foo/bar maka gagal.

Inilah solusi lengkapnya :

import os
from setuptools import setup

PKG_DIR = os.path.dirname(os.path.abspath(__file__))

setup(
    install_requires=[
        f'foo @ file://localhost{PKG_DIR}/foo/bar'
    ]
)

Ini adalah UX yang sangat buruk yang dicampur dengan kesalahan yang ambigu dan membuang-buang waktu.

Bagaimana kami dapat memperbaiki situasi ini?

@ Qix- senang Anda menemukan ini! Saya membenturkan kepala saya ke dinding mencoba semua format yang sama. Ini adalah opsi alternatif saya untuk https://github.com/pypa/pip/issues/6162 dan penghentian dependency_links.

Kami mencoba menyiapkan repo pribadi dan tidak memiliki server internal kami sendiri. Solusi kami adalah mempublikasikan paket ke s3 dan kemudian menggunakannya, kami mengunduhnya, meletakkannya di folder lokal, lalu menambahkannya ke install_requires .

Saya yakin ada banyak kasus penggunaan lain yang akan mendapat manfaat dari cara intuitif untuk menginstal paket lokal.

@ryanaklein Saya sebenarnya menyarankan untuk mengabaikan semua negativitas yang belum diteliti terhadap submodul git dan mencobanya (dengan asumsi Anda menggunakan Git). Jika Anda berhenti memikirkannya sebagai cabang dan mulai menganggapnya sebagai tag (atau rilis), mereka mulai bekerja dengan sangat baik. Mereka sangat sering digunakan di dunia C / C ++, dan kami menjual paket Python untuk menggunakannya dengan cukup sukses (selain bug di atas, tentu saja!).

Dapat mengurangi biaya jaringan / $$ biaya S3 :)

Perilaku yang diharapkan
Saya seharusnya bisa melakukan name @ ./some/path (atau, sejujurnya, cukup ./some/path ) untuk menentukan paket vendor lokal ke basis kode saya.

Untuk referensi URL langsung ( name @ ./some/path ) ada dua tempat di mana pekerjaan dilakukan:

  1. pypa / packaging # 120 yang melacak penolakan referensi url langsung PEP 508 yang valid
  2. di sisi pip kita perlu menafsirkan jalur relatif terhadap sesuatu, saya memulai diskusi di sini untuk mendapatkan umpan balik tentang apa yang paling masuk akal. Kami dapat melacak tindakan akhirnya di sini atau di edisi khusus baru.

Yang terakhir tidak akan dapat diterima per PEP 508, jadi akan sulit untuk membenarkan dukungan apalagi membuatnya bekerja di semua alat.

Ini adalah UX yang sangat buruk yang dicampur dengan kesalahan yang ambigu dan membuang-buang waktu.
Bagaimana kami dapat memperbaiki situasi ini?

5204 akan membantu dengan pertanyaan umum ini dari sudut pandang pip.

Ups! Perhatikan bagaimana, ketika kita "mengurai" hasil dari panggilan parse pertama, jalur kita menjadi file absolut: /// ...

Saya rasa ini disebabkan oleh bug CPython yang diangkat dalam masalah 22852 - "urllib.parse salah menghapus #fragment kosong,? Query, // netloc"

Bug itu tampaknya juga menyebabkan masalah # 3783 - lihat komentar ini.

Apa status masalah ini? Solusi untuk menyelesaikan dependensi lokal yang tidak ada di PyPI sangat dibutuhkan, misalnya dalam konteks repositori monolitik.

Perhatikan bahwa npm telah mengimplementasikan fitur ini dengan cara yang sama dan dependencies dapat ditentukan dalam package.json menggunakan jalur lokal .

Silakan lihat komentar di atas untuk mengetahui apa yang perlu terjadi sebelum ini memiliki kesempatan untuk diterapkan. pip tidak bisa melakukan apa-apa sebelumnya.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat