Механизм IBP Proxy¶
Приложение planiqum.apps.ibp — это динамический прокси-модуль, который обеспечивает прозрачное переключение между различными реализациями IBP (Integrated Business Planning) в проекте.
Введение¶
В ядре Planiqum часто требуется обращаться к моделям, представлениям или скриптам, специфичным для конкретной инсталляции IBP (например, ibp0, ibp1). Чтобы избежать жесткой привязки к конкретному приложению в коде ядра (например, import planiqum.apps.ibp1), используется промежуточный слой — planiqum.apps.ibp.
Этот слой перенаправляет все обращения (импорты, вызовы атрибутов) к "текущему" активному модулю IBP, который задается в настройках.
Как это работает¶
Механизм реализован в файле src/planiqum/apps/ibp.py и базируется на подмене модуля в sys.modules.
1. Настройка MODULE_IBP¶
В файле конфигурации (settings.py или config.conf) задается переменная MODULE_IBP. Она содержит имя реального модуля, который должен использоваться в данный момент.
Пример:
# settings.py
MODULE_IBP = "ibp1" # или "ibp0", "ibp_demo" и т.д.
2. Класс IBPProxyModule¶
Этот класс наследуется от types.ModuleType и переопределяет поведение доступа к атрибутам.
class IBPProxyModule(ModuleType):
def __init__(self, name, real_module):
# ... инициализация ...
self._real_module = real_module
# Копирование атрибутов реального модуля в прокси
for attr_name in dir(real_module):
if not attr_name.startswith("_"):
setattr(self, attr_name, getattr(real_module, attr_name))
def __getattr__(self, name):
# Перехват обращений к подмодулям (models, views и т.д.)
if name in ["models", "views", "urls", "forms", "serializers", ...]:
# Динамический импорт из реального модуля
full_name = f"planiqum.apps.{MODULE_IBP}.{name}"
module = importlib.import_module(full_name)
# Кэширование и возврат
return module
# Делегирование остальных атрибутов реальному модулю
return getattr(self._real_module, name)
3. Подмена модуля (Monkey Patching)¶
В конце файла src/planiqum/apps/ibp.py происходит магия:
_real_module = importlib.import_module(f"planiqum.apps.{MODULE_IBP}")
new_module = IBPProxyModule("planiqum.apps.ibp", _real_module)
sys.modules["planiqum.apps.ibp"] = new_module
Благодаря этому, когда любой другой код выполняет import planiqum.apps.ibp, Python возвращает экземпляр IBPProxyModule, который уже "настроен" на работу с ibp1 (или другим выбранным модулем).
Как использовать¶
Правильные импорты¶
Всегда импортируйте IBP через прокси-модуль planiqum.apps.ibp.
Правильно:
from planiqum.apps.ibp import models as ibp_models
from planiqum.apps.ibp.views import some_view
Неправильно (в коде ядра):
from planiqum.apps.ibp1 import models as ibp_models # Жесткая привязка!
Использование в миграциях¶
В миграциях Django часто используется apps.get_model. Прокси-механизм работает и здесь, так как INSTALLED_APPS будет содержать planiqum.apps.ibp (если так настроено), но чаще всего IBP приложения добавляются в INSTALLED_APPS динамически или явно.
Однако, для доступа к моделям IBP из кода, не зависящего от конкретной реализации, используйте прокси.
Создание новой реализации IBP¶
Чтобы создать новую версию IBP (например, ibp2):
- Создайте директорию
src/planiqum/apps/ibp2. - Добавьте
__init__.py. - Реализуйте стандартную структуру Django-приложения (
models.py,views.py,urls.py). - В настройках сервера измените
MODULE_IBPнаibp2. - Перезапустите сервер. Теперь
from planiqum.apps.ibp import ...будет работать с кодом изibp2.
Ограничения¶
- IDE и статический анализ: PyCharm/VS Code могут не видеть атрибуты внутри
planiqum.apps.ibp, так как они разрешаются динамически. Это нормально. - Циклические импорты: Будьте осторожны при импорте
planiqum.apps.ibpвнутри самогоibpX. Это может привести к рекурсии или циклическому импорту. Обычно модуль реализации (ibp1) не должен знать о существовании прокси.
Важно: описанные настройки и сценарии могут отличаться в вашей инсталляции Planiqum
За уточнениями и методологической поддержкой обращайтесь в компанию
ЮНИК СОФТ