Python SQLAlchemy Pydantic في الممارسة: الكود والأوامر التي تهم حقاً

بايثون SQLAlchemy Pydantic: الأساسيات في مقال واحد — كود حقيقي، مخططات وخطوات ملموسة، مقتطفات من دورة تتكون من 27 درسًا.

Python SQLAlchemy Pydantic في الممارسة: الكود والأوامر التي تهم حقاً

لا توجد نظرية لا نهاية لها هنا: نفتح الطرفية ونمارس. إليك أساسيات Python SQLAlchemy Pydantic، مستخرجة مباشرة من دورة كاملة تضم 27 درسًا — مع كود حقيقي يمكنك نسخه ولصقه الآن.

tl;dr
  • مقدمة
  • SQLAlchemy Core
  • ORM Declarative
  • الجلسة والمعاملات
  • استعلامات متقدمة
~$ cat ./parcours.md # Python SQLAlchemy Pydantic — 9 فصول
01
مقدمة
→ SQLAlchemy 2.0, العصر الجديد→ Pydantic v2 والتنميط الصارم
02
SQLAlchemy Core
→ المحرك والاتصال→ الجداول والبيانات الوصفية+ 1 دروس أخرى
03
ORM التعريفي
→ Mapped et mapped_column→ الأنواع والقيود+ 2 دروس أخرى
04
الجلسة والمعاملات
→ نمط الجلسة→ المعاملات ونقاط الحفظ+ 1 دروس أخرى
05
الاستعلامات المتقدمة
→ الربط، CTE، الاستعلامات الفرعية→ التجميعات والتحليلات+ 1 دروس أخرى
06
Pydantic v2
→ BaseModel et Field→ المحققات والحقول المحسوبة+ 1 دروس أخرى
07
SQLAlchemy + Pydantic
→ ربط SQLAlchemy <-> Pydantic→ Pydantic Settings
08
التهجيرات والاختبارات
→ تهجيرات Alembic→ الاختبارات باستخدام pytest وfactories+ 1 دروس أخرى
🏁
المشروع النهائي (+ 1 فصول في الطريق)
→ ستعود بمشروع ملموس وقابل للعرض

المصانع باستخدام factory-boy

NOTEالهدف — استبدال بيانات الاختبار المكتوبة يدويًا بـ مصانع تعريفية. ستولد كائنات SQLAlchemy واقعية ومتسقة وقابلة للتكرار باستخدام factory-boy وFaker، دون تكرار الكود في كل اختبار.

الأهداف التعليمية

TIPفي نهاية هذه الوحدة
  • شرح سبب صعوبة إدارة الـ 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):

WARNINGانتبه إلى مشكلة N+1 في الإنشاءRelatedFactoryList مع size=100 يدرج 100 كائن واحدًا تلو الآخر. لكميات كبيرة من البذور، فضّل create_batch أو الإدراج الجماعي.

ربط المصانع بـ pytest

نعيد استخدام الـ fixture db_session من الوحدة السابقة (التراجع التلقائي). نضخ هذه الجلسة في المصانع، ثم نعرض fixtures عملية.

الاستعلامات المعقدة والتقارير

NOTEالهدف — بناء طبقة التحليلات في ERP: الإيرادات حسب العميل، ترتيب المنتجات، التطور الشهري. تجمع الاستعلامات المتقدمة من الفصل 04 مع نماذج المشروع، وتعرض النتائج عبر مخططات Pydantic مخصصة للتقارير.

الأهداف التعليمية

TIPفي نهاية هذه الوحدة
  • كتابة تجميع متعدد الجداول (عملاء، طلبات، بنود) باستخدام GROUP BY
  • ترتيب المنتجات باستخدام دالة النافذة rank()
  • هيكلة استعلام قابل للقراءة باستخدام CTE
  • ربط نتيجة غير متجانسة (Row) بمخطط Pydantic للتقارير
  • التحقق من هذه الاستعلامات باختبارات على بيانات المصانع

الفكرة: فصل المعاملاتي عن التحليلي

حتى الآن، ينشئ ERP الطلبات (معاملاتي). التقارير تجيب على أسئلة أخرى: من أفضل عملائي؟ أي منتج يُباع أكثر هذا الشهر؟ هذه الاستعلامات لا تعدل شيئًا؛ بل تجمع وترتب. نعزلها في وحدة reporting/ لعدم خلط المسؤوليات.

الخدمات المعاملاتية

تنشئ وتعدل الكيانات (إنشاء طلب، خفض المخزون). جلسة واحدة، معاملة واحدة.

استعلامات التقارير

قراءة فقط، تجميعات ثقيلة. تعيد صفوفًا (Row) بدلًا من كيانات ORM.

الإيرادات حسب العميل

نربط Customer وOrder وOrderLine، ثم نجمع quantity * unit_price. نستخدم واجهة select() 2.0 وfunc للتجميع.

مشروع ERP - النماذج

مشروع نهائي • دفتر الشروط • نماذج ORM

NOTEالهدف — تصميم ERP مصغر: عملاء، منتجات، طلبات، فواتير، مخزون.

المواصفات

NOTEالميزات:
  • إدارة العملاء (B2B/B2C) مع العناوين
  • كتالوج المنتجات مع مخزون متعدد المستودعات
  • طلبات متعددة البنود مع حالة سير العمل
  • الفوترة التلقائية مع ضريبة القيمة المضافة
  • حركات المخزون المراجعة
  • تقارير حسب الشهر / العميل / المنتج

هيكل المشروع

output
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

output
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

output
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

output
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

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

الملخص

NOTEللتذكر
  • نماذج منفصلة حسب المجال (customer, order, product)
  • Decimal للأموال (لا تستخدم float أبدًا)
  • Enum لحالات سير العمل
  • cascade للتجميعات (Order -> lines)
  • StockMovement = سجل تدقيق غير قابل للتعديل
va-plus-loin

يغطي هذا المقال المقتطفات الأكثر فائدة — الدورة الكاملة Python SQLAlchemy Pydantic (9 فصول، 27 درسًا، تمارين مصححة ومشروع نهائي) تأخذك حتى النهاية.

./acceder-au-cours-complet cours gratuit : Vibe Coding

الأسئلة الشائعة

كم من الوقت يستغرق تعلم Python SQLAlchemy Pydantic؟
مع تقدم منظم (9 فصول، 27 درسًا قصيرًا وعمليًا)، يمكن الوصول إلى مستوى تشغيلي في بضعة أسابيع بمعدل 30 إلى 60 دقيقة يوميًا. المهم هو تطبيق كل مفهوم فورًا.
هل هناك متطلبات مسبقة؟
تكفي أساسيات في الحوسبة. إذا كنت تعرف استخدام الطرفية وقراءة كود بسيط، فأنت جاهز.
من أين نبدأ عمليًا؟
أعد إنتاج الأوامر في هذا المقال، ثم تابع الدورة الكاملة Python SQLAlchemy Pydantic: تتسلسل فيها الـ 27 درسًا بالترتيب، مع تمارين ومشروع نهائي.

📬 هل تريد تلقي هذا النوع من الأدلة كل أسبوع؟ اشترك مجانًا — كود حقيقي، بدون كلام زائد.