ادغام Django ORM با SQLAlchemy برای تجزیه و تحلیل آسان تر داده ها

May 2021


توسعه محصولات جانگو معمولاً آسان و سرراست است: مستندات عالی ، ابزارهای زیاد، کتابخانه های منبع باز فراوان و جامعه بزرگ. Django ORM کنترل کامل لایه SQL را در اختیار شما قرار می دهد تا از شما در برابر اشتباهات محافظت کند، و جزئیات اساسی کوئری‌ها را در اختیار شما قرار می دهد تا بتوانید زمان بیشتری را صرف طراحی و ساخت ساختار برنامه خود در کد پایتون کنید. با این حال ، گاهی اوقات چنین رفتاری ممکن است برایتان مشکل ایجاد کند - به عنوان مثال ، هنگامی که توسعه دهندگان جنگو در حال ساخت یک پروژه مربوط به تجزیه و تحلیل داده ها هستند. ساخت پرس و جوهای پیشرفته با جنگو کار آسانی نیست. خواندن آن دشوار است (در پایتون) و درک آنچه در سطح SQL بدون ورود به سیستم یا چاپ پرسشهای SQL ایجاد شده در جایی انجام می شود دشوار است. علاوه بر این ، چنین کوئری هایی نمی توانند به اندازه کافی کارآمد باشند ، بنابراین هنگامی که داده های بیشتری را برای بازی با آنها در DB بارگذاری می کنید ، این کار شما را از بین می برد. در یک لحظه ، می توانید بیش از حد SQL خام را از طریق Django مشاهده کنید، و این لحظه ای است که باید یک وقفه کنید و نگاهی به یک ابزار جالب دیگر بیندازید که درست بین لایه ORM و لایه SQL خام قرار گرفته است.

 

همانطور که از عنوان مقاله مشاهده می کنید ، ما با موفقیت Django ORM و SQLAlchemy Core را با یکدیگر مخلوط کردیم و از نتایج بسیار راضی هستیم. ما یک برنامه کاربردی ساخته ایم که به کمک تجزیه و تحلیل داده های تولید شده توسط سیستم های EMR از طریق جمع آوری داده ها در نمودارها و جداول ، امتیاز دهی به توان عملیاتی / کارایی / هزینه کارکنان و برجسته سازی نکات حاشیه ای که به شما امکان می دهد فرآیندهای کسب و کار را برای کلینیک ها بهینه کرده و صرفه جویی کنید.

 

چه فایده ای دارد که Django ORM را با SQLAlchemy مخلوط کنیم؟

چند دلیل وجود دارد که ما برای این کار از جنگو ORM خارج شدیم:

 

شایان ذکر است که ما یک پایگاه داده دوم نیز ایجاد کردیم که توسط Django ORM اداره می شود تا سایر کارهای مربوط به برنامه وب و نیازهای منطقی تجاری را پوشش دهد. Django ORM در حال تکامل از نسخه ای به نسخه دیگر است و به آن ویژگی های بیشتر و بیشتری اضافه میشود. به عنوان مثال ، در نسخه های اخیر ، برخی از ویژگی های شسته و رفته مانند پشتیبانی از عبارات Subquery یا توابع Window و بسیاری موارد دیگر اضافه شده است، که قبل از استفاده از sqlalchemy یا ابزارهای دیگر بهتر است نگاهی به آنها بیندازید.

 

بنابراین به همین دلیل تصمیم گرفتیم نگاهی به SQLAlchemy بیندازیم. این شامل دو بخش است -ORM و Core. اینSQLAlchemy ORM مشابه Django ORM است ، اما در عین حال ، آنها متفاوت هستند. SQLAlchemy ORM در مقایسه با رویکرد Django’s Active Record از مفهوم دیگری با عنوان Data Mapper استفاده می کند. تا آنجا که شما در حال ساخت پروژه های Django هستید ، قطعاً نباید ORM را تغییر دهید (اگر دلیل خاصی برای این کار ندارید) ، زیرا می خواهید از چارچوب Django REST ، Django-admin و سایر موارد ظریف استفاده کنید که به مدل های جنگو گره خورده است.

 

قسمت دوم  Core نام دارد. درست بین ORM سطح بالا و SQL سطح پایین قرار دارد. هسته بسیار قدرتمند و انعطاف پذیر است. این امکان را برای شما فراهم می کند تا SQL query های مورد نظر خود را بسازید ، و وقتی چنین پرس و جوهایی را در پایتون مشاهده می کنید ، درک اینکه چه خبر است آسان است. به عنوان مثال ، به جستجوی نمونه مستندات نگاهی بیندازید:

q = session.query(User).filter(User.name.like('e%')).  
    limit(5).from_self().
    join(User.addresses).filter(Address.email.like('q%')).
    order_by(User.name)

که به نتیجه زیر ختم میشود:

SELECT anon_1.user_id AS anon_1_user_id,  
       anon_1.user_name AS anon_1_user_name
FROM (SELECT "user".id AS user_id, "user".name AS user_name  
FROM "user"  
WHERE "user".name LIKE :name_1  
 LIMIT :param_1) AS anon_1
JOIN address ON anon_1.user_id = address.user_id  
WHERE address.email LIKE :email_1 ORDER BY anon_1.user_name  

 

توجه: با چنین ترفندهایی ، ما با مشکل N + 1 مواجه نمی شویم: from_select یک بسته بندی اضافی SELECT را در اطراف درخواست ایجاد می کند ، بنابراین در ابتدا مقدار ردیف ها را کاهش می دهیم (از طریق LIKE و LIMIT) و فقط پس از آن به اطلاعات آدرس می پیوندیم .

 

نحوه ترکیب برنامه جنگو و SQLALchemy

بنابراین اگر علاقه مند هستید و می خواهید SQLAlchemy را با برنامه Django مخلوط کنید ، در اینجا چند نکته وجود دارد که می تواند به شما کمک کند.

 

اول از همه ، شما نیاز به ایجاد یک متغیر جهانی با Engine دارید ، اما ارتباط واقعی با DB در اولین اتصال یا اجرای تماس برقرار می شود.

sa_engine = create_engine(settings.DB_CONNECTION_URL, pool_recycle=settings.POOL_RECYCLE)  

 

Create_engine پیکربندی اضافی را برای اتصال می پذیرد. MySQL / MariaDB / AWS Aurora  دارای یک تنظیم interact_timeout است که به طور پیش فرض 8 ساعت است ، بنابراین بدون پارامتر اضافی pool_recycle شما SQLError آزار دهنده خواهید داشت: (OperationalError) (2006 ، "سرور MySQL از بین رفته است"). بنابراین POOL_RECYCLE باید کوچکتر از interact_timeout باشد. به عنوان مثال نیمی از آن: POOL_RECYCLE = 4 * 60 * 60

 

مرحله بعدی ایجاد درخواست های شما است. بسته به معماری برنامه خود ، می توانید جداول و فیلدها را با استفاده از کلاس Table and Column (که همچنین با ORM قابل استفاده است) ایجاد کنید ، یا اگر برنامه شما قبلاً جداول و ستون ها را به روش دیگری ذخیره کرده است ، می توانید این کار را با استفاده Table یا Column انجام دهید. مثل کاری که در این لینک انجام شده.

 

در برنامه ما ، گزینه دوم را انتخاب کردیم ، زیرا اطلاعات مربوط به aggregateها ، فرمول ها و قالب بندی را در syntax خود ذخیره کردیم. سپس ما یک لایه ساختیم که چنین ساختارهایی را می خواند و براساس دستورالعمل های ارائه شده نمایش داده شد.

 

هنگامی که درخواست شما آماده شد ، به سادگی با sa_engine.execute (query) استفاده کنید. نشانگر باز می شود تا زمانی که همه داده ها را بخوانید یا  آن را به طور صریح ببندید.

 

یک نکته بسیار آزار دهنده وجود دارد که قابل ذکر است. همانطور که یک اسناد می گوید ، SQLAlchemy توانایی محدودی در انجام رشته سازی پرس و جو دارد ، بنابراین به دست آوردن یک کوئری نهایی که اجرا شود ، کار چندان ساده ای نیست. می توانید پرس و جو را به تنهایی چاپ کنید:

print(query)

SELECT  
role_group_id, role_group_name, nr_patients  
FROM "StaffSummary"  
WHERE day >= :day_1 AND day <= :day_2 AND location_id = :location_id_1 AND service_id = :service_id_1  

(به نظر می رسد این مورد چندان ترسناک نیست ، اما با پرس و جوهای پیچیده تر ، می تواند حدود 20+ متغیرهایی باشد که پر کردن آنها برای ایجاد بعداً در کنسول SQL بسیار آزار دهنده و زمان بر است.)

 

 

اگر فقط رشته ها و اعدادی دارید که باید در پرس و جو وارد شوند ، این برای شما مفید خواهد بود:

print(s.compile(compile_kwargs={"literal_binds": True}))  

برای تاریخ‌ها، چنین ترفندی جواب نمی دهد. در StackOverflow بحثی در مورد چگونگی دستیابی به نتایج مطلوب وجود دارد ، اما راه حل ها جذاب نیستند.

 

گزینه دیگر فعال کردن درخواست های ورود به پرونده از طریق پیکربندی پایگاه داده است ، اما در این حالت می توانید با مشکل دیگری روبرو شوید. اگر Django ORM به این پایگاه داده وصل شود نیز یافتن کوئری که می خواهید از آن اشکال زدایی پیدا کند دشوار می شود.

 

تست کردن

توجه: Pytest multidb note می گوید: "در حال حاضر pytest-django به طور خاص از پشتیبانی چند پایگاه داده Django پشتیبانی نمی کند. با این وجود می توانید از نمونه های عادی Django TestCase برای استفاده از پشتیبانی multi_db استفاده کنید. "

 

پس یعنی چی - پشتیبانی نمی کنی؟ به طور پیشفرض جنگو برای هر db که در DATABASE نوشته شده باشد یک دیتابیس تستی ایجاد و حذف میکند. این ویژگی به خوبی با pytest کار میکند.

 

Django TestCase و TransactionTestCase با multi_db = True پاک کردن داده ها را در چندین پایگاه داده بین تست‌ها امکان پذیر می کند. همچنین بارگیری داده در پایگاه داده دوم از طریق django-fixtures را امکان پذیر می کند ، اما بهتر است به جای آن از modelmommy یا factoryboy استفاده کنید که تحت تأثیر این ویژگی نیستند.

 

چند مورد هک در بحث pytest-django وجود دارد که نشان می دهد چگونه می توانید با حل این مشکل کار کنید و multi_db را قادر به ادامه آزمایش pytest کنید.

 

یک توصیه مهم وجود دارد - برای جداول دارای مدل های جنگو ، باید داده ها را از طریق Django-ORM در DB ذخیره کنید. در غیر این صورت ، در هنگام نوشتن تست‌ها با مشکل روبرو خواهید شد. TestCase نمی تواند معاملات دیگری را که خارج از اتصال Django DB اتفاق افتاده است ، انجام دهد. اگر چنین وضعیتی دارید ، می توانید از TransactionalTestCase with multi_db = True برای تست هایی که عملکرد اصلی را شامل میشوند استفاده کنید. اما یادتان باشد اینگونه تست‌ها از TestCaseها کندتر هستند.

 

همچنین ، سناریوی دیگری نیز ممکن است - شما مدل های جنگو را فقط در یک پایگاه داده دارید و از طریق SQLAlchemy با پایگاه داده دوم کار می کنید. در این حالت ، multi_db اصلاً روی شما تأثیر نمی گذارد. در چنین مواردی ، باید یک pytest-fixture بنویسید (یا اگر از unittests استفاده می کنید آن را به صورت mixin و منطق راه اندازی در setUp انجام دهید) که ساختار DB را از پرونده SQL ایجاد می کند. چنین پرونده ای باید حاوی DROP TABLE IF EXISTS قبل از CREATE TAB باشد. این ابزار باید برای هر مورد آزمایشی که با این پایگاه داده دستکاری می شود ، اعمال شود. سایر وسایل می توانند داده ها را در جداول ایجاد شده بارگیری کنند.

 

توجه: این تست ها کندتر خواهند بود زیرا جداول برای هر آزمون دوباره ساخته می شوند. در حالت ایده آل ، جداول باید یک بار ایجاد شوند (به عنوان @ pytest.fixture اعلام می شود pytest.fixture(scope='session', autouse=True)) ، و هر معامله باید داده ها را برای هر آزمون برگرداند. دستیابی به آن به دلیل اتصالات مختلف آسان نیست: . اما در حین آزمون ، کد برنامه شما ممکن است پرسش هایی از قبیل connection.execute (کوئری) را انجام دهد که خارج از معامله ای که داده های آزمون را ایجاد کرده است ، برای DB انجام شود. بنابراین با سطح انزوا معامله پیش فرض ، برنامه هیچ داده ای را مشاهده نمی کند ، فقط جداول خالی است. تغییر سطح انزوا در معامله به READ UNCOMMITTED برای اتصال SQLAlchemy امکان پذیر است ، و همه چیز مطابق انتظار کار خواهد کرد ، اما قطعاً اصلاً راه حل نیست.

 

نتیجه

به طور خلاصه همه موارد بالا ، SQLAlchemy Core ابزاری عالی است که شما را به SQL نزدیک می کند و به شما امکان درک و کنترل کامل کوئری‌ها را می دهد. اگر شما در حال ساخت برنامه (یا بخشی از آن) هستید که به aggreagateهای پیشرفته نیاز دارد ، ارزش آن را دارد که از قابلیت های Core SQLAlchemy به عنوان جایگزینی برای ابزارهای ORM Django دیدن کنید.

مقالات مرتبط

شش کاربرد جنگو در وب

django lazy querysets

django aggregation

django custom template filter