بيئة
وصف
لست متأكدًا مما إذا كان هذا خطأ في six
أم خطأ Pip. إسمح لي إذا كان ينتمي إلى six
.
يبدو أن النقطة تسمح بالمسارات المحلية في install_requires
عبر name @ ./some/path
، لكن تحليل عنوان 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
ولكن يمكن تخفيفه في Pip بالسماح scheme in ['file', '']
وتوجيه المستخدمين لاستخدام نموذج URI ./foo/bar
.
بالنظر إلى هذين المنطقين المتناقضين ، من المستحيل استخدام المسارات المحلية في مفاتيح install_requires
في تكوينات distutils
أو setuptools
.
سلوك متوقع
يجب أن أكون قادرًا على عمل 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 يحدد أن URIs للمسار النسبي غير مسموح به مع مخطط file:
، لذلك من الناحية الفنية ، يجب أن يكون خطأ six
على file:./foo/bar
.
ومع ذلك ، هذا يعني ، من الناحية الفنية ، أنه يجب أن أكون قادرًا على القيام بما يلي في ملف setup.py:
PKG_DIR = os.path.dirname(os.path.abspath(__file__))
install_requires = [
f"name @ file://{PKG_DIR}/foo/bar"
]
ومع ذلك ، يبدو أن النقطة تقوم بإنشاء نسخة "نظيفة" من الحزمة في /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 أو أيًا كان) يبتلع الإخراج ، فقد تقدمت وقمت بما يلي في الإعداد الخاص بي.
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'
]
)
هذه تجربة مستخدم سيئة جدًا ممزوجة بأخطاء غامضة ومضيعة للوقت.
كيف يمكننا تحسين هذا الوضع؟
@ Qix- سعيد لأنك وجدت هذا! كنت أقوم بضرب رأسي على الحائط محاولًا استخدام نفس الأشكال. هذا هو خياري البديل لـ https://github.com/pypa/pip/issues/6162 وإهمال ارتباطات التبعية.
نحن نحاول إعداد ريبو خاص وليس لدينا خادم داخلي خاص بنا. حلنا هو نشر الحزم على s3 ومن ثم استهلاكها نقوم بتنزيلها ووضعها في مجلد محلي ثم إضافتها إلى install_requires
.
أنا متأكد من أن هناك العديد من حالات الاستخدام الأخرى التي قد تستفيد من طريقة بديهية لتثبيت الحزم المحلية.
ryanaklein أود في الواقع أن أقترح تجاهل كل السلبية غير البحثية تجاه الوحدات الفرعية git وتجربتها (بافتراض أنك تستخدم Git). إذا توقفت عن التفكير فيها كفروع وبدأت في التفكير فيها كعلامات (أو إصدارات) ، فإنها تبدأ في العمل بشكل جيد حقًا. يتم استخدامها بشكل متكرر في عالم C / C ++ ، وقمنا ببيع حزم Python باستخدامها بنجاح كبير (بصرف النظر عن الخطأ أعلاه ، بالطبع!).
قد ينخفض على الشبكة / $ تكاليف S3 :)
سلوك متوقع
يجب أن أكون قادرًا على عملname @ ./some/path
(أو ، بصراحة ،./some/path
) لتحديد حزمة موردة محلية لقاعدة الشفرة الخاصة بي.
بالنسبة لمرجع عنوان URL المباشر ( name @ ./some/path
) يوجد مكانان يتم فيهما العمل:
لن يكون هذا الأخير مقبولًا وفقًا لـ PEP 508 ، لذلك سيكون من الصعب تبرير دعم أقل بكثير لجعله يعمل عبر جميع الأدوات.
هذه تجربة مستخدم سيئة جدًا ممزوجة بأخطاء غامضة ومضيعة للوقت.
كيف يمكننا تحسين هذا الوضع؟
وجه الفتاة! لاحظ كيف ، عندما "نحلل" النتيجة من استدعاء التحليل الأول ، يصبح مسارنا ملفًا مطلقًا: /// ...
أعتقد أن هذا يرجع إلى خطأ CPython الذي أثير في الإصدار رقم 22852 - "يقطع urllib.parse بشكل خاطئ #fragment،؟ query، // netloc"
يبدو أن هذا الخطأ يتسبب أيضًا في المشكلة رقم 3783 - راجع هذا التعليق .
ما هو وضع هذه القضية؟ هناك حاجة ماسة إلى حل لحل التبعيات المحلية غير الموجودة في PyPI ، على سبيل المثال في سياق المستودعات المتجانسة.
لاحظ أن npm نفذت هذه الميزة بطريقة مماثلة ويمكن تحديد dependencies
في package.json
باستخدام مسار محلي .
يرجى الاطلاع على هذا التعليق أعلاه لمعرفة ما يجب أن يحدث قبل أن تتاح الفرصة لذلك. لا تستطيع النقطة أن تفعل شيئًا قبل ذلك.
التعليق الأكثر فائدة
حسنًا ، أرى المشكلة. يستخدم كل من
setuptools
وpkg-resources
وpip
إصدارات مختلفة قليلاً من مكتبةpackaging
.في
pip
، هذا هو الإصدار الذي عرضته أعلاه.ومع ذلك ، في كل شيء آخر ، يكون ما يلي (لست متأكدًا من الخيار "الأحدث" ، ولكن المنطق التالي مقيد جدًا وغير متوافق تمامًا وفقًا لـ RFC 3986 حيث يجب السماح بـ
file:///
، مما يعني فارغةnetloc
):🙄
هذا يعني أن مسار الملف الخاص بي به
file:///foo/bar
وليسfile://localhost/foo/bar
فإنه يفشل.ها هو الحل الكامل :
هذه تجربة مستخدم سيئة جدًا ممزوجة بأخطاء غامضة ومضيعة للوقت.
كيف يمكننا تحسين هذا الوضع؟