<p>pipは、直接URL参照の相対パスをサポートしていません</p>

作成日 2019年06月28日  ·  7コメント  ·  ソース: pypa/pip

環境

  • pipバージョン:19.1.1
  • Pythonバージョン:3.7
  • OS:MacOS Mojave
ここに環境に関する詳細情報を自由に追加してください

説明
これがsixバグなのか、Pipのバグなのかわかりません。 sixに属している場合は、すみません。

Pipはinstall_requiresを介してname @ ./some/path install_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='')

最後の1つでは、 netlocが空ではなく.になり、 pathがローカルではなく絶対になります。 これにより、非ローカルパスに関するエラーが発生します。 それはすべて問題ありません。2番目の形式を使用して条件付きロジックを満たすことができます(ただし、実際には最初の形式もサポートする必要があります)。

ただし、他の場所で競合するロジックがあります...

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:///...なることに注意してください。

これが、2番目に述べたチェックが失敗する理由です-パスはローカルではありません。 これはsixバグだと思いますが、 scheme in ['file', '']を許可し、ユーザーに./foo/bar URIフォームを使用するように指示することで、Pipで軽減できます。

これらの2つの相反するロジックを考えると、 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

2番目の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が相対パスURIがfile:スキームでは許可されないことを指定していることがわかったので、技術的にはsixfile:./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ようなものが得られます。

これをテスト関数で実行すると、2番目の関数の条件が満たされます。

>>> 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要件が満たされます。

ただし、2番目の関数のロジックが満たされていても、 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-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件

さて、問題がわかりました。 setuptoolspkg-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 )の場合、作業が行われている場所は2つあります。

  1. 有効なPEP508直接URL参照の拒否を追跡するpypa / packages#120
  2. ピップ側では、何かに関連する相対パスを解釈する必要があるので、ここで議論を開始して、何が最も理にかなっているのかについてのフィードバックを得ました。 ここまたは新しい専用の問題で、最終的なアクションを追跡できます。

後者はPEP508に従って受け入れられないため、すべてのツールで機能するようにサポートすることを正当化するのは難しいでしょう。

これは、あいまいで時間の無駄なエラーと混ざり合ったかなり悪いUXです。
どうすればこの状況を改善できますか?

5204は、ピップの観点からこの一般的な質問に役立つはずです。

おっと! 最初の解析呼び出しの結果を「解析解除」すると、パスが絶対ファイル:/// ...になることに注意してください。

これは、問題22852で発生したCPythonのバグが原因だと思います

そのバグは問題#3783も引き起こしているようです–このコメントを参照してください。

この問題の状況はどうなっていますか? PyPIにないローカル依存関係を解決するためのソリューションは、たとえばモノリシックリポジトリのコンテキストで緊急に必要とされています。

npmはこの機能を同様の方法で実装しており、 dependenciesローカルパスを使用してpackage.jsonで指定できることに注意してください。

これが実施される前に何が起こる必要があるかを知るために、上記のこのコメントを参照し

このページは役に立ちましたか?
0 / 5 - 0 評価