<p>pip unterstützt keine relativen Pfade in direkten URL-Referenzen</p>

Erstellt am 28. Juni 2019  ·  7Kommentare  ·  Quelle: pypa/pip

Umgebung

  • Pip-Version: 19.1.1
  • Python-Version: 3.7
  • Betriebssystem: MacOS Mojave
Fühlen Sie sich frei, hier weitere Informationen über Ihre Umgebung hinzuzufügen

Beschreibung
Ich bin mir nicht sicher, ob dies ein six -Fehler oder ein Pip-Fehler ist. Entschuldigen Sie, wenn es zu six .

Pip scheint lokale Pfade in install_requires über name @ ./some/path zuzulassen, aber die URL-Analyse ist schrecklich kaputt.

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

In dieser Funktion werden urlsplit , um die einzelnen Komponenten der eingehenden URL abzurufen.

So sieht das mit ein paar Eingaben aus:

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

Beachten Sie, dass das letzte Ergebnis dazu führt, dass netloc . statt leer ist und path absolut und nicht lokal ist. Dies löst den Fehler in Bezug auf nicht lokale Pfade aus. Das ist alles in Ordnung und gut - ich kann die zweite Form verwenden, um die bedingte Logik zu erfüllen (obwohl sie auch die erste wirklich unterstützen sollte).

Es gibt jedoch anderswo widersprüchliche Logik ...

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

Dies ist die Logik, die fehlschlägt, obwohl wir die vorherige Logik erfüllen.

Hier ist eine Testfunktion, die das Problem zeigt:

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)

Hier ist die Ausgabe für ./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='')

Alles gut, obwohl es nicht der Logik der ersten Funktion entspricht, ein Schema von file: verlangen.

Hier ist die Ausgabe für 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='')

Hoppla! Beachten Sie, dass, wenn wir das Ergebnis des ersten Analyseaufrufs "entparsen", unser path zu absolut file:///... .

Aus diesem Grund schlägt die zweite Prüfung fehl - der Pfad ist nicht lokal. Ich glaube, dass dies ein Fehler in six aber in Pip kann gemildert werden, indem scheme in ['file', ''] und Benutzer angewiesen werden, das URI-Formular ./foo/bar zu verwenden.

Angesichts dieser beiden widersprüchlichen Logik ist es unmöglich, lokale Pfade in install_requires Schlüsseln in distutils oder setuptools Konfigurationen zu verwenden.

Erwartetes Verhalten
Ich sollte in der Lage sein, name @ ./some/path (oder ehrlich gesagt einfach ./some/path ) zu tun, um ein in meiner Codebasis lokales Lieferpaket anzugeben.

Wie zu reproduzieren

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

Ausgabe

Ab dem ersten 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

Ab dem zweiten 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'

BEARBEITEN:

Ich habe gerade herausgefunden, dass RFC 3986 angibt, dass relative Pfad-URIs mit dem file: -Schema nicht zulässig sind. Technisch gesehen sollte six auf file:./foo/bar fehlerhaft sein.

Technisch bedeutet dies jedoch, dass ich in meiner setup.py Folgendes tun kann:

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

Pip scheint jedoch eine "saubere" Kopie des Pakets in /tmp zu erstellen, sodass wir so etwas wie file:///tmp/pip-req-build-9u3z545j/foo/bar .

Wenn wir dies durch unsere Testfunktion ausführen, erfüllen wir die Bedingung der zweiten Funktion:

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

Dort ist alles gut. Das "unparse" liefert das gleiche Ergebnis, und die netloc -Anforderungen werden für die Bedingung der ersten Funktion erfüllt.

Wir haben jedoch immer noch einen Fehler von Invalid URL , obwohl die Logik der zweiten Funktion erfüllt ist.

Da pip (oder distutils oder setuptools oder was auch immer) die Ausgabe verschluckt, habe ich in meiner setup.py Folgendes ausgeführt

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

Dadurch wird wie erwartet überprüft, ob alle Dateien vorhanden sind. Es kann also nicht sein, dass eine Datei fehlt oder so. Die Zeile darüber mit "Invalid URL given" ist die einzige Stelle in der Codebasis, an der die Zeichenfolge angezeigt wird.

Zu diesem Zeitpunkt bin ich mir nicht sicher, wo das Problem liegt.

vendored dependency needs discussion bug

Hilfreichster Kommentar

Okay, ich sehe das Problem. setuptools , pkg-resources und pip alle leicht unterschiedliche Versionen der packaging -Bibliothek.

In pip ist es die Version, die ich oben gezeigt habe.

Bei allem anderen ist es jedoch das Folgende (ich bin nicht sicher, welches das "neuere" ist, aber die folgende Logik ist sehr einschränkend und nicht vollständig konform gemäß RFC 3986, da file:/// zulässig sein sollte, was impliziert ein leeres 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")

🙄

Das heißt, da mein Dateipfad file:///foo/bar und nicht file://localhost/foo/bar hat, schlägt er fehl.

Hier ist die Komplettlösung :

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

Dies ist ziemlich schlechtes UX, gemischt mit mehrdeutigen und zeitraubenden Fehlern.

Wie können wir diese Situation verbessern?

Alle 7 Kommentare

Okay, ich sehe das Problem. setuptools , pkg-resources und pip alle leicht unterschiedliche Versionen der packaging -Bibliothek.

In pip ist es die Version, die ich oben gezeigt habe.

Bei allem anderen ist es jedoch das Folgende (ich bin nicht sicher, welches das "neuere" ist, aber die folgende Logik ist sehr einschränkend und nicht vollständig konform gemäß RFC 3986, da file:/// zulässig sein sollte, was impliziert ein leeres 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")

🙄

Das heißt, da mein Dateipfad file:///foo/bar und nicht file://localhost/foo/bar hat, schlägt er fehl.

Hier ist die Komplettlösung :

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

Dies ist ziemlich schlechtes UX, gemischt mit mehrdeutigen und zeitraubenden Fehlern.

Wie können wir diese Situation verbessern?

@ Qix- froh, dass du das gefunden hast! Ich schlug meinen Kopf gegen die Wand und versuchte alle gleichen Formate. Dies ist meine alternative Option zu https://github.com/pypa/pip/issues/6162 und die Ablehnung von dependency_links.

Wir versuchen, ein privates Repo einzurichten und haben keinen eigenen internen Server. Unsere Lösung besteht darin, Pakete auf s3 zu veröffentlichen und sie dann zu konsumieren. Wir laden sie herunter, legen sie in einem lokalen Ordner ab und fügen sie dann zu install_requires .

Ich bin sicher, dass es viele andere Anwendungsfälle gibt, die von einer intuitiven Möglichkeit zur Installation lokaler Pakete profitieren würden.

@ryanaklein Ich würde eigentlich vorschlagen, die gesamte Negativität der Forschung gegenüber Git-Submodulen zu ignorieren und sie auszuprobieren (vorausgesetzt, Sie verwenden Git). Wenn Sie aufhören, sie als Zweige zu betrachten, und anfangen, sie als Tags (oder Releases) zu betrachten, beginnen sie wirklich gut zu funktionieren. Sie werden in der C / C ++ - Welt sehr häufig verwendet, und wir haben Python-Pakete mit ihnen ziemlich erfolgreich verkauft (abgesehen von dem oben genannten Fehler natürlich!).

Könnte die Netzwerk- / $$ Kosten von S3 reduzieren :)

Erwartetes Verhalten
Ich sollte in der Lage sein, name @ ./some/path (oder ehrlich gesagt einfach ./some/path ) zu tun, um ein in meiner Codebasis lokales Lieferpaket anzugeben.

Für die direkte URL-Referenz ( name @ ./some/path ) gibt es zwei Orte, an denen gearbeitet wird:

  1. Pypa / Verpackung Nr. 120, die die Ablehnung gültiger direkter URL-Referenzen für PEP 508 verfolgt
  2. Auf der Pip-Seite müssen wir relative Pfade relativ zu etwas interpretieren. Ich habe die Diskussion hier begonnen , um Feedback zu erhalten, was am sinnvollsten ist. Wir könnten die mögliche Aktion hier oder in einer neuen speziellen Ausgabe verfolgen.

Letzteres wäre gemäß PEP 508 nicht akzeptabel, daher ist es schwer zu rechtfertigen, dass die Unterstützung für alle Tools wesentlich geringer ist.

Dies ist ziemlich schlechtes UX, gemischt mit mehrdeutigen und zeitraubenden Fehlern.
Wie können wir diese Situation verbessern?

5204 sollte bei dieser allgemeinen Frage vom Standpunkt der Pip aus helfen.

Hoppla! Beachten Sie, dass unser Pfad zur absoluten Datei wird, wenn wir das Ergebnis des ersten Analyseaufrufs "entparsen": /// ...

Ich denke, dies liegt an dem CPython-Fehler, der in Problem 22852 ausgelöst wurde - "urllib.parse entfernt fälschlicherweise leeres #fragment ,? Query, // netloc"

Dieser Fehler scheint auch das Problem # 3783 zu verursachen - siehe diesen Kommentar .

Wie ist der Status dieses Problems? Eine Lösung zum Auflösen lokaler Abhängigkeiten, die nicht von PyPI abhängen, wird dringend benötigt, beispielsweise im Zusammenhang mit monolithischen Repositorys.

Beachten Sie, dass npm diese Funktion auf ähnliche Weise implementiert hat und dependencies package.json Verwendung eines lokalen Pfads in package.json werden kann .

Bitte lesen Sie diesen Kommentar oben, um zu erfahren, was geschehen muss, bevor dies umgesetzt werden kann. pip kann vorher nichts machen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen