Umgebung
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.
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 ...
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.
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:
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?
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.
Hilfreichster Kommentar
Okay, ich sehe das Problem.
setuptools
,pkg-resources
undpip
alle leicht unterschiedliche Versionen derpackaging
-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 leeresnetloc
):🙄
Das heißt, da mein Dateipfad
file:///foo/bar
und nichtfile://localhost/foo/bar
hat, schlägt er fehl.Hier ist die Komplettlösung :
Dies ist ziemlich schlechtes UX, gemischt mit mehrdeutigen und zeitraubenden Fehlern.
Wie können wir diese Situation verbessern?