أعجوبة

البرمجيات الحُرة والمفتوحة المصدر

أدوات المستخدم

أدوات الموقع


okasha:elixir

قواعد البيانات عبر الإكسير

مقدمة

يمكن استعمال ربط تطبيقات عكاشة مع قواعد البيانات مثل sqlite (دعمها مدمج في بايثون) أو mysql عبر حزم بايثون لهذا الغرض (مثل pymysql)

لكن في هذه الحالة يكون عليك متابعة عدد من الأمور مثل:

  • تكون محصور في قاعدة بيانات بعينها (مثلا سيكون من الصعب نقل الكود من SQLite إلى MySQL أو PostgreSQL)
  • عليك القيام يدويا بتحسين تعابير SQL
  • في sqlite لا تستطيع التحدّث مع اتصال فتحته في thread آخر ممّا يعني أنك قد تحتاج لعمل اتصال لكل طلب.

لذا هناك بديل لهذه الطريقة اسمه SQLAlchemy وحوله بُني الإكسير لتسهيله.

الإكسير عبارة عن ORM أي Object-relational mapping حيث تتحوّل الجداول والصفوف وعلاقاتها إلى كائنات.

تدعم SQLAlchemy عددا كبيرا من قواعد البيانات من بينها:

  • Sqlite
  • MySQL
  • Oracle
  • Postgresql

إنّ الإكسير يقوم بالكثير من التحسينات فهو مثلا يعمل اتصال واحد مع قاعدة البيانات إلاّ في حالة Sqlite يقوم بعمل اتصال لكل thread

الإكسير من خارج عكاشة

عمل المخطط

لنفترض أن لدينا قاعدة بيانات للكتب تتكوّن من جدول للمؤلفين وجدول للكتب وجدول للناشرين وجدول للتصنيفات من خلال keywords.

جدول الكتب يتكوّن من الحقول التالية:

  • معرف رقمي فريد تلقائي
  • اسم الكتاب وهو نص قصير
  • وصف الكتاب وهو نص حر طويل
  • عام النشر
  • معرف ISBN فريد ومفهرس
  • المؤلّفون وهي علاقة كثير لكثير many to many مع جدول المؤلفين يعني ممكن أن يكون أكثر من مؤلف لنفس الكتاب أو للمؤلف الواحد أكثر من كتاب
  • الناشر وهي علاقة كثير لواحد أي قد يكون لأكثر من كتاب ناشر واحد
  • التصنيفات وهي علاقة كثير لكثير.

جدول المؤلفون حيث يتكوّن من الحقول التالية:

  • معرف رقمي فريد تلقائي
  • اسم المؤلف وهو نص
  • عام الميلاد
  • عام الوفاة أو صفر للأحياء
  • الكتب وهي علاقة كثير لكثير (عكس علاقة المؤلفون في جدول الكتب)

جدول الناشرون

  • معرف رقمي فريد تلقائي
  • اسم الناشر وهو نص
  • الكتب وهي علاقة واحد لكثير (عكس علاقة الناشر في جدول الكتب)

جدول التصنيفات بالكلمات المفتاحية

  • معرف رقمي
  • الكلمة
  • الكتب وهي علاقة كثير لكثير

تنفيذ المخطط

لنكتب وحدة module تُعرّف هذه القاعدة كما يلي ونحفظها في ملف باسم bookstoreModel.py

from elixir import *
 
class Book(Entity):
  using_options(tablename="book")
  n = Field(Integer, primary_key=True, autoincrement=True)
  title = Field(Unicode(30), index=True)
  description = Field(UnicodeText)
  year = Field(Integer)
  isbn = Field(String(16), index=True, unique=True)
  authors = ManyToMany('Author', lazy=False)
  publisher = ManyToOne('Publisher', lazy=False)
  keywords = ManyToMany('Keywords', lazy=False)
 
class Author(Entity):
  using_options(tablename="author")
  n = Field(Integer, primary_key=True, autoincrement=True)
  name = Field(Unicode(30), index=True)
  details = Field(UnicodeText)
  year_of_birth = Field(Integer, index=True)
  year_of_death = Field(Integer, index=True, default=0)
  books = ManyToMany('Book', lazy=True)
 
class Publisher(Entity):
  using_options(tablename="publisher")
  n = Field(Integer, primary_key=True, autoincrement=True)
  name = Field(Unicode(30), index=True)
  books = OneToMany('Book', lazy=True)
 
class Keywords(Entity):
  using_options(tablename="keywords")
  n = Field(Integer, primary_key=True, autoincrement=True)
  name = Field(Unicode(30), index=True)
  books = ManyToMany('Book', lazy=True)

في نفس المجلد افتح بايثون التفاعلي واكتب فيه

from bookstoreModel import Book, Author, Publisher, Keywords
import elixir
elixir.metadata.bind='sqlite:///db.sqlite'
elixir.setup_all()
elixir.create_all()

الأمر الأخير create_all يعمل على إنشاء قاعدة البيانات (نستدعيه مرة واحدة حيث لاحقا نكتفي ب setup_all) وهي في مثالنا نتحدث مع قاعدة بيانات من نوع sqlite وهي موجودة في الملف db.sqlite لاحظ سطر bind

يمكنك طبعا استعمال mysql وتقديم اسم مستخدم وكلمة سر واسم القاعدة …إلخ مثلا

elixir.metadata.bind='mysql://user:pass@localhost/mytestdb'

لكن دعونا نتابع مستخدمين ملف sqlite وليس mysql. الآن اخرج من مفسر بايثون التفاعلي وانظر محتويات ملف db.sqlite عبر الأمر التالي:

sqlite3 db.sqlite .dump | less

ستلاحظ مخطط قاعدة البيانات schema دون مدخلات لأنها لا تزال خالية.

لنعد إلى مفسر بايثون التفاعلي ونفتح قاعدة البيانات بالأوامر

from bookstoreModel import Book, Author, Publisher, Keywords
import elixir
elixir.metadata.bind='sqlite:///db.sqlite'
elixir.setup_all()

في مراحل الاختبار يمكنك إظهار عمليات SQL حتى تراها بعينك وذلك بكتابة السطر

elixir.metadata.bind.echo = True

إضافة المدخلات

لنقم الآن بإدخال بعض الحقول في قواعد البيانات. ولنبدأ بعمل حقل في جدول الكلمات المفتاحية وذلك عبر الأمر

kw_s=Keywords(name=u"science")
kw_a=Keywords(name=u"art")
p1=Publisher(name=u"My Press")
p2=Publisher(name=u"Nashir2")
elixir.session.commit()

السطر الأول يعمل كائن من نوع Keywords ويعين خصائصه والتي هي name ويعطيها قيمة science وهذا يعني عمل صف في جدول keywords قيمة العمود name فيه هي science. يتم حفظ هذا الكائن في المتغير kw_s وهذا غير إلزامي لكنني فعلته لأني قد استخدمه لاحقا.

السطر الثاني عمل كائنا آخر وبالتالي صف آخر بقيمة أخرى. ثم علمنا ناشرين بنفس الطريقة. ولو قمت الآن بفتح قاعدة البيانات لن تجد فيها شيء ذلك أن الإكسير يجمع العمليات إلى أن تطلب منه إرسالها محسنة عبر الدالة elixir.session.commit.

لنضف الآن عدد من المؤلفين

a1=Author(name=u"Oqlah ibn Khalaf", details=u"short story pioneer in Balama", year_of_birth=1975)
a2=Author(name=u"John Random Hacker", details=u"fictional author from cyberspace", year_of_birth=1980)
elixir.session.commit()

لنضف الآن أحد الكتب

b1=Book(title=u"The twilight of Balama", description=u"a short story.", year=2010, isbn="0-1234-5678-9", publisher=p1)
b1.authors.append(a1)
b1.keywords.append(kw_a)
 
b2=Book(title=u"Facts you don't know", description=u"true stories from a fake author", year=2010, isbn="1-1234-5678-9", publisher=p2)
b2.authors.append(a2)
b2.keywords.append(kw_s)
 
elixir.session.commit()

لاحظ استخدام append لإضافة العلاقات. طبعا يمكنك استخدام الاستعلامات لتوليد الكائنات التي تمررها إلى append عوضا عن حفظ الكائنات في متغيرات

الاستعلام والتعديل

أغلق جلسة بايثون التفاعلي السابقة ولنبدأ واحدة جديدة ونكتب بدايتنا التقليدية

from bookstoreModel import Book, Author, Publisher, Keywords
import elixir
elixir.metadata.bind='sqlite:///db.sqlite'
elixir.setup_all()

لنستعلم عن شيء له معرف فريد أو فهرس فريد مثلا

b=Book.get_by(isbn="1-1234-5678-9")
print b.title

لاحظ استخدام get_by على الفئة نفسها دون كائن منها (يعرف هذا class method أو static method) مع تمرير لها معرف isbn للحصول على كائن الكتاب. وباملناسبة يمكننا التعديل على الكائن مباشرة حيث تعكس تلك التعديلات على قاعدة البيانات فور عمل commit مثلا لنفرض أني أريد زيادة علامة تعجب في نهاية العنوان

b.title+=u"!"
elixir.session.commit()

إن العلاقات تعمل بشكل تلقائي يعني لو استعلمنا عن الكتب تحت تصنيف art يمكننا طباعة أسماءها وأسماء مؤلفيها

kw=Keywords.get_by(name="art")
for b in kw.books:
  print "book: ",b.title,", authors: "
  for a in b.authors:
    print b.name,
  print "."

الاستعلام والفلترة

بطريقة مشابهة لنفرض أننا نريد كل الكتب التي طبعت بعد 1960

books=Book.query.filter(Book.year>1960)
for b in books: print b.title, "@", b.year

يمكننا أيضا تركيب الفلاتر وراء بعضها كذلك يمكننا ترتيب النتائج وأخذ أول كذا نتيجة

books=Book.query.filter(Book.year>1960).order_by(elixir.sqlalchemy.desc(Book.year)).limit(10)
for b in books: print b.title, "@", b.year

مثال على ربطها مع عكاشة

okasha/elixir.txt · آخر تعديل: 2015/04/23 03:21 بواسطة 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki