→ العودة إلى المدونة
Tutorials & How-Tos

قائمة تحقق بناء uv ونشره: متى تكون واجهة uv الخلفية كافية، ومتى لا تكون كذلك

يمكن أن يبسط uv بناء حزم Python ونشرها، لكنه لا يلغي الحاجة إلى إثبات أن ملف wheel أو توزيع المصدر يعملان خارج مساحة عملك المحلية. تساعد هذه القائمة المشرفين على تحديد متى تكون واجهة بناء uv الخلفية كافية، ومتى تكون واجهة خلفية أخرى مطلوبة، وكيفية التحقق من المخرجات قبل النشر.

بقلم Hamza Diaz
3 أكتوبر 202610 دقيقة قراءة87 مشاهدة

يمكن أن يجعل uv build عمل إصدار حزم Python أسهل، لكنه لا يجعل ملف wheel أو توزيع المصدر الناتج صحيحا افتراضيا. قد ينجح المشروع في حل ملف القفل، ويمرر الاختبارات من نسخة العمل، ويبدأ داخل Docker، ثم يفشل مع شخص يثبت المخرج المنشور.

ينتج ذلك الفشل عادة من فجوة واضحة: ملف محلي، أو اعتمادية مسار، أو مجلد بيانات حزمة، أو استيراد console-script كان موجودا في مساحة العمل لا في التوزيع. هذه القائمة للمشرفين الذين يريدون استخدام uv دون الخلط بين مستودع يعمل وحزمة قابلة للإصدار. الأوامر أدناه فحوصات مقترحة لمشروعك. لم يتم تنفيذها في سير العمل هذا، ولم يتم اختلاق أي مخرجات أوامر.

القاعدة: اختبر المخرج، لا نسخة العمل

يوفر uv للمشرفين سطح أدوات واحدا لإدارة المشروع، وبناء التوزيعات، والنشر. يوثق دليل الحزم الرسمي uv build وuv publish، ويوثق دليل الواجهة الخلفية uv_build كواجهة بناء خلفية. هذه الأدوات مفيدة. لكنها تترك لك سؤالا صعبا واحدا: هل يستطيع مستهلك خارجي تثبيت واستخدام الشيء الذي تخطط لنشره؟

يتلقى المستهلك بيانات وصفية وملفات من ملف wheel، أو sdist، أو فهرس حزم. ولا يتلقى نسخة العمل القابلة للتحرير، أو إعدادات مصدر مساحة العمل، أو اعتمادية مسار خاصة، أو ذاكرة طبقات Docker المؤقتة، أو مجلد بيانات محلي، ما لم تكن تلك القطع جزءا من المخرج أو قابلة للوصول من بيانات وصفية معلنة. لذلك يستحق المخرج مسار اختبار خاصا به.

هذا هو نفس نمط التفكير القائم على الدليل وراء قابلية مراقبة بناء محرك TensorRT. رسالة التقدم ليست دليلا. وفي التغليف، نجاح الاختبارات المحلية لا يساوي تثبيت المستهلك من ملف wheel.

flowchart TD A[Project works in checkout] --> B[Choose backend deliberately] B --> C[Build wheel and sdist] C --> D[Inspect archives by exact name] D --> E[Rebuild from sdist where relevant] E --> F[Install wheel outside checkout] F --> G[Import module and load package data] G --> H{Ready to publish?} H -->|No| B H -->|Yes| I[Publish, then verify from target index]

متى يكون uv_build كافيا، ومتى لا يكون كذلك

تعد واجهة بناء uv الخلفية مرشحا جيدا عندما تكون الحزمة Python خالصة وتقليدية: وحدات عادية، وتخطيط src واضح، وبيانات وصفية مباشرة، وبيانات حزمة بسيطة، ولا توجد خطافات إصدار خاصة بالواجهة الخلفية. مكتبة صغيرة أو CLI بواجهة عامة ضيقة أفضل كأول ترحيل من حزمة تجمع أيضا شيفرة أصلية أو تركب مخرجات مولدة أثناء الإصدار.

الحد الحالي مهم: يدعم uv_build حاليا Python الخالص فقط. إذا كانت الحزمة نفسها تبني وحدات امتداد، فواجهة خلفية أخرى مطلوبة. هذا هو الحد الموثق. يمكن أن يظل uv مفيدا كواجهة أمامية حول واجهة خلفية أخرى، لكن استبدال الواجهة الخلفية يجب أن ينتظر حتى تثبت مكافأة المخرجات. تحتاج مشاريع Python القريبة من GPU والشيفرة الأصلية، مثل أحمال العمل التي نوقشت في اختبار قبول مسار NVIDIA Warp، إلى إبقاء ذلك الفصل واضحا. تغليف غلاف Python ليس نفس مهمة تجميع كل اعتمادية يستطيع استدعاءها.

شكل المشروعقرار الواجهة الخلفيةما يجب إثباته قبل الإصدار
مكتبة Python خالصة بتخطيط srcuv_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. الإشارة المتوقعة هي أن المخرج يحتوي على الملفات نفسها التي تحتاجها واجهتك العامة بعد التثبيت. هذه ليست مخرجات مسجلة من سير العمل هذا.

الفحصالأمر أو هدف المراجعةالإشارة المتوقعة
محتويات wheelpython -m zipfile --list ...whlتظهر الوحدة وملف البيانات في wheel
محتويات sdistpython -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

بقلم

Hamza Diaz

حمزة دياز هو مؤسس Optijara، حيث يبني وكلاء ذكاء اصطناعي عمليين، وأنظمة أتمتة، وسير عمل Copilot للشركات الخدمية. يكتب عن تشغيل الذكاء الاصطناعي، واستراتيجية الوكلاء، والتطبيق الواقعي للفرق التي تريد أنظمة مفيدة بدلًا من الضجيج.