环境
描述
我不确定这是six
错误还是Pip错误。 请问如果它属于six
。
Pip似乎允许通过name @ ./some/path
在install_requires
使用本地路径,但是URL解析被严重破坏了。
在此函数中,它使用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
是绝对的而不是本地的。 这会触发有关非本地路径的错误。 一切都很好,我可以使用第二种形式来满足条件逻辑(尽管它确实也应该支持第一种形式)。
但是,其他地方存在矛盾的逻辑...
即使我们满足先前的逻辑,这也是失败的逻辑。
这是显示问题的测试函数:
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中加以缓解。
鉴于这两个相互矛盾的逻辑,不可能在distutils
或setuptools
配置的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"
是代码库中字符串显示的唯一位置。
在这一点上,我不确定是什么问题。
好的,我明白了。 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
),有两个地方在工作:
根据PEP 508,后者是不可接受的,因此很难证明支持少得多,使其在所有工具上都能正常工作。
这是非常糟糕的UX,混杂了模棱两可和浪费时间的错误。
我们如何改善这种情况?
此问题的状态如何? 迫切需要一种解决不依赖PyPI的本地依赖关系的解决方案,例如在整体存储库中。
请注意,npm以类似的方式实现了此功能,可以在package.json
使用本地路径指定dependencies
。
请在上方查看
最有用的评论
好的,我明白了。
setuptools
,pkg-resources
和pip
都使用packaging
库的稍有不同的版本。在
pip
,这是我上面显示的版本。但是,在其他所有内容中,都是以下内容(我不确定哪个是“较新的”内容,但是以下逻辑是非常有限的,并且不完全符合RFC 3986的规定,因为应该允许
file:///
,这意味着空的netloc
):🙄
这意味着由于我的文件路径具有
file:///foo/bar
而不是file://localhost/foo/bar
因此它失败了。这是完整的解决方案:
这是非常糟糕的UX,混杂了模棱两可和浪费时间的错误。
我们如何改善这种情况?