4  وصل التطبيق بقاعدة البيانات

السؤال: كيف أصل برمجية بايثون بقاعدة البيانات حتى أستطيع استعلامها والتخزين فيها؟

الجواب: نستعمل حزمة psycopg في بايثون للربط مع نظام قاعدة البيانات PostgreSQL.

ونعرف من مرجع التثبيت من صفحة psycopg طريقة تثبيت الحزمة:

uv add "psycopg[binary]"

الآليات الأساسية

تطبق هذه الحزمة وغيرها البروتوكول الرسمي الموحَّد في بايثون للتواصل مع قواعد البيانات: DB-API 2.0. ولذلك فإن تعلمنا لآليات حزمة psycopg قابل للقياس على غيرها مما يتصل بنظم قواعد البيانات الأخرى.

1. استيراد المكتبة

import psycopg

2. إنشاء الاتصال

يححد المعطى conninfo معلومات الاتصال وهي:

  • مسار الوصول:
    • عنوان المضيف (host)؛ ولابد من تعيينه لأننا نستعمل دوكر (Docker)؛ وأما عند التعيين فإنه يتصل بمنفذ محلي (Socket) لا بمنفذ شبكة (Network Socket)
    • والمنفذ في ذلك العنوان عند المضيف (port)
    • ثم اسم قاعدة البيانات المراد الاتصال بها (dbname)
  • معلومات الأمان:
    • اسم المستخدم (user)
    • كلمة المرور (password)

وقد تم تعيينها في درسٍ سابق عند الإنشاء الأوَّل لخدمة قاعدة البيانات عن طريق ملف compose.yaml المرتبط بملف متغيرات البيئة (.env - تذكر: النقطة أولاً)؛ فلا بد من مطابقتهما عند الاتصال:

conn = psycopg.connect(conninfo='user=user123 password=secret123 host=localhost port=5432 dbname=testdb')

ويجب أن تعلم أنه ثمة موضعان تعتبرها طرية الاتصال (psycopg.connect) قد يسبب عدم العلم بتأثيرهما حيرة عند محاولة إصلاح الأخطاء عند محاولة الاتصال:

الموضع الأول: متغيرات البيئة، وبعضها تم تعيينه في .env وقد يكون بعضها افتراضيًّا؛ أي: له قيمة ابتدائية و,إن لم يتم تعيينه. ومرجعها في صفحة متغيرات البيئة في بوستقرس.

الموضع الثاني: نص الاتصال (Connection String) وهو المعطى conninfo كما تقدَّم وله أسلوبان في التعيين (يراجع في ذلك صفحة الاتصال | قسم نص الاتصال):

أحدهما: بسرد المقابلات على هذا النحو (الصفة=القيمة) كما تقدَّم. وراجع قسم معطيات نص الاتصال التي تكون على هذا النحو؛ لمعرفة كل صفة ومعناها مثل (host أو dbname أو sslmode وغيرها).

الآخر: بالمعرف الموحد للموارد (URI) وهو مشهور جدًا. ويكون على هذا النحو:

scheme://user:password@host:port/database

ومثاله في نظام postgresql هذا النص المطابِق للذي كتبناه بالطريقة الأولى:

conn = psycopg.connect('postgresql://user123:secret123@localhost:5432/testdb')

وبهذا يكون الاتصال قد تم بين بايثون ونظام قاعدة بيانات بوستقرس (postgres) المشغَّل في جهازنا على منصة دوكر.

3. إرسال الأوامر وقراءة النتائج (بإجراء المعاملات)

يتم إنشاء المؤشر (Cursor) من الاتصال (Connection) ليتم التفاعل مع قاعدة البيانات؛ أي: إرسال الأوامر وقراءة النتائج.

الكتابة

وفيما يلي نستعرض إسرال أوامر التعريف (DDL) والمعالجة (DML) لإنشاء جدولٍ وملئه ببعض البيانات، وذلك بأحد أمرين:

with psycopg.connect('postgresql://user123:secret123@localhost:5432/testdb') as conn
    with conn.cursor() as cur:
        cur.execute("""
            CREATE TABLE test (
                id serial PRIMARY KEY,
                num integer,
                data text)
            """)

        cur.execute(
            "INSERT INTO test (num, data) VALUES (%s, %s)",
            (100, "abc'def"))

        cur.executemany(
            "INSERT INTO test (num) values (%s)",
            [(33,), (66,), (99,)])

ونلاحظ استعمال أسلوب إدارة السياق (Context Manager) آليًّا بكلمة (with) وهذا النمط قد رأيناه مسبقًا في المقدمة البايثونية للبرمجة باللغة العربية.

فأما سياق الاتصال (conn) فهدفه وضع هذه الأوامر في معاملة واحدة (Transaction). والحقيقة أنه اختصار لكل هذه القطعة متعددة المسارات التي تتحكم في المعاملة؛ وما نجعله داخل السياق يتم وضعه داخل قطعة try هنا:

conn = psycopg.connect()
try:
    ... # use the connection
except BaseException:
    conn.rollback()
else:
    conn.commit()
finally:
    conn.close()

وهذا يتم بلغة التحكم بالمعاملات (TCL) كما بينا سابقًا، وهناك حالتان:

  • الحالة الأولى: أن يتم كل شيء على ما يرام؛ أي تنفذ قطعة try ثم ينتقل سير التنفيذ لقطعة else التي تقوم باعتماد هذه العمليات commit، وأخيرًا يتم الانتقال لقطعة finally لإغلاق الاتصال المفتوح؛ حِفظًا للموارِد.
  • الحالة الثانية: أن يحصل خطأ أثناء تنفيذ قطعة try؛ فيقفز سير التنفيذ لقطعة except ليتم تنفيذ rollback الذي يعكس العمليات التي تمت قبل حصول الخطأ ليعيد حالة قاعدة البيانات لأصلها قبل بداية المعاملة وكأنها لم تكن. وأخيرًا يتم الانتقال لقطعة finally لإغلاق الاتصال المفتوح؛ حِفظًا للموارِد.

وبدل أن نكتب كل هذا، استعملنا طريقة with لنختصر.

وأما السياق الثاني (سياق المؤشر cur) فتُجمَعُ كل الأوامر في execute المتتابعة ولا يتم إرسالها إلا عند نهاية السياق؛ أو بالتصريح باعتمادها من كائن الاتصال هكذا (conn.commit).

القراءة

ويُمكن جلب البيانات حالاً بإحدى ثلاثة طرق:

with psycopg.connect('postgresql://user123:secret123@localhost:5432/testdb') as conn
    with conn.cursor() as cur:
         # Query the database and obtain data as Python objects.
        cur.execute("SELECT * FROM test")
        print(cur.fetchone())
        # will print (1, 100, "abc'def")

        cur.execute("SELECT id, num FROM test order by num")
        for record in cur:
            print(record)

فأما كر البيانات فإنه ليس من المرغوب عند كثرتها كثرة كاثرة أن يتم سحبها دفعة واحدة! ولذلك فإن المؤشر يمكن أن يُضبط لتحسين الأداء. راجع صفحة المؤشر (cursor) في توثيق psycopg.

خلاصة

إنّ صلة بايثون ببوستقرس (PostgreSQL) تمرّ عبر حزمة psycopg، وهي تطبيق لبروتوكول DB-API 2.0، فتستوردها، وتُنشئ نصّ الاتصال بما يحويه من المضيف والمنفذ واسم القاعدة، ثم تفتح اتصالاً ومؤشّراً. وما يجري داخل سياق الاتصال يُعدّ معاملة واحدة تُعتمَد أو تُرَدّ، فيُغنيك ذلك عن كتابة طقوس ‎try/except/commit/rollback‎. وأوامر execute و executemany تباشر إنشاء الجداول وملء البيانات، ثم تُقرأ النتائج بسحب صفّ أو جماعة أو بكرّ المؤشّر.

تفاصيل لا تشملها مكتبة psycopg:

  1. كتابة SQL كنص داخل بايثون يعرضنا للخطأ الإملائي من نسيان حرف أو فاصلة أو علامة تنصيص أو نقطة، أو ذِكر علاقة أو صفة غير موجودة أصلاً!
  2. تمرير المعطيات في نفس نص الـSQL يعرضنا لاختراق الحقن (SQL Injection).
  3. أحتاج أن غير مكتبة psycopg عند تغير نظام قواعد البيانات؛ إذْ هي مختصة بنظام PostgreSQL.
  4. وأحتاج أن أكتب بلهجات مختلفة (SQL Dialects) بما يتوافق مع اللغة المخصصة لكل نظام من نظم قواعد البيانات.
  5. الأرقام تقول أن عملية إنشاء الاتصال ثقيلة على الجهاز؛ من سيتكفل بحل هذا الأمر: بإنشاء الاتصالات مرة واحدة في البداية وحفظها في مَجمع الاتصالات (Connection Pool)؟

ولذلك سنلجأ لاستعمال حزمة SQLAlchemy وهي أشهر إطار للتعامل مع قواعد البيانات في بايثون. وهي قسمان:

  1. النواة (Core)
  2. موفِّق العلاقات والكائنات (ORM)

وسنعرِّف بهذه الحزمة في الدرس القادم.