قائمة تحقق بناء uv ونشره: متى تكون واجهة uv الخلفية كافية، ومتى لا تكون كذلك
يمكن أن يبسط uv بناء حزم Python ونشرها، لكنه لا يلغي الحاجة إلى إثبات أن ملف wheel أو توزيع المصدر يعملان خارج مساحة عملك المحلية. تساعد هذه القائمة المشرفين على تحديد متى تكون واجهة بناء uv الخلفية كافية، ومتى تكون واجهة خلفية أخرى مطلوبة، وكيفية التحقق من المخرجات قبل النشر.
يمكن أن يجعل uv build عمل إصدار حزم Python أسهل، لكنه لا يجعل ملف wheel أو توزيع المصدر الناتج صحيحا افتراضيا. قد ينجح المشروع في حل ملف القفل، ويمرر الاختبارات من نسخة العمل، ويبدأ داخل Docker، ثم يفشل مع شخص يثبت المخرج المنشور.
ينتج ذلك الفشل عادة من فجوة واضحة: ملف محلي، أو اعتمادية مسار، أو مجلد بيانات حزمة، أو استيراد console-script كان موجودا في مساحة العمل لا في التوزيع. هذه القائمة للمشرفين الذين يريدون استخدام uv دون الخلط بين مستودع يعمل وحزمة قابلة للإصدار. الأوامر أدناه فحوصات مقترحة لمشروعك. لم يتم تنفيذها في سير العمل هذا، ولم يتم اختلاق أي مخرجات أوامر.
القاعدة: اختبر المخرج، لا نسخة العمل
يوفر uv للمشرفين سطح أدوات واحدا لإدارة المشروع، وبناء التوزيعات، والنشر. يوثق دليل الحزم الرسمي uv build وuv publish، ويوثق دليل الواجهة الخلفية uv_build كواجهة بناء خلفية. هذه الأدوات مفيدة. لكنها تترك لك سؤالا صعبا واحدا: هل يستطيع مستهلك خارجي تثبيت واستخدام الشيء الذي تخطط لنشره؟
يتلقى المستهلك بيانات وصفية وملفات من ملف wheel، أو sdist، أو فهرس حزم. ولا يتلقى نسخة العمل القابلة للتحرير، أو إعدادات مصدر مساحة العمل، أو اعتمادية مسار خاصة، أو ذاكرة طبقات Docker المؤقتة، أو مجلد بيانات محلي، ما لم تكن تلك القطع جزءا من المخرج أو قابلة للوصول من بيانات وصفية معلنة. لذلك يستحق المخرج مسار اختبار خاصا به.
هذا هو نفس نمط التفكير القائم على الدليل وراء قابلية مراقبة بناء محرك TensorRT. رسالة التقدم ليست دليلا. وفي التغليف، نجاح الاختبارات المحلية لا يساوي تثبيت المستهلك من ملف wheel.
متى يكون uv_build كافيا، ومتى لا يكون كذلك
تعد واجهة بناء uv الخلفية مرشحا جيدا عندما تكون الحزمة Python خالصة وتقليدية: وحدات عادية، وتخطيط src واضح، وبيانات وصفية مباشرة، وبيانات حزمة بسيطة، ولا توجد خطافات إصدار خاصة بالواجهة الخلفية. مكتبة صغيرة أو CLI بواجهة عامة ضيقة أفضل كأول ترحيل من حزمة تجمع أيضا شيفرة أصلية أو تركب مخرجات مولدة أثناء الإصدار.
الحد الحالي مهم: يدعم uv_build حاليا Python الخالص فقط. إذا كانت الحزمة نفسها تبني وحدات امتداد، فواجهة خلفية أخرى مطلوبة. هذا هو الحد الموثق. يمكن أن يظل uv مفيدا كواجهة أمامية حول واجهة خلفية أخرى، لكن استبدال الواجهة الخلفية يجب أن ينتظر حتى تثبت مكافأة المخرجات. تحتاج مشاريع Python القريبة من GPU والشيفرة الأصلية، مثل أحمال العمل التي نوقشت في اختبار قبول مسار NVIDIA Warp، إلى إبقاء ذلك الفصل واضحا. تغليف غلاف Python ليس نفس مهمة تجميع كل اعتمادية يستطيع استدعاءها.
| شكل المشروع | قرار الواجهة الخلفية | ما يجب إثباته قبل الإصدار |
|---|---|---|
مكتبة Python خالصة بتخطيط src | uv_build مرشح معقول | يتم استيراد ملف wheel، ويعاد بناء sdist، وبيانات الحزمة موجودة |
| حزمة CLI بسيطة | قد يلائمها uv_build | يستدعي console script المثبت الوحدة المغلفة |
| حزمة ذات ملفات مولدة | قرر بعد وضوح مسار التوليد | المخرجات المولدة موجودة في كلا المخرجين أو يعاد بناؤها من sdist |
| حزمة ذات وحدات امتداد | استخدم واجهة خلفية أخرى | تتم معالجة خطوات البناء الأصلية، ووسوم المنصات، وسلاسل الأدوات |
| مشروع setuptools أو Hatchling مخصص وناضج | أبق الواجهة الخلفية الحالية أولا | تعمل واجهة uv الأمامية دون فقدان سلوك الإصدار الحالي |
مثال عملي: invoice-normalizer
استخدم حزمة افتراضية صغيرة حتى يكون للفحوصات شيء ملموس لفحصه. تطبع الحزمة قواميس الفواتير في شكل موحد وتشحن خريطة عملات كبيانات حزمة. لديها أيضا console script، ما يمنحك موضعا إضافيا يمكن أن تظهر فيه أخطاء التغليف.
pyproject.toml:
[build-system]
requires = ["uv_build>=0.12.22,<0.13"]
build-backend = "uv_build"
[project]
name = "invoice-normalizer"
version = "0.1.0"
description = "Normalize simple invoice payloads."
readme = "README.md"
requires-python = ">=3.11"
dependencies = []
[project.scripts]
invoice-normalizer = "invoice_normalizer:main"README.md ليس اختياريا في هذا المثال، لأن pyproject.toml يشير إليه. أنشئه قبل البناء:
# invoice-normalizer
Normalize simple invoice payloads for packaging checks.src/invoice_normalizer/__init__.py:
from importlib.resources import files
import json
def normalize_invoice(payload: dict) -> dict:
currency = payload.get("currency", "USD").upper()
data = json.loads(files(__package__).joinpath("data/currencies.json").read_text())
if currency not in data["supported"]:
raise ValueError(f"unsupported currency: {currency}")
return {"invoice_id": str(payload["invoice_id"]), "currency": currency}
def main() -> None:
print(normalize_invoice({"invoice_id": 123, "currency": "usd"}))src/invoice_normalizer/data/currencies.json:
{"supported": ["USD", "EUR", "GBP"]}هذا ليس نظام فواتير إنتاجيا. إنه صغير عمدا. لديه استيراد عام، وconsole script، وبيانات حزمة، وهي بالضبط الأجزاء التي تعمل غالبا من نسخة العمل بينما تفشل من ملف wheel المبني.
ابن وافحص بالاسم الدقيق للأرشيف
ابن أولا كلا التوزيعين:
uv buildثم افحص الملفات التي تتوقعها بالاسم الدقيق. تجنب أمثلة wildcards الغامضة في التوثيق وCI، لأن المخرجات القديمة يمكن أن تبقى في dist وتجعل الفحص يبدو أفضل مما هو عليه.
python -m zipfile --list dist/invoice_normalizer-0.1.0-py3-none-any.whl
python -m tarfile --list dist/invoice_normalizer-0.1.0.tar.gzابحث عن invoice_normalizer/__init__.py، وinvoice_normalizer/data/currencies.json، وبيانات المشروع الوصفية، ومدخلات README إذا استخدمت، وملفات الترخيص إذا أعلنت، وبيانات وصفية لنقطة دخول console-script. الإشارة المتوقعة هي أن المخرج يحتوي على الملفات نفسها التي تحتاجها واجهتك العامة بعد التثبيت. هذه ليست مخرجات مسجلة من سير العمل هذا.
| الفحص | الأمر أو هدف المراجعة | الإشارة المتوقعة |
|---|---|---|
| محتويات wheel | python -m zipfile --list ...whl | تظهر الوحدة وملف البيانات في wheel |
| محتويات sdist | python -m tarfile --list ...tar.gz | تظهر المصدر، ومدخلات البيانات الوصفية، وملف البيانات في sdist |
| تثبيت wheel نظيف | تثبيت ملف wheel الدقيق خارج نسخة العمل | يأتي مسار الاستيراد من الحزمة المثبتة، لا من المستودع |
| تحميل بيانات الحزمة | استدعاء normalize_invoice بعد التثبيت | بيانات JSON متاحة عبر importlib.resources |
| اختبار دخان CLI | تشغيل invoice-normalizer المثبت | تحل نقطة الدخول إلى الشيفرة المغلفة |
أعد البناء من sdist
إذا نشرت sdist، فأثبت أنه يستطيع إعادة بناء ملف wheel من الملفات التي يحتويها. يلتقط هذا صنفا مختلفا من الأخطاء عن فحص ملف wheel الأول. قد لا يظهر README مفقود، أو ملف مولد، أو مجلد بيانات مفقود حتى يبني مستهلك من المصدر.
REPO_DIR="$PWD"
SDIST_PATH="$REPO_DIR/dist/invoice_normalizer-0.1.0.tar.gz"
SDIST_CHECK_DIR="$(mktemp -d)"
cd "$SDIST_CHECK_DIR"
python -m tarfile --extract "$SDIST_PATH"
cd invoice_normalizer-0.1.0
uv build --wheel
python -m zipfile --list dist/invoice_normalizer-0.1.0-py3-none-any.whlالإشارة المتوقعة هي ملف wheel معاد بناؤه من نسخة عمل sdist، يتبعه محتوى أرشيف ما زال يتضمن الوحدة وبيانات الحزمة. تعامل مع هذا كتسلسل تحقق مقترح، لا كتقرير بأن الأوامر شغلت هنا.
ثبت ملف wheel خارج نسخة العمل
لفحوصات التثبيت النظيفة، أنشئ البيئة خارج المستودع، وثبت ملف wheel الدقيق عبر مسار مطلق، ثم شغل فحوصات الاستيراد وبيانات الحزمة هناك. لا تحتسب uv run من نسخة العمل المصدرية كدليل تغليف.
REPO_DIR="$PWD"
WHEEL_PATH="$REPO_DIR/dist/invoice_normalizer-0.1.0-py3-none-any.whl"
WHEEL_CHECK_DIR="$(mktemp -d)"
cd "$WHEEL_CHECK_DIR"
uv venv .venv
. .venv/bin/activate
uv pip install "$WHEEL_PATH"
python -c 'from invoice_normalizer import normalize_invoice; assert normalize_invoice({"invoice_id": 7, "currency": "eur"}) == {"invoice_id": "7", "currency": "EUR"}'
python -c 'from importlib.resources import files; assert files("invoice_normalizer").joinpath("data/currencies.json").is_file()'
invoice-normalizerيتحقق تأكيد الاستيراد من الدالة العامة. ويتحقق تأكيد الموارد من أن ملف JSON مغلف، لا أنه موجود فقط في شجرة المصدر. ويتحقق أمر CLI من أن بيانات نقطة الدخول الوصفية تحل بعد التثبيت. مرة أخرى، هذه إشارات متوقعة على المشرف تسجيلها في CI أو ملاحظات الإصدار؛ لا تزعم هذه المقالة تنفيذها محليا.
ما الذي يثبته uv build --no-sources
يستحق uv build --no-sources الاستخدام، لكن يجب أن يبقى حده دقيقا. إنه يعطل tool.uv.sources لحل اعتماديات البناء من build-system.requires. يمكن أن يكشف ذلك افتراضات إعداد المصدر حول متطلبات البناء.
لا يحل اعتماديات التشغيل بنفسه. ولا يدقق استيرادات وقت التشغيل. ولا يكشف كل حزمة غير معلنة تستوردها شيفرتك بعد التثبيت. إذا كانت وحدة تستورد requests وقت التشغيل لكنها تنسى إعلانها، فإن --no-sources ليس الفحص الذي يثبت الخطأ. التثبيت النظيف متبوعا باختبارات دخان للاستيراد وسير العمل هو المكان الأفضل لالتقاط ذلك.
استخدم --no-sources كفحص واحد في تسلسل إصدار، لا كبوابة الإصدار. إذا فشل، فقرر ما يفترض أن تكونه الحزمة. تحتاج الحزم العامة إلى متطلبات محدثة الإصدارات وقابلة للوصول. وتحتاج الحزم الخاصة إلى توثيق الوصول إلى الفهرس. ولا ينبغي نشر حزم مساحة العمل الداخلية فقط كما لو كان المستهلكون الخارجيون يستطيعون تثبيتها. تعد نقاشات GitHub التاريخية حول فصل واجهة uv الخلفية سياقا عمليا مفيدا، لكن لا ينبغي التعامل مع issue 3957 كدليل على خطأ حالي في إعداد المصدر.
{
"checklist": "Outside-Consumer Build Check",
"minimumSignals": ["wheel built", "sdist built", "archives inspected by exact name", "wheel installed outside checkout", "import and package data verified"],
"noSourcesBoundary": "Checks build dependency resolution without tool.uv.sources; it is not a runtime dependency audit."
}فحوصات Docker والنشر
يساعد Docker عندما تريد إصدار uv مثبتا، ومزامنة اعتماديات قابلة للتكرار، وبيئة CI نظيفة. ويمكنه أيضا إخفاء أخطاء التغليف إذا نسخت الصورة شجرة المصدر مباشرة أو أعادت استخدام طبقات مخزنة مؤقتا. أبق تحقق صورة التطبيق منفصلا عن تحقق التوزيع. بدء حاوية لا يثبت أن ملف wheel يتضمن currencies.json.
قبل uv publish، أكد اسم الحزمة، والإصدار، ومتطلب Python، والاعتماديات، ومدخلات README، وبيانات الترخيص الوصفية، وإعداد فهرس الهدف. بعد النشر، تحقق كمستهلك من فهرس الهدف بالإصدار الدقيق. نجاح الرفع يثبت فقط أن السجل قبل الملفات. ولا يثبت أن المستخدمين يستطيعون استيراد الحزمة، أو تشغيل CLI، أو تحميل البيانات المغلفة.
PUBLISH_CHECK_DIR="$(mktemp -d)"
TARGET_INDEX_URL="https://test.pypi.org/simple"
cd "$PUBLISH_CHECK_DIR"
uv venv .venv
. .venv/bin/activate
uv pip install --index-url "$TARGET_INDEX_URL" "invoice-normalizer==0.1.0"
python -c 'from invoice_normalizer import normalize_invoice; assert normalize_invoice({"invoice_id": 42}) == {"invoice_id": "42", "currency": "USD"}'
python -c 'from importlib.resources import files; assert files("invoice_normalizer").joinpath("data/currencies.json").is_file()'
invoice-normalizerلإصدار حقيقي، وجه TARGET_INDEX_URL إلى الفهرس الذي نشرت إليه فعلا، وأضف أي إعداد مصادقة أو فهرس إضافي مطلوب صراحة. الإشارة المتوقعة هي تثبيت نظيف بالإصدار الدقيق من ذلك الفهرس، يتبعه نفس فحوصات الاستيراد والبيانات وCLI المستخدمة قبل النشر.
أخطاء شائعة
الخطأ الأول هو معاملة نجاح القفل كنجاح نشر. ملف القفل دليل ضمن سياق المشروع، لا دليل توزيع. خطأ شائع آخر هو تبديل الواجهة الخلفية، وتخطيط الاعتماديات، وCI، واعتمادات النشر في تغيير واحد. يجعل ذلك الإخفاقات أصعب قراءة. غير طبقة واحدة كل مرة.
خطأ ثالث هو تجاوز sdist لأن ملف wheel ثبت محليا. ما زال بعض المستهلكين يبنون من المصدر، ويمكن أن يصبح sdist معطوبا أول فشل يرونه. والخطأ الرابع هو افتراض أن Docker أجاب عن سؤال المخرج. لم يفعل، إلا إذا ثبتت الحاوية نفس ملف wheel أو sdist الذي سيتلقاه المستهلك.
مسار التبني الأفضل شبه ممل، وهذا مدح في هندسة الإصدار: استخدم uv كواجهة أمامية أولا، وأبق الواجهة الخلفية الحالية إذا كانت تشفر بالفعل سلوك الإصدار، واعتمد uv_build فقط عندما يلائم شكل المشروع. عامل ملف wheel وsdist كمنتج. تحقق منهما من خارج نسخة العمل. اختر الواجهة الخلفية بعد أن يدعم دليل المخرج ذلك.
النقاط الرئيسية
- 1يبسط uv سير عمل بناء حزم Python ونشرها، لكن المخرجات ما زالت تحتاج إلى تحقق من منظور مستهلك خارجي.
- 2يدعم uv_build حاليا مشاريع Python الخالصة فقط؛ تتطلب وحدات الامتداد واجهة خلفية أخرى.
- 3يتحقق uv build --no-sources من حل اعتماديات البناء دون tool.uv.sources، لا من اكتمال اعتماديات وقت التشغيل.
- 4التثبيت النظيف من المخرجات المبنية خارج نسخة العمل هو أقوى إشارة محلية قبل النشر.
- 5قابلية Docker للتكرار لا تثبت صحة wheel أو sdist.
الخلاصة
يكون uv في أفضل حالاته عندما يجعل التغليف أسهل فهما، لا عندما يخفي افتراضات الإصدار خلف أمر أسرع. عامل ملف wheel وsdist كمنتج، وتحقق منهما من خارج نسخة العمل، واختر uv_build فقط عندما تكون الحزمة Python خالصة وتثبت المخرجات المبنية قصة الإصدار.
الأسئلة الشائعة
ماذا ينشئ uv build؟
ينشئ uv build مخرجات توزيع Python مثل ملفات wheel وتوزيعات المصدر، بحسب المشروع والأمر المستخدم.
متى ينبغي أن أستخدم واجهة بناء uv الخلفية؟
استخدم uv_build للحزم المتوافقة المكتوبة بPython الخالص وذات التخطيطات التقليدية. استخدم واجهة خلفية أخرى عندما تبني الحزمة وحدات امتداد أو تحتاج إلى سلوك واجهة خلفية مخصص.
ما فائدة uv build --no-sources؟
يعطل tool.uv.sources لحل اعتماديات البناء من build-system.requires، ما يساعد على كشف افتراضات مصادر البناء. لكنه ليس تدقيقا لاعتماديات وقت التشغيل.
هل تثبت صورة Docker عاملة أن حزمة Python الخاصة بي صحيحة؟
لا. يمكن أن ينسخ Docker ملفات المصدر أو يستخدم طبقات مخزنة مؤقتا تخفي محتويات wheel أو sdist المفقودة.
ما الذي ينبغي التحقق منه بعد النشر؟
ثبت الإصدار المنشور من الفهرس الهدف في بيئة نظيفة، ثم تحقق من الاستيرادات، وconsole scripts، وبيانات الحزمة، وسير عمل مستهلك بسيط.
المصادر
بقلم
Hamza Diazحمزة دياز هو مؤسس Optijara، حيث يبني وكلاء ذكاء اصطناعي عمليين، وأنظمة أتمتة، وسير عمل Copilot للشركات الخدمية. يكتب عن تشغيل الذكاء الاصطناعي، واستراتيجية الوكلاء، والتطبيق الواقعي للفرق التي تريد أنظمة مفيدة بدلًا من الضجيج.
