<p>pip不支持直接URL引用中的相对路径</p>

创建于 2019-06-28  ·  7评论  ·  资料来源: pypa/pip

环境

  • 点子版本:19.1.1
  • Python版本:3.7
  • 操作系统:MacOS Mojave
随时在此处添加有关您的环境的更多信息

描述
我不确定这是six错误还是Pip错误。 请问如果它属于six

Pip似乎允许通过name @ ./some/pathinstall_requires使用本地路径,但是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的错误,但可以通过允许scheme in ['file', '']并指示用户使用./foo/bar URI形式在Pip中加以缓解。

鉴于这两个相互矛盾的逻辑,不可能在distutilssetuptools配置的install_requires键中使用本地路径。

预期行为
我应该能够执行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指定file:方案不允许相对路径URI,因此从技术上讲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='')

那里一切都很好。 “ unparse”产生相同的结果,并且第一个函数的条件满足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

最有用的评论

好的,我明白了。 setuptoolspkg-resourcespip都使用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条评论

好的,我明白了。 setuptoolspkg-resourcespip都使用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,它跟踪有效的PEP 508直接网址引用的拒绝
  2. 在点子方面,我们需要解释相对于某事物的相对路径,我在这里开始讨论以获取最有意义的反馈。 我们可以在此处或在新的专门期刊中跟踪最终的操作。

根据PEP 508,后者是不可接受的,因此很难证明支持少得多,使其在所有工具上都能正常工作。

这是非常糟糕的UX,混杂了模棱两可和浪费时间的错误。
我们如何改善这种情况?

5204应该从点子的角度帮助解决这个一般性问题。

糟糕! 请注意,当我们从第一个解析调用中“解析”结果时,我们的路径变为绝对file:///...。

我认为这是由于问题22852中引发的CPython错误

该错误似乎也导致了问题#3783-请参阅此评论

此问题的状态如何? 迫切需要一种解决不依赖PyPI的本地依赖关系的解决方案,例如在整体存储库中。

请注意,npm以类似的方式实现了此功能,可以在package.json使用本地路径指定dependencies

在上方查看

此页面是否有帮助?
0 / 5 - 0 等级