برای مدیریت تنظیمات و پیکربندی (configuration) برنامهها، نیاز داریم مقادیر مختلفی رو (مثل تنظیمات اتصال به دیتابیس) تعیین کنیم. در این نوشته، راهکارهای مختلف برای انجام این کار رو (به صورت کلی و مستقل از تکنولوژی) بررسی میکنیم و بعد سراغ کتابخونههای پایتون برای این راهکارها میریم و در نهایت ابزاری رو برای تنظیمات Django انتخاب میکنیم.
لب کلام (TL;DR): از ترکیب config file و env variables استفاده میکنیم و کتابخونه dynaconf یا pydantic-settings
اگر همین Django و CLI اون رو در نظر بگیریم، چند نوع ورودی برای برنامه خواهیم داشت:
تو این نوشته تمرکزمون روی نوع سوم و کمی هم نوع دومه. امّا مستقل از نوع ورودی، به دنبال راهکاری هستیم که چند نیازمندی/ویژگی رو برآورده کنه:
برای این کار، چند راه مرسوم وجود داره:
البته این راهحلها ناسازگار نیستن و میتونیم ترکیبی از راهحلها رو استفاده کنیم (مثل استفاده از env vars به عنوان مقادیر پیشفرض برای flagها). علاوهبر این، امکان توسعه هر کدوم از راهحلها هم وجود داره (مثل اضافه کردن validation به env vars).
در ادامه مزایا و معایب هر کدوم از این راهها رو با هم میبینیم.
گذشته از توصیه شدن توسط The Twelve-Factor App، برتری اصلی این راهکار، امنیت بیشتره. علاوهبر این، در محیطهای مختلف (از جمله docker و k8s) به عنوان راهکاری شناخته شده در دسترسه، و استفاده ازش سادهست.
چالش اصلی این روش اینه که راهی برای تعیین متغییرها (و نوعشون) و کنترل صحت ورودی در ابتدای اجرای برنامه نداریم. مشکل دیگهی این راهکار اینه که فقط از مقادیر string پشتیبانی میکنه (و برای مقادیر دیگه نیاز داریم یکم کد بنویسیم). در آخر هم به خاطر در معرض دید نبودن، ممکنه کمی عیبیابی رو سخت کنه.
مزیت اصلی این روش، مشخص بودن پارامترها (و نوعشون) و کنترل در ابتدای اجرای برنامهست.
مشکل اصلی این روش، قابل رویت بودن رمزهاست: هر کسی که بتونه برنامههای در حال اجرا رو ببینه (مثل خروجی دستور ps)، میتونه پارامترهای ورودی برنامه (از جمله رمزها) رو ببینه. در بعضی کاربردها، مثل وقتی که از پارامترها برای تنظیمات مرتبط با محیط اجرای برنامه استفاده کنیم (مثل تنظیمات اتصال به دیتابیس یا سایر سیستمها)، با توجه به تفاوت بین محیطهای مختلف، باید هربار لیست بلندی از ورودیها رو به برنامه بدیم و این کار جانکاه خواهد بود!
مزیت اصلی این روش، سادگی استفادهست. علاوهبر این، به دلیل پشتیبانی از ساختار تودرتو (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 رو اجرا کنیم، توی CLI وارد کنیم، راهکار مطلوبمون حتماً باید از فایل (حالا یا dot env یا config file) پشتیبانی کنه و قابلیت ترکیب این فایلها رو داشته باشه. علاوهبر فایل، باید بتونه تنظیمات رو از env هم بخونه (برای تغییرات موقتی و/یا امنیت). با در نظر گرفتن این موارد، پیشنهاد میکنم از dynaconf استفاده کنین. به خصوص که:
ضعف اصلی dynaconf، تعریف ساختار برای تنظیماته (عجیب هم نیست. قرار بوده تنظیمات پویا باشن، نه از پیش تعریف شده). اگر دنبال تعریف یک dataclass در پایتون برای تنظیمات هستین (تا از قابلیتهای type-hinting پایتون بهرهمند بشین)، بهتره سراغ pydantic-settings یا typed-settings برید.
در نهایت هم اگر به هیچ کدوم از این قابلیتها نیاز ندارین، خودتون رو با طناب مجانی دار نزنین و سراغ گزینههای سادهتر مثل django-configurations (یا باز هم سادهتر، django-environ) برید.