<p>pip не поддерживает относительные пути в прямых URL-ссылках</p>

Созданный на 28 июн. 2019  ·  7Комментарии  ·  Источник: pypa/pip

Окружающая обстановка

  • версия pip: 19.1.1
  • Версия Python: 3.7
  • ОС: MacOS Mojave
Не стесняйтесь добавлять здесь дополнительную информацию о своей среде

Описание
Я не уверен, ошибка ли это six или ошибка Пипа. Простите, если он принадлежит six .

Кажется, что Pip разрешает локальные пути в install_requires через name @ ./some/path , но анализ URL-адресов ужасно нарушен.

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

В этой функции он использует urlsplit для получения отдельных компонентов входящего URL.

Вот как это выглядит после нескольких вводов:

./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='')

Обратите внимание, что последнее приводит к тому, что netloc становится . вместо пустого, а path является абсолютным, а не локальным. Это вызывает ошибку в отношении нелокальных путей. Все в порядке - я могу использовать вторую форму для удовлетворения условной логики (хотя она действительно должна поддерживать и первую).

Однако в другом месте есть противоречивая логика ...

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

Это логика, которая не работает, даже если мы удовлетворяем предыдущей логике.

Вот тестовая функция, которая показывает проблему:

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)

Вот результат для ./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='')

Все хорошо, хотя это не удовлетворяет логике первой функции, требующей схемы file: .

Вот результат для 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='')

Ой! Обратите внимание, как, когда мы «разбираем» результат первого вызова синтаксического анализа, наш path становится абсолютным file:///... .

Поэтому вторая проверка не проходит - путь не локальный. Я считаю, что это ошибка в six но ее можно уменьшить в Pip, разрешив scheme in ['file', ''] и указав пользователям использовать форму URI ./foo/bar .

С учетом этих двух противоречащих друг другу частей логики невозможно использовать локальные пути в ключах install_requires в конфигурациях distutils или setuptools .

Ожидаемое поведение
Я должен иметь возможность сделать name @ ./some/path (или, честно говоря, просто ./some/path ), чтобы указать продаваемый пакет, локальный для моей кодовой базы.

Как размножаться

#!/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

Вывод

С первого 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

Со второго 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'

РЕДАКТИРОВАТЬ:

Только что выяснилось, что RFC 3986 указывает, что относительные URL-адреса пути не разрешены в схеме file: , поэтому технически six должно вызывать ошибку в file:./foo/bar .

Однако технически это означает, что я должен иметь возможность делать в своем setup.py следующее:

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

Однако похоже, что pip создает «чистую» копию пакета в /tmp , поэтому мы получаем что-то вроде file:///tmp/pip-req-build-9u3z545j/foo/bar .

Выполняя это через нашу тестовую функцию, мы удовлетворяем условию второй функции:

>>> 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='')

Там все хорошо. «Непроанализированный» дает тот же результат, и требования netloc выполняются для условного оператора первой функции.

Однако мы все еще встречаемся с ошибкой Invalid URL , хотя логика второй функции удовлетворяется.

Поскольку pip (или distutils, или setuptools, или что-то еще) проглатывает вывод, я пошел дальше и сделал следующее в моем setup.py

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

Это подтверждает, что все файлы там, как и ожидалось, поэтому это не может быть файл или что-то в этом роде. Строка выше с "Invalid URL given" - единственное место в кодовой базе, где отображается строка.

На данный момент я не уверен, в чем проблема.

vendored dependency needs discussion bug

Самый полезный комментарий

Хорошо, я вижу проблему. setuptools , pkg-resources и pip все они используют несколько разные версии библиотеки packaging .

В pip это та версия, которую я показал выше.

Однако во всем остальном это следующее (я не уверен, какой из них «новее», но следующая логика очень ограничивает и не полностью соответствует RFC 3986, поскольку file:/// должно быть разрешено, подразумевая пустой 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")

🙄

Это означает, что, поскольку мой путь к файлу имеет file:///foo/bar а не file://localhost/foo/bar он не работает.

Вот полное решение :

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'
    ]
)

Это довольно плохой UX, смешанный с неоднозначными ошибками, которые тратят время.

Как мы можем исправить эту ситуацию?

Все 7 Комментарий

Хорошо, я вижу проблему. setuptools , pkg-resources и pip все они используют несколько разные версии библиотеки packaging .

В pip это та версия, которую я показал выше.

Однако во всем остальном это следующее (я не уверен, какой из них «новее», но следующая логика очень ограничивает и не полностью соответствует RFC 3986, поскольку file:/// должно быть разрешено, подразумевая пустой 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")

🙄

Это означает, что, поскольку мой путь к файлу имеет file:///foo/bar а не file://localhost/foo/bar он не работает.

Вот полное решение :

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'
    ]
)

Это довольно плохой UX, смешанный с неоднозначными ошибками, которые тратят время.

Как мы можем исправить эту ситуацию?

@ Qix- рад, что ты это нашел! Я бился головой об стену, пробуя все те же форматы. Это мой альтернативный вариант https://github.com/pypa/pip/issues/6162 и отказ от dependency_links.

Мы пытаемся создать частное репо и у нас нет собственного внутреннего сервера. Наше решение - публиковать пакеты в s3, а затем для их использования мы загружаем их, помещаем в локальную папку, а затем добавляем в install_requires .

Я уверен, что есть много других вариантов использования, которые выиграют от интуитивно понятного способа установки локальных пакетов.

@ryanaklein Я бы на самом деле предлагал игнорировать всю негативную реакцию на подмодули git и попробовать их (при условии, что вы используете Git). Если вы перестанете думать о них как о ветвях и начнете думать о них как о тегах (или релизах), они начнут работать очень хорошо. Они очень часто используются в мире C / C ++, и мы довольно успешно продавали пакеты Python, использующие их (конечно, помимо указанной выше ошибки!).

Может сократить расходы на сеть / $$ S3 :)

Ожидаемое поведение
Я должен иметь возможность сделать name @ ./some/path (или, честно говоря, просто ./some/path ), чтобы указать продаваемый пакет, локальный для моей кодовой базы.

Для прямой ссылки URL ( name @ ./some/path ) есть два места, где происходит работа:

  1. pypa / Packaging # 120, который отслеживает отклонение действительных прямых ссылок на URL PEP 508.
  2. Что касается пункта, то нам нужно интерпретировать относительные пути относительно чего-либо, я начал обсуждение здесь, чтобы получить отзывы о том, что имеет наибольший смысл. Мы могли бы отслеживать возможные действия здесь или в новом специальном выпуске.

Последнее было бы неприемлемо для PEP 508, поэтому было бы трудно оправдать поддержку, тем более, чтобы она работала во всех инструментах.

Это довольно плохой UX, смешанный с неоднозначными ошибками, которые тратят время.
Как мы можем исправить эту ситуацию?

5204 должен помочь с этим общим вопросом с точки зрения пипса.

Ой! Обратите внимание, как, когда мы «разбираем» результат первого вызова синтаксического анализа, наш путь становится абсолютным файлом: /// ...

Я думаю, это связано с ошибкой CPython, поднятой в проблеме 22852 - «urllib.parse неправильно удаляет пустой #fragment,? Query, // netloc»

Эта ошибка, кажется, также вызывает проблему № 3783 - см. Этот комментарий .

Каков статус этой проблемы? Срочно необходимо решение для разрешения локальных зависимостей, не связанных с PyPI, например, в контексте монолитных репозиториев.

Обратите внимание, что npm реализовал эту функцию аналогичным образом, и dependencies можно указать в package.json используя локальный путь .

См. Этот комментарий выше, чтобы узнать, что должно произойти, прежде чем это будет реализовано. pip ничего не может сделать до этого.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги