لود داینامیک فایلها و اپلیکیشن‌ها در پایتون و فلسک

سلام.در فلسک یه کانسپت بخصوص به نام بلوپرینت(Blueprint) وجود داره که اجازه میده ما بتونیم بخش‌های مختلف اپلیکیشن رو، از همدیگه تفکیک بکنیم. اگر با جنگو آشنا باشید، بلوپرینت‌ها همون ‍`اپ` های جنگو هستن.حالا، اخیرا نیاز شده بود که به صورت داینامیک یه اپلیکیشن به پروژه اضافه بشه یا ازش برداشته بشه و میخواستیم این کار به ساده‌ترین صورت ممکن قابل انجام باشه. از بین کل‌ راه‌های موجود برای این‌کار، کپی کردن فایل به محلی مشخص و لود شدن بلو‌پرینت و ریجستر شدنش به صورت اتوماتیک رو انتخاب کردیم.با فلسک، پایتونی کد میزنیم!نکتهٔ خیلی مهم در مورد فلسک، این هستش که بر خلاف جنگو، شما پایتون کد می‌زنید. در واقع، برای اینکه بتونید با جنگو کار کنید، باید یاد بگیرید که هر کاری رو توی جنگو به چه صورت انجام می‌دن. ولی این در مورد فلسک صدق نمی‌کنه. فلسک اونقدر کوچیکه و بدون پیش‌فرض در مورد کد شما، که برای کار کردن باهاش، کافیه بلد باشید اون کار رو توی پایتون چطوری باید انجام داد. باقی قضیه دیزاین و توانایی خودتون هست. این قضیه در عین حال که سادگی بیش‌از اندازه فلسک رو به همراه داره، ساختن اپلیکیشن پروداکشن رو باهاش به شدت سخت می‌کنه. شما مسئول همه چیز هستید و هیچ کاری برای شما انجام نمیشه.انجام نرمال کارهااول، میگم‌که ساختار پروژه‌ام چطوری هستش و فایل‌ها رو چطوری کنار هم می‌چینم و بلو‌پرینت درست می‌کنم، بعدش لود داینامیک اونها رو بررسی می‌کنم.من نیاز دارم که یه پروژه فلسک که بلوپرینت ساپورت می‌کنه بسازم. کل فایل‌هایی که باید بسازم به صورت زیر هستش├── develop.py ├── dloader │ ├── application.py │ ├── apps │ │ ├── __init__.py │ │ └── test_app │ │ ├── __init__.py │ │ └── views.py │ ├── __init__.py └── README.mdکدهایی که باید داخل این فایل‌ها بره رو از توی گیت میتونید بردارید. من تمام فایل‌ها رو بررسی نمی‌کنم!کد‌های فایل application.py به صورت زیر هستنfrom flask import Flask def create_app(): app = Flask(__name__) _load_blueprints(app) return app def _load_blueprints(app): from .apps.test_app import test app.register_blueprint(test)همچنین برای ساختن بلو‌پرینت‌ در فایل init.py به صورت زیر عمل کردم(فایل داخل پوشه apps)from flask import Blueprint test = Blueprint('test', __name__)در صورت اجرا، این‌ کدها به درستی عمل میکنند. حالا میخوایم به صورت داینامیک بلو‌پرینت‌ها رو لود کنیم. تغییرات چندان زیادی لازم نیست. همچنین میشه به صورت های مختلفی این‌کار رو انجام داد. من در نهایت از روشی استفاده کردم که در ادامه توضیحش می‌دم اما کاملا به فکر هستم که سیستم‌اش رو عوض کنم.من فایل application.py رو به صورت زیر ویرایش میکنمimport importlib import os def _load_blueprints(app): folder_names = os.listdir("dloader/apps/") for name in folder_names: if name.endswith("_app"): tmp_blueprint = importlib.import_module(f".{name}", "dloader.apps") app.register_blueprint(tmp_blueprint.blueprint)اول از همه کل‌پوشه‌های داخل مسیر رو به دست اوردم، بعد از اون روی لیست پوشه‌ها لوپ زدم. در مرحله بعد بررسی کردم که اگر اسم پوشه به app_ ختم میشه، بنابراین جزو بلوپرینت های من هستش. اون رو ایمپورت کردم و بعد هم ریجستر.این وسط یه اتفاقی افتاده و اون خط اخر هستش. همینجوری که می‌بینید من از ماژول ایمپورت شده، دارم متغییری به نام blueprint رو استفاده می‌کنم. دلیل این قضیه این هستش که من با ایمپورتی که در خط قبلی انجام دادم، نیاز دارم به خود ابجکت بلوپرینت اشاره کنم تا بتونم ریجسترش کنم.حالا برای اینکه اسم بلوپرینت هم داینامیک باشه، من همیشه ابجکت بلوپرینت رو باید داخل یه متغییر به اسم blueprint گذاشته باشم:)فایل init ام به صورت زیر تغییر میکنه بنابراینtest = Blueprint('test', __name__) blueprint = testهمینجور که می‌بینید، من در نهایت ابجکت blueprint رو برای ریجستر کردن استفاده کردم.همچنین در صورتی که بخوام url_prefix استفاده کنم هم می‌تونم به همین صورت یه متغییر بهش بایند کنم و استفاده‌اش کنم.این importlib چیکار میکنهماژول importlib برای ایمپورت کردن پکیج‌ها و ماژول‌های مختلف به صورت داینامیک و از داخل کد هستش. همینجور که می‌بینید ما از یه کد کاملا پایتونی برای انجام این‌کار در فلسک استفاده کردیم.این تابع دوتا ارگومنت می‌گیره که البته دومی الزامی نیست. بر اساس داکیومنت پایتونThe 'package' argument is required when performing a relative import. It specifies the package to use as the anchor point from which to resolve the relative import to an absolute import.بنابراین فقط در صورتی که داریم رلتیو ایمپورت انجام می‌دیم، نیاز هستش که از package استفاده کنیم. همچنین بهتره که با یه . شروع کنیم اسم ماژولی که باید ایمپورت بشه تا به مشکل نخوریم! :)این ماژول واقعا کار راه انداز هستش و مشخصا بهتون کمک می‌کنه که به صورت داینامیک و راحت، با استفاده از کد ماژول‌های مختلف رو ایمپورت کنین و نه اینکه همیشه همه چیز رو ایمپورت کنین. همچنین میشه به صورت کلی به عنوان یه پلاگین سیستم و لودر اتوماتیک پلاگین‌ها هم بهش نگاه کرد.کد کامل پروژه که کار میکنه رو براتون اینجا اپلود کردم که میتونید استفاده کنین.