Python SQLAlchemy Pydantic في الممارسة: الكود والأوامر التي تهم حقاً
بايثون SQLAlchemy Pydantic: الأساسيات في مقال واحد — كود حقيقي، مخططات وخطوات ملموسة، مقتطفات من دورة تتكون من 27 درسًا.
لا توجد نظرية لا نهاية لها هنا: نفتح الطرفية ونمارس. إليك أساسيات Python SQLAlchemy Pydantic، مستخرجة مباشرة من دورة كاملة تضم 27 درسًا — مع كود حقيقي يمكنك نسخه ولصقه الآن.
- مقدمة
- SQLAlchemy Core
- ORM Declarative
- الجلسة والمعاملات
- استعلامات متقدمة
المصانع باستخدام factory-boy
factory-boy وFaker، دون تكرار الكود في كل اختبار.الأهداف التعليمية
- شرح سبب صعوبة إدارة الـ fixtures المكتوبة يدويًا
- تعريف
SQLAlchemyModelFactoryمرتبطة بجلسة - استخدام
FakerوSequenceوSubFactoryلبيانات واقعية - التعامل مع العلاقات (one-to-many) عبر
RelatedFactoryList - ربط المصانع بـ fixtures pytest من الوحدة السابقة
الفكرة: مصنع بدلًا من نموذج فارغ
في اختبار، تحتاج إلى كائن User صالح. عند كتابته يدويًا يصبح User(name="Test", email="a@b.c", age=30, is_active=True, ...). تكراره في 40 اختبارًا يعني 40 مرة نفس المهمة المملة، وإذا أضفت عمود NOT NULL تنكسر الـ 40 اختبارًا.
المصنع هو مصنع: تصف مرة واحدة كيفية إنشاء User نموذجي، ثم يطلب كل اختبار UserFactory() ويحصل على كائن كامل وصالح. إذا احتاج اختبار لحالة خاصة، يعدل الحقل المعني فقط: UserFactory(age=17). كل شيء آخر يُملأ تلقائيًا.
تعريفي
تصف النموذج النوعي، لا كل نسخة. يبقى كود الاختبار قصيرًا وقابلاً للقراءة.
واقعي
Faker يولد أسماء وبريد إلكتروني وعناوين مقبولة بدلًا من "aaa".
قابل للتكرار
يمكن تثبيت البذرة (seed) للحصول على نفس البيانات في كل تشغيل.
التثبيت وأول مصنع
نثبت factory-boy (الذي يحتوي Faker):
RelatedFactoryList مع size=100 يدرج 100 كائن واحدًا تلو الآخر. لكميات كبيرة من البذور، فضّل create_batch أو الإدراج الجماعي.ربط المصانع بـ pytest
نعيد استخدام الـ fixture db_session من الوحدة السابقة (التراجع التلقائي). نضخ هذه الجلسة في المصانع، ثم نعرض fixtures عملية.
الاستعلامات المعقدة والتقارير
الأهداف التعليمية
- كتابة تجميع متعدد الجداول (عملاء، طلبات، بنود) باستخدام
GROUP BY - ترتيب المنتجات باستخدام دالة النافذة
rank() - هيكلة استعلام قابل للقراءة باستخدام CTE
- ربط نتيجة غير متجانسة (Row) بمخطط Pydantic للتقارير
- التحقق من هذه الاستعلامات باختبارات على بيانات المصانع
الفكرة: فصل المعاملاتي عن التحليلي
حتى الآن، ينشئ ERP الطلبات (معاملاتي). التقارير تجيب على أسئلة أخرى: من أفضل عملائي؟ أي منتج يُباع أكثر هذا الشهر؟ هذه الاستعلامات لا تعدل شيئًا؛ بل تجمع وترتب. نعزلها في وحدة reporting/ لعدم خلط المسؤوليات.
الخدمات المعاملاتية
تنشئ وتعدل الكيانات (إنشاء طلب، خفض المخزون). جلسة واحدة، معاملة واحدة.
استعلامات التقارير
قراءة فقط، تجميعات ثقيلة. تعيد صفوفًا (Row) بدلًا من كيانات ORM.
الإيرادات حسب العميل
نربط Customer وOrder وOrderLine، ثم نجمع quantity * unit_price. نستخدم واجهة select() 2.0 وfunc للتجميع.
مشروع ERP - النماذج
مشروع نهائي • دفتر الشروط • نماذج ORM
المواصفات
- إدارة العملاء (B2B/B2C) مع العناوين
- كتالوج المنتجات مع مخزون متعدد المستودعات
- طلبات متعددة البنود مع حالة سير العمل
- الفوترة التلقائية مع ضريبة القيمة المضافة
- حركات المخزون المراجعة
- تقارير حسب الشهر / العميل / المنتج
هيكل المشروع
erp/ ├── alembic.ini ├── migrations/ ├── app/ │ ├── core/ │ │ ├── config.py # الإعدادات │ │ └── database.py │ ├── models/ │ │ ├── customer.py │ │ ├── product.py │ │ ├── order.py │ │ └── invoice.py │ ├── schemas/ # مخططات Pydantic للإدخال/الإخراج │ ├── services/ # منطق الأعمال │ ├── api/ # مسارات FastAPI │ └── main.py └── tests/
نموذج Customer
import enum from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy import ForeignKey from datetime import datetime class CustomerType(str, enum.Enum): B2B = "b2b" B2C = "b2c" class Customer(Base): __tablename__ = "customers" id: Mapped[int] = mapped_column(primary_key=True) type: Mapped[CustomerType] name: Mapped[str] email: Mapped[str] = mapped_column(unique=True) tax_id: Mapped[str | None] # لـ B2B created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) addresses: Mapped[list["Address"]] = relationship(back_populates="customer", cascade="all") orders: Mapped[list["Order"]] = relationship(back_populates="customer") class Address(Base): __tablename__ = "addresses" id: Mapped[int] = mapped_column(primary_key=True) customer_id: Mapped[int] = mapped_column(ForeignKey("customers.id")) street: Mapped[str] city: Mapped[str] zip_code: Mapped[str] country: Mapped[str] is_billing: Mapped[bool] = mapped_column(default=False) is_shipping: Mapped[bool] = mapped_column(default=True) customer: Mapped["Customer"] = relationship(back_populates="addresses")
نموذج Product و Stock
from decimal import Decimal class Product(Base): __tablename__ = "products" id: Mapped[int] = mapped_column(primary_key=True) sku: Mapped[str] = mapped_column(unique=True) name: Mapped[str] price: Mapped[Decimal] = mapped_column(Numeric(10, 2)) vat_rate: Mapped[Decimal] = mapped_column(Numeric(4, 2), default=Decimal("20.00")) active: Mapped[bool] = mapped_column(default=True) stocks: Mapped[list["Stock"]] = relationship(back_populates="product") class Warehouse(Base): __tablename__ = "warehouses" id: Mapped[int] = mapped_column(primary_key=True) code: Mapped[str] = mapped_column(unique=True) name: Mapped[str] class Stock(Base): __tablename__ = "stocks" id: Mapped[int] = mapped_column(primary_key=True) product_id: Mapped[int] = mapped_column(ForeignKey("products.id")) warehouse_id: Mapped[int] = mapped_column(ForeignKey("warehouses.id")) quantity: Mapped[int] = mapped_column(default=0) __table_args__ = ( UniqueConstraint("product_id", "warehouse_id"), CheckConstraint("quantity >= 0"), ) product: Mapped["Product"] = relationship(back_populates="stocks")
نموذج Order و OrderLine
class OrderStatus(str, enum.Enum): DRAFT = "draft" CONFIRMED = "confirmed" SHIPPED = "shipped" DELIVERED = "delivered" CANCELLED = "cancelled" class Order(Base): __tablename__ = "orders" id: Mapped[int] = mapped_column(primary_key=True) customer_id: Mapped[int] = mapped_column(ForeignKey("customers.id")) reference: Mapped[str] = mapped_column(unique=True) status: Mapped[OrderStatus] = mapped_column(default=OrderStatus.DRAFT) created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) customer: Mapped["Customer"] = relationship(back_populates="orders") lines: Mapped[list["OrderLine"]] = relationship(back_populates="order", cascade="all, delete-orphan") invoice: Mapped["Invoice"] = relationship(back_populates="order", uselist=False) class OrderLine(Base): __tablename__ = "order_lines" id: Mapped[int] = mapped_column(primary_key=True) order_id: Mapped[int] = mapped_column(ForeignKey("orders.id")) product_id: Mapped[int] = mapped_column(ForeignKey("products.id")) quantity: Mapped[int] = mapped_column() unit_price: Mapped[Decimal] = mapped_column(Numeric(10, 2)) order: Mapped["Order"] = relationship(back_populates="lines") product: Mapped["Product"] = relationship()
نموذج Invoice و StockMovement
class Invoice(Base): __tablename__ = "invoices" id: Mapped[int] = mapped_column(primary_key=True) order_id: Mapped[int] = mapped_column(ForeignKey("orders.id"), unique=True) number: Mapped[str] = mapped_column(unique=True) issued_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) subtotal: Mapped[Decimal] = mapped_column(Numeric(10, 2)) vat: Mapped[Decimal] = mapped_column(Numeric(10, 2)) total: Mapped[Decimal] = mapped_column(Numeric(10, 2)) paid: Mapped[bool] = mapped_column(default=False) order: Mapped["Order"] = relationship(back_populates="invoice") class StockMovement(Base): __tablename__ = "stock_movements" id: Mapped[int] = mapped_column(primary_key=True) product_id: Mapped[int] = mapped_column(ForeignKey("products.id")) warehouse_id: Mapped[int] = mapped_column(ForeignKey("warehouses.id")) quantity: Mapped[int] # سالب = خروج reason: Mapped[str] # "order_123", "restock" created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
الملخص
- نماذج منفصلة حسب المجال (customer, order, product)
- Decimal للأموال (لا تستخدم float أبدًا)
- Enum لحالات سير العمل
- cascade للتجميعات (Order -> lines)
- StockMovement = سجل تدقيق غير قابل للتعديل
يغطي هذا المقال المقتطفات الأكثر فائدة — الدورة الكاملة Python SQLAlchemy Pydantic (9 فصول، 27 درسًا، تمارين مصححة ومشروع نهائي) تأخذك حتى النهاية.
./acceder-au-cours-complet cours gratuit : Vibe Codingالأسئلة الشائعة
كم من الوقت يستغرق تعلم Python SQLAlchemy Pydantic؟
هل هناك متطلبات مسبقة؟
من أين نبدأ عمليًا؟
📬 هل تريد تلقي هذا النوع من الأدلة كل أسبوع؟ اشترك مجانًا — كود حقيقي، بدون كلام زائد.