حسین طالقانی
حسین طالقانی
خواندن ۷ دقیقه·۸ ماه پیش

مدیریت تنظیمات در پایتون و جنگو (Django)

برای مدیریت تنظیمات و پیکربندی (configuration) برنامه‌ها، نیاز داریم مقادیر مختلفی رو (مثل تنظیمات اتصال به دیتابیس) تعیین کنیم. در این نوشته، راه‌کارهای مختلف برای انجام این کار رو (به صورت کلی و مستقل از تکنولوژی) بررسی می‌کنیم و بعد سراغ کتابخونه‌های پایتون برای این راهکارها می‌ریم و در نهایت ابزاری رو برای تنظیمات Django انتخاب می‌کنیم.

لب کلام (TL;DR): از ترکیب config file و env variables استفاده می‌کنیم و کتابخونه dynaconf یا pydantic-settings

اگر همین Django و CLI اون رو در نظر بگیریم، چند نوع ورودی برای برنامه خواهیم داشت:

  • دستور (command) که به manage.py میدیم (مثل runserver)
  • پارامترهای دستور (که با نام‌های arguments, flags, options شناخته می‌شن)
  • تنظیمات، که در فایل settings.py و متغیرهای محیطی (env variables) مشخص می‌شن

تو این نوشته تمرکزمون روی نوع سوم و کمی هم نوع دومه. امّا مستقل از نوع ورودی، به دنبال راهکاری هستیم که چند نیازمندی/ویژگی رو برآورده کنه:

  • مشخص بودن پارامترهای ورودی: برنامه برای اجرا به چه پارامترها/تنظیماتی نیاز داره؟ نوع و مقادیر قابل قبول متغیرها چیه؟
  • کنترل ورودی‌ها در شروع برنامه: اگر برنامه اجرا شد، بتونیم مطمئن باشیم ورودی‌ها درست بودن (در مقابل اینکه وسط اجرا و هنگام دسترسی متغیر مورد نظر متوجه مشکل بشیم)
  • حفاظت از رمزها: برخی ورودی‌ها، ماهیت رمز (secret) دارن و باید مطمئن باشیم تا حد امکان از این موارد حفاظت میشه (= امکان دسترسی غیرمجاز بهش محدود میشه)
  • سادگی اجرای برنامه:‌ برای نمونه، اگر لیست بلندی از پارامترها (با مقدار متفاوت از پیش‌فرض) وجود داشته باشه، پاس دادن تمام پارامترها در هر بار اجرای برنامه سخت خواهد بود (در مقابل اینکه مثلاً‌ همه پارامترها در فایل تعریف شده باشه و برنامه تنظیمات رو از فایل بخونه)
  • سادگی رفع مشکل (debug): وقتی مشکلی پیش میاد، بتونیم سریع بفهمیم مقدار پارامتر مورد نظر چیه و کجا این مقدار تعیین شده
  • سادگی تغییر (موقت) مقادیر در زمان اجرا:‌ برای مثال وسط حادثه بتونیم بدون نیاز به deploy مجدد، پارامترهای برنامه رو تغییر بدیم تا یه فرضیه رو امتحان کنیم. حتی ممکنه نیاز داشته باشیم بدون اینکه سایر درخواست‌هایی که به یه سرور می‌رسن رو دستخوش تغییر کنیم، مقدار متغیر رو فقط برای یک (یا چندتا) از پروسه‌ها تغییر بدیم.
  • پشتیبانی از انواع مختلف داده (data types): برای نمونه، راهکاری مثل env variables فقط از string پشتیبانی می‌کنه و در این شرایط، نیاز به لایه‌ای اضافه‌تر برای تعریف نوع داده، و بررسی صحت ورودی
    و تبدیل داده خواهیم داشت. یا درمورد داده‌های پیچیده‌تر (مثل list یا object) نیاز به توافق روی راهکاری برای serialize/deserialize داریم.


راهکارهای کلی

برای این کار، چند راه مرسوم وجود داره:

  • استفاده از environment variables و/یا فایل dot env
  • استفاده از پارامتر‌های ورودی در CLI (که با نام‌های args یا options یا flags شناخته میشن؛ اگرچه این کلمات تفاوت‌هایی جزیی با هم دارن)
  • استفاده از فایل پیکره‌بندی مثل config.json/yml/toml

البته این راه‌حل‌ها ناسازگار نیستن و می‌تونیم ترکیبی از راه‌حل‌ها رو استفاده کنیم (مثل استفاده از env vars به عنوان مقادیر پیش‌فرض برای flagها). علاوه‌بر این، امکان توسعه هر کدوم از راه‌حل‌ها هم وجود داره (مثل اضافه کردن validation به env vars).

در ادامه مزایا و معایب هر کدوم از این راه‌ها رو با هم می‌بینیم.

متغیرهای محیطی (Env. Variables)

گذشته از توصیه شدن توسط The Twelve-Factor App، برتری اصلی این راهکار، امنیت بیش‌تره. علاوه‌بر این، در محیط‌های مختلف (از جمله docker و k8s) به عنوان راهکاری شناخته شده در دسترسه، و استفاده ازش ساده‌ست.

چالش اصلی این روش اینه که راهی برای تعیین متغییرها (و نوعشون) و کنترل صحت ورودی در ابتدای اجرای برنامه نداریم. مشکل دیگه‌ی این راهکار اینه که فقط از مقادیر string پشتیبانی می‌کنه (و برای مقادیر دیگه نیاز داریم یکم کد بنویسیم). در آخر هم به خاطر در معرض دید نبودن، ممکنه کمی عیب‌یابی رو سخت کنه.

پارامترهای خط فرمان (CLI Args)

مزیت اصلی این روش، مشخص بودن پارامترها (و نوع‌شون) و کنترل در ابتدای اجرای برنامه‌ست.

مشکل اصلی این روش، قابل رویت بودن رمزهاست: هر کسی که بتونه برنامه‌های در حال اجرا رو ببینه (مثل خروجی دستور ps)، می‌تونه پارامترهای ورودی برنامه (از جمله رمزها) رو ببینه. در بعضی کاربردها، مثل وقتی که از پارامترها برای تنظیمات مرتبط با محیط اجرای برنامه استفاده کنیم (مثل تنظیمات اتصال به دیتابیس یا سایر سیستم‌ها)، با توجه به تفاوت بین محیط‌های مختلف، باید هربار لیست بلندی از ورودی‌ها رو به برنامه بدیم و این کار جانکاه خواهد بود!


فایل‌های پیکربندی (Config. Files)

مزیت اصلی این روش، سادگی استفاده‌ست. علاوه‌بر این، به دلیل پشتیبانی از ساختار تودرتو (nested)، مدیریت و فهم پارامترها بهتر خواهد بود (به جای چند پارامتر جدا از هم برای db، یک object با چند property خواهیم داشت).

چالش اصلی این روش، توسعه یه کتابخونه‌ست که مسئولیت تعریف schema و خوندن فایل تنظیمات و صحت‌سنجی اون رو به عهده داشته باشه. علاوه‌بر این، بر خلاف flags و env که امکان overwrite کردن مقادیر پیش‌فرض و/یا فایل رو دارن (با تعیین env variable یا flag در زمان اجرای برنامه)، تنها راه تغییر مقادیر در این روش، تغییر فایل config خواهد بود. از نظر امنیت هم با توجه به اینکه رمزها در فایل نوشته میشه، ریسک کامیت شدن فایل وجود داره.


پیکربندی یعنی تنظیم خاص برای محیط/کاربرد خاص
پیکربندی یعنی تنظیم خاص برای محیط/کاربرد خاص


کتابخانه‌ها

اگر بخواین CLI خودتون رو بسازین، گزینه‌های مختلفی از argparse کتابخونه استاندارد پایتون تا Google Fire و Click و Typer پیش روی شماست. امّا وقتی دنبال دریافت یه سری تنظیمات توی Django هستین، که خودش CLI داره، احتمالاً کتابخونه‌ی Abseil Flags گزینه بهتری باشه. چون بدون اینکه شما رو درگیر تعریف دستور (command) بکنه، بهتون اجازه میده پارامترهای ورودی رو تعریف کنین. علاوه‌بر این، به شما این امکان رو میده که flagهای خودتون رو در کنار پارامترهای Django اضافه کنین (و absl.flags فقط پارامترهایی که براش تعریف کردین رو کنترل می‌کنه و بقیه موارد رو بدون بررسی و تغییر به جنگو میده)

با توجه به توضیحات خود توسعه‌دهندگان Abseil Flags (همچنین اینجا و اینجا) به‌طور کلی استفاده از flag خوب نیست و ملاحظات جدی داره. علاوه‌بر این، community محدودی داره و این می‌تونه شما رو گیر بندازه.

برای استفاده از راهکارهای مبتنی بر env، می‌تونین سراغ کتابخانه‌های ساده‌ای مثل django-environ یا python-decouple یا environs برید که یک لایه cast/parsing به os.getenv پایتون اضافه کردن. اگر به دنبال انعطاف‌پذیری و امکانات متنوع‌تری هستین (از جمله پشتیبانی از نوع داده‌های متنوع‌تر مثل set و tuple و dict یا صحت‌سنجی‌های بیش‌تر مثل IP یا Secret)، می‌تونین از django-configurations استفاده کنین.

اگر دنبال کتاخونه‌ای هستین که از config file در کنار متغیرهای محیطی استفاده کنه و امکانات کاملی برای خوندن/مدیریت تنظیمات به شما بده، باید سراغ dynaconf و Hydra (از facebook research) برید. البته این آخری (Hydra) برای کاربردهای خیلی پیچیده (مثل یادگیری ماشین و deep learning) طراحی شده و احتمالاً اگر جای دیگه استفاده کنین، اذیت بشین.

اگر اولویت‌تون تعریف schema برای تنظیماته، می‌تونین سراغ typed-settings برید یا از pydantic-settings استفاده کنین. البته هسته اصلی pydantic که نقش تعریف schema و صحت‌سنجی ورودی داره رو می‌تونین در کنار کتابخونه‌های دیگه (از جمله typed-settings و dynaconf) استفاده کنین.

راهکار پیشنهادی برای مدیریت تنظیمات جنگو (Django)

در یک پروژه جنگو شما احتمالاً تنظیماتی دارین که بین همه محیط‌ها مشترکه و مقادیری هم تو هر محیط متفاوته. از اونجایی که دوست نداریم این مقادیر رو هر بار که می‌خوایم یه دستور django رو اجرا کنیم، توی CLI وارد کنیم، راهکار مطلوب‌مون حتماً باید از فایل (حالا یا dot env یا config file) پشتیبانی کنه و قابلیت ترکیب این فایل‌ها رو داشته باشه. علاوه‌بر فایل، باید بتونه تنظیمات رو از env هم بخونه (برای تغییرات موقتی و/یا امنیت). با در نظر گرفتن این موارد، پیشنهاد می‌کنم از dynaconf استفاده کنین. به خصوص که:

  • به سادگی با django یکپارچه میشه
  • نه تنها فایل جدایی برای رمزها در نظر گرفته (که با خیال راحت توی .gitignore بذارین به جای اینکه بخواین به سبک Django Settings «قول بدین حواستون باشه این خط رو کامیت نکنین»)، بلکه امکان یکپارچه‌سازی با Hashicorp Vault رو داره.
  • با امکاناتی که در CLI میده، می‌تونین لیست تمام تنظیمات رو ببینین (list)، سلسله مراتب تغییرات یه پارامتر و مقدار نهایی اون رو ببینین (inspect) و تنظیمات رو قبل از از اجرای برنامه (یا راه‌اندازی مجدد) صحت‌سنجی کنین (validate).
  • امکان اتصال به Redis یا storageهای دیگه رو داره که هم به شما امکان تغییر تنظیمات در زمان اجرا و/یا به اشتراک گذاشتن تنظیمات بین چند instance رو میده.

ضعف اصلی dynaconf، تعریف ساختار برای تنظیماته (عجیب هم نیست. قرار بوده تنظیمات پویا باشن، نه از پیش تعریف شده). اگر دنبال تعریف یک dataclass در پایتون برای تنظیمات هستین (تا از قابلیت‌های type-hinting پایتون بهره‌مند بشین)، بهتره سراغ pydantic-settings یا typed-settings برید.

در نهایت هم اگر به هیچ کدوم از این قابلیت‌ها نیاز ندارین، خودتون رو با طناب مجانی دار نزنین و سراغ گزینه‌های ساده‌تر مثل django-configurations (یا باز هم ساده‌تر، django-environ) برید.

منابع و مطالب بیش‌تر برای مطالعه

جنگوdjangoبرنامه نویسی
سالک .[ ل ِ ] (ع ص ، اِ) مسافر و راه رونده. / a3dho3yn.ir
شاید از این پست‌ها خوشتان بیاید