<p>pip no admite rutas relativas en referencias directas de URL</p>

Creado en 28 jun. 2019  ·  7Comentarios  ·  Fuente: pypa/pip

Medio ambiente

  • versión pip: 19.1.1
  • Versión de Python: 3.7
  • SO: MacOS Mojave
No dude en agregar más información sobre su entorno aquí

Descripción
No estoy seguro de si esto es un error de six o un error de Pip. Disculpe si pertenece a six .

Pip parece permitir rutas locales en install_requires través de name @ ./some/path , pero el análisis de URL está terriblemente roto.

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

En esta función, usa urlsplit para obtener los componentes individuales de la URL entrante.

Así es como se ve con algunas piezas de entrada:

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

Observe que el último da como resultado que netloc sea . lugar de estar vacío y que path sea ​​absoluto, no local. Esto dispara el error con respecto a rutas no locales. Eso está muy bien, puedo usar la segunda forma para satisfacer la lógica condicional (aunque realmente debería admitir la primera también).

Sin embargo, hay una lógica conflictiva en otros lugares ...

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

Esta es la lógica que falla a pesar de que satisfacemos la lógica anterior.

Aquí hay una función de prueba que muestra el problema:

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)

Aquí está la salida de ./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='')

Todo bien, aunque no satisface la lógica de la primera función de requerir un esquema de file: .

Aquí está la salida de 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='')

¡Ups! Observe cómo, cuando "descomprimimos" el resultado de la primera llamada de análisis, nuestro path convierte en absoluto file:///... .

Esta es la razón por la que falla la segunda verificación mencionada: la ruta no es local. Creo que esto es un error en six pero se puede mitigar en Pip permitiendo scheme in ['file', ''] e indicando a los usuarios que usen el formulario ./foo/bar URI.

Teniendo en cuenta estas dos piezas contradictorias de la lógica, es imposible utilizar las rutas locales en install_requires llaves, ya sea en distutils o setuptools configuraciones.

Comportamiento esperado
Debería poder hacer name @ ./some/path (o, honestamente, simplemente ./some/path ) para especificar un paquete vendored local a mi base de código.

Cómo reproducir

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

Salida

Desde el primer 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

Desde el segundo 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'

EDITAR:

Acabo de descubrir que RFC 3986 especifica que los URI de ruta relativa no están permitidos con el esquema file: , por lo que técnicamente six debería tener un error en file:./foo/bar .

Sin embargo, eso significa que, técnicamente, debería poder hacer lo siguiente en mi setup.py:

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

Sin embargo, pip parece estar creando una copia "limpia" del paquete en /tmp , por lo que obtenemos algo como file:///tmp/pip-req-build-9u3z545j/foo/bar .

Ejecutando eso a través de nuestra función de prueba, satisfacemos el condicional de la segunda función:

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

Todo va bien ahí. El "no analizar" produce el mismo resultado, y se cumplen los requisitos netloc para el condicional de la primera función.

Sin embargo, todavía nos encontramos con un error de Invalid URL , aunque se satisface la lógica de la segunda función.

Dado que pip (o distutils o setuptools o lo que sea) se traga la salida, seguí adelante e hice lo siguiente en mi setup.py

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

Lo que verifica que todos los archivos estén allí, como se esperaba, por lo que no puede faltar un archivo o algo así. La línea de arriba que tiene "Invalid URL given" es el único lugar en el código base donde aparece la cadena.

En este punto, no estoy seguro de cuál es el problema.

vendored dependency needs discussion bug

Comentario más útil

Bien, veo el problema. setuptools , pkg-resources y pip usan versiones ligeramente diferentes de la biblioteca packaging .

En pip , es la versión que mostré arriba.

Sin embargo, en todo lo demás, es el siguiente (no estoy seguro de cuál es el "más nuevo", pero la siguiente lógica es muy limitante y no cumple completamente con RFC 3986, ya que se debe permitir file:/// , lo que implica un netloc vacío):

        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")

🙄

Eso significa que como mi ruta de archivo tiene file:///foo/bar y no file://localhost/foo/bar entonces falla.

Aquí está la solución completa :

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

Esta es una experiencia de usuario bastante mala mezclada con errores ambiguos y que hacen perder tiempo.

¿Cómo podemos mejorar esta situación?

Todos 7 comentarios

Bien, veo el problema. setuptools , pkg-resources y pip usan versiones ligeramente diferentes de la biblioteca packaging .

En pip , es la versión que mostré arriba.

Sin embargo, en todo lo demás, es el siguiente (no estoy seguro de cuál es el "más nuevo", pero la siguiente lógica es muy limitante y no cumple completamente con RFC 3986, ya que se debe permitir file:/// , lo que implica un netloc vacío):

        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")

🙄

Eso significa que como mi ruta de archivo tiene file:///foo/bar y no file://localhost/foo/bar entonces falla.

Aquí está la solución completa :

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

Esta es una experiencia de usuario bastante mala mezclada con errores ambiguos y que hacen perder tiempo.

¿Cómo podemos mejorar esta situación?

@ Qix- ¡Me alegro de que hayas encontrado esto! Me golpeaba la cabeza contra la pared intentando todos los mismos formatos. Esta es mi opción alternativa a https://github.com/pypa/pip/issues/6162 y la desaprobación de dependency_links.

Estamos intentando configurar un repositorio privado y no tenemos nuestro propio servidor interno. Nuestra solución es publicar paquetes en s3 y luego consumirlos los descargamos, los colocamos en una carpeta local y luego los agregamos a install_requires .

Estoy seguro de que hay muchos otros casos de uso que se beneficiarían de una forma intuitiva de instalar paquetes locales.

@ryanaklein En realidad, sugeriría ignorar toda la negatividad de no investigación hacia los submódulos de git y probarlos (asumiendo que está usando Git). Si dejas de pensar en ellos como ramas y comienzas a pensar en ellos como etiquetas (o lanzamientos), comienzan a funcionar realmente bien. Se usan con mucha frecuencia en el mundo C / C ++, y vendimos paquetes de Python usándolos con bastante éxito (aparte del error anterior, ¡por supuesto!).

Podría reducir los costos de red / $$ de S3 :)

Comportamiento esperado
Debería poder hacer name @ ./some/path (o, honestamente, simplemente ./some/path ) para especificar un paquete vendido localmente a mi base de código.

Para la referencia de URL directa ( name @ ./some/path ), hay dos lugares donde se está trabajando:

  1. pypa / packaging # 120 que rastrea el rechazo de referencias URL directas de PEP 508 válidas
  2. En el lado del pip, necesitamos interpretar las rutas relativas relativas a algo, comencé la discusión aquí para obtener comentarios sobre lo que tiene más sentido. Podríamos rastrear la acción final aquí o en un nuevo número específico.

Esto último no sería aceptable según PEP 508, por lo que sería difícil justificar el soporte y mucho menos para que funcione en todas las herramientas.

Esta es una experiencia de usuario bastante mala mezclada con errores ambiguos y que hacen perder tiempo.
¿Cómo podemos mejorar esta situación?

5204 debería ayudar con esta pregunta general desde el punto de vista de pip.

¡Ups! Observe cómo, cuando "descomprimimos" el resultado de la primera llamada de análisis, nuestra ruta se convierte en archivo absoluto: /// ...

Creo que esto se debe al error de CPython planteado en el problema 22852 - "urllib.parse elimina incorrectamente #fragment,? Query, // netloc vacío".

Ese error parece causar también el problema # 3783; consulte este comentario .

¿Cuál es el estado de este problema? Se necesita con urgencia una solución para resolver las dependencias locales que no están en PyPI, por ejemplo, en el contexto de los repositorios monolíticos.

Tenga en cuenta que npm ha implementado esta función de manera similar y dependencies se puede especificar en package.json usando una ruta local .

Consulte este comentario anterior para saber qué debe suceder antes de que esto tenga la oportunidad de implementarse. pip no puede hacer nada antes de eso.

¿Fue útil esta página
0 / 5 - 0 calificaciones