Я ищу способ иметь настройки и настройки приложений, которые просты в использовании, трудно ошибиться и немного накладных расходов.
В настоящее время я организовал его следующим образом:
myapp/defaults.py
# application defaults
import sys
if sys.platform == 'win32':
MYAPP_HOME_ROOT = os.path.dirname(os.environ['USERPROFILE'])
else:
MYAPP_HOME_ROOT = '/home'
в моем проекте у меня есть:
mysite/settings.py
from myapp.defaults import * # import all default from myapp
MYAPP_HOME_ROOT = '/export/home' # overriding myapp.defaults
С помощью этой настройки я мог импортировать и использовать настройки в обычном режиме django (from django.conf import settings
и settings.XXX
from django.conf import settings
).
update-3 (зачем нам это нужно)
- Настройки по умолчанию ("по умолчанию"):
- Приложение более удобно использовать, если его можно настроить, переопределив набор разумных настроек по умолчанию.
- приложение "имеет знания домена", поэтому имеет смысл, если это возможно, предоставлять разумные значения по умолчанию.
- пользователю не обязательно предоставлять все настройки, необходимые для каждого приложения, этого должно быть достаточно, чтобы переопределить небольшое подмножество и оставить остальные значения по умолчанию.
- это очень полезно, если значения по умолчанию могут реагировать на окружающую среду. Вы часто захотите сделать что-то другое, когда
DEBUG
True, но могут быть полезны любые другие глобальные параметры: напримерMYAPP_IDENTICON_DIR = os.path.join(settings.MEDIA_ROOT, 'identicons')
(https://en.wikipedia.org/wiki/Identicon) - Project (site/global) должны переопределять параметры приложения по умолчанию, то есть пользователь, который определил
MYAPP_IDENTICON_DIR = 's3://myappbucket.example.com/identicons'
в (глобальном) файлеsettings.py
для своего сайта, должно получить это значение, а не по умолчанию. - любое решение, которое держится близко к обычным способам использования настроек (параметры
import.. settings; settings.FOO
) превосходит решение, которое нуждается в новом синтаксисе (поскольку новый синтаксис будет расходиться, и мы получим новые и уникальные способы использования настроек из приложения к приложению). - возможно, здесь используется zen python:
- Если внедрение трудно объяснить, это плохая идея.
- Если внедрение легко объяснить, это может быть хорошей идеей.
(Первоначальный пост поставил две ключевые проблемы ниже, оставив вышеприведенные предположения неустановленными..)
Проблема № 1: при выполнении модульных тестов для приложения нет сайта, поэтому settings
не будут иметь никаких из myapp.defaults
.
Проблема №2: Существует также большая проблема, если myapp.defaults
необходимо использовать что-либо из настроек (например, settings.DEBUG
), так как вы не можете импортировать настройки из defaults.py
(так как это будет циклический импорт).
Чтобы решить проблему №1, я создал слой косвенности:
myapp/conf.py
from . import defaults
from django.conf import settings
class Conf(object):
def __getattr__(self, attr):
try:
return getattr(settings, attr)
except AttributeError:
return getattr(defaults, attr)
conf = Conf() # !<-- create Conf instance
и использование:
myapp/views.py
from .conf import conf as settings
...
print settings.MYAPP_HOME_ROOT # will print '/export/home' when used from mysite
Это позволяет файлу conf.py
работать с "пустым" файлом настроек тоже, и код myapp может продолжать использовать привычные settings.XXX
.
Он не решает проблему №2, определяя параметры приложения на основе, например, settings.DEBUG
. Моим текущим решением является добавление в класс Conf
:
from . import defaults
from django.conf import settings
class Conf(object):
def __getattr__(self, attr):
try:
return getattr(settings, attr)
except AttributeError:
return getattr(defaults, attr)
if settings.DEBUG:
MYAPP_HOME_ROOT = '/Users'
else:
MYAPP_HOME_ROOT = '/data/media'
conf = Conf() # !<-- create Conf instance
но это не удовлетворяет, так как mysite больше не может переопределять параметр, а настройки/настройки myapp теперь распространяются на два файла...
Есть ли более простой способ сделать это?
update-4: "просто используйте django test runner.."
Приложение, которое вы тестируете, опирается на структуру Django, и вы не можете обойти тот факт, что вам нужно сначала загрузить фреймворк, прежде чем вы сможете протестировать приложение. Часть процесса начальной загрузки создает настройки по умолчанию.py и далее с помощью тестовых лент, поставляемых с помощью django, чтобы убедиться, что ваши приложения тестируются в среде, в которой они, вероятно, будут запущены.
Хотя это звучит так, как будто это должно быть правдой, на самом деле это не имеет большого смысла, например, нет такой settings.py
, как settings.py
(по крайней мере, не для многоразового приложения). Говоря об интеграционном тестировании, имеет смысл тестировать приложение с сайтом/настройками/приложениями/базами данных/кеш (ы)/ограничениями ресурсов/и т.д. что он встретится в производстве. Тем не менее, для модульного тестирования мы хотим протестировать только те единицы кода, которые мы рассматриваем, с максимально возможными внешними зависимостями (и настройками). Тестер тестирования Django делает и должен издеваться над основными частями фреймворка, поэтому нельзя сказать, что он работает в любой "реальной" среде.
Хотя тестовый бегун Django отлично работает, существует длинный список проблем, с которыми он не справляется. Для нас две большие проблемы: (i) выполнение тестов последовательно настолько медленное, что набор тестов становится неиспользуемым (<5 минут при параллельном запуске, почти час при последовательном запуске); (ii) иногда вам просто нужно запускать тесты в больших базах данных (мы восстанавливаем резервную копию прошлой ночи в тестовую базу данных, с которой могут протестировать тесты - слишком большие для светильников).
Люди, которые делали нос, py.test, twill, Selenium и любой из инструментов тестирования fuzz, действительно хорошо знают тестирование, так как это их единственное внимание. Было бы стыдно не быть в состоянии опираться на их коллективный опыт.
Я не первый, кто столкнулся с этой проблемой, и не похоже, что есть простое решение. Вот два проекта, которые имеют различное решение:
Обновление, метод python-oidc-provider:
Пакет python-oidc-provider (https://github.com/juanifioren/django-oidc-provider) имеет еще один творческий способ решения проблемы с настройками приложения/по умолчанию. Он использует свойства для определения значений по умолчанию в файле myapp/settings.py
:
from django.conf import settings
class DefaultSettings(object):
@property
def MYAPP_HOME_ROOT(self):
return ...
default_settings = DefaultSettings()
def get(name):
value = None
try:
value = getattr(default_settings, name)
value = getattr(settings, name)
except AttributeError:
if value is None:
raise Exception("Missing setting: " + name)
используя настройку внутри myapp становится:
from myapp import settings
print settings.get('MYAPP_HOME_ROOT')
good: решает проблему №2 (используя настройки при определении значений по умолчанию), решает проблему № 1 (используя настройки по умолчанию из тестов).
bad: различный синтаксис для доступа к настройкам (settings.get('FOO')
сравнению с обычными settings.FOO
), myapp не может предоставить значения по умолчанию для параметров, которые будут использоваться вне myapp (параметры, которые вы получаете from django.conf import settings
будут не содержат значений по умолчанию из myapp). Внешний код может работать from myapp import settings
чтобы получать обычные настройки и значения myapp по умолчанию, но это ломается, если более чем одно приложение хочет это сделать...
Update2, пакет django-appconf: (Примечание: не относится к Django AppConfig..)
С помощью django-appconfig
параметры приложения создаются в myapp/conf.py
(их нужно загружать раньше, поэтому вам, вероятно, следует импортировать файл conf.py с models.py - поскольку он загружен раньше):
from django.conf import settings
from appconf import AppConf
class MyAppConf(AppConf):
HOME_ROOT = '...'
Применение:
from myapp.conf import settings
print settings.MYAPP_HOME_ROOT
AppConf автоматически добавит префикс MYAPP_
, а также MYAPP_HOME_ROOT
обнаружит, было ли переопределено/переопределено MYAPP_HOME_ROOT
в настройках проекта.
pro: простой в использовании, решает проблему № 1 (доступ к настройкам приложения из тестов) и проблема №2 (с использованием настроек при определении значений по умолчанию). Пока файл conf.py загружается раньше, внешний код должен иметь возможность использовать значения по умолчанию, определенные в myapp.
con: значительно магический. Имя параметра conf.py отличается от его использования (поскольку appconf автоматически добавляет префикс MYAPP_
). Extenal/opaque dependency.