#python #django
#python #django
Вопрос:
Я ищу способ иметь приложения по умолчанию и настройки, которые просты в использовании, их трудно ошибиться и имеют небольшие накладные расходы..
В настоящее время я организовал его следующим образом:
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
).
обновление-3 (зачем нам это нужно)
- Настройки по умолчанию («по умолчанию»):
- Приложение удобнее в использовании, если его можно настроить, переопределив набор разумных настроек по умолчанию.
- приложение «обладает знаниями предметной области», поэтому имеет смысл предоставлять разумные значения по умолчанию, когда это возможно.
- пользователю приложения неудобно указывать все настройки, необходимые для каждого приложения, должно быть достаточно переопределить небольшое подмножество и оставить для остальных значения по умолчанию.
- это очень полезно, если значения по умолчанию могут реагировать на окружающую среду. Вы часто захотите сделать что-то другое, когда
DEBUG
значение True, но может быть полезен любой другой глобальный параметр: напримерMYAPP_IDENTICON_DIR = os.path.join(settings.MEDIA_ROOT, 'identicons')
(https://en.wikipedia.org/wiki/Identicon ) - настройки проекта (сайта / глобальные) должны переопределять настройки приложения по умолчанию, т. Е. Пользователь, который определен
MYAPP_IDENTICON_DIR = 's3://myappbucket.example.com/identicons'
в (глобальном)settings.py
файле для своего сайта, должен получить это значение, а не значение по умолчанию для приложения. - любое решение, близкое к обычному способу использования settings (
import .. settings; settings.FOO
), превосходит решение, требующее нового синтаксиса (поскольку новый синтаксис будет отличаться, и мы получим новые и уникальные способы использования настроек от приложения к приложению). - вероятно, здесь применим дзен python:
- Если реализацию сложно объяснить, это плохая идея.
- Если реализацию легко объяснить, это может быть хорошей идеей.
(В исходном сообщении были указаны две ключевые проблемы, приведенные ниже, оставляя вышеуказанные предположения неустановленными ..)
Проблема № 1: при запуске модульных тестов для приложения, однако, нет сайта, поэтому settings
не было бы ни одного из myapp.defaults
.
Проблема № 2: Существует также большая проблема, если myapp.defaults
необходимо использовать что-либо из настроек (например settings.DEBUG
), поскольку вы не можете импортировать настройки из defaults.py
(поскольку это будет циклический импорт).
To solve problem #1 I created a layer of indirection:
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
and usage:
myapp/views.py
from .conf import conf as settings
...
print settings.MYAPP_HOME_ROOT # will print '/export/home' when used from mysite
This allows the conf.py
file to work with an «empty» settings file too, and the myapp code can continue using the familiar settings.XXX
.
It doesn’t solve problem #2, defining application settings based on e.g. settings.DEBUG
. My current solution is to add to the Conf
class:
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
but this is not satisfying since mysite can no longer override the setting, and myapp’s defaults/settings are now spread over two files…
Is there an easier way to do this?
update-4: «just use the django test runner..»
The app you are testing relies on the Django framework — and you cannot get around the fact that you need to bootstrap the framework first before you can test the app. Part of the bootstrapping process is creating a default settings.py and further using the django-supplied test runners to ensure that your apps are being testing in the environment that they are likely to be run.
While that sounds like it ought to be true, it doesn’t actually make much sense, e.g. there is no such thing as a default settings.py
(at least not for a reusable app). When talking about integration testing it makes sense to test an app with the site/settings/apps/database(s)/cache(s)/resource-limits/etc. that it will encounter in production. For unit testing, however, we want to test just the unit of code that we’re looking at — with as few external dependencies (and settings) as possible. The Django test runner(s) do, and should, mock out major parts of the framework, so it can’t be said to be running in any «real» environment.
While the Django test runner(s) are great, there are a long list of issues it doesn’t handle. The two biggies for us are (i) running tests sequentially is so slow that the test suite becomes unused (<5mins when running in parallel, almost an hour when running sequentially), (ii) sometimes you just need to run tests on big databases (we restore last night’s backup to a test database that the tests can run against — much too big for fixtures).
The people who made nose, py.test, twill, Selenium, and any of the fuzz testing tools really do know testing well, since that is their only focus. It would be a shame to not be able to draw on their collective experience.
I am not the first to have encountered this problem, and there doesn’t look like there is an easy or common solution. Here are two projects that have different solution:
Update, python-oidc-provider method:
The python-oidc-provider package (https://github.com/juanifioren/django-oidc-provider) has another creative way to solve the app-settings/defaults problem. It uses properties to define defaults in a myapp/settings.py
file:
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')
хорошо: решает проблему № 2 (использование настроек при определении значений по умолчанию), решает проблему № 1 (использование настроек по умолчанию из тестов).
плохо: другой синтаксис для доступа к настройкам ( settings.get('FOO')
по сравнению с обычным settings.FOO
), myapp не может предоставить значения по умолчанию для настроек, которые будут использоваться за пределами myapp (настройки, которые вы получаете из from django.conf import settings
, не будут содержать никаких значений по умолчанию из myapp). Внешний код может from myapp import settings
использоваться для получения обычных настроек и настроек myapp по умолчанию, но это не работает, если несколько приложений хотят это сделать…
Обновление2, пакет django-appconf: (Примечание: не связан с AppConfig Django ..)
С 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
переопределен / переопределен в настройках проекта.
pro: прост в использовании, решает проблему № 1 (доступ к настройкам приложения из тестов) и проблему № 2 (использование настроек при определении значений по умолчанию). До тех пор, пока conf.py файл загружается раньше, внешний код должен иметь возможность использовать значения по умолчанию, определенные в myapp.
против: значительно волшебно. Имя параметра в conf.py отличается от его использования (поскольку appconf автоматически добавляет MYAPP_
префикс). Расширенная / непрозрачная зависимость.
Комментарии:
1. Если вы не можете отфильтровать это до вопроса, а не долгого обсуждения; Я боюсь, что ваш вопрос может быть закрыт как «слишком широкий». Я предлагаю обсудить это в трекере ошибок django .
2. Вопрос довольно прост: «Как вы разумно выполняете настройки в приложениях многократного использования?» Всего остального достаточно, чтобы объяснить, почему решений, которые работают для проблем с игрушками, недостаточно, и что нет какого-либо очевидного решения, которое все используют.
3. Почему вы не можете
from django.conf import settings
myapp/defaults.py
использоватьclass Conf
решение (канонический способ, кстати)? Кстати, все остальные решения (кроме этого и первого) будут генерировать плохие документы.4. @конечно, поскольку ваш сайт settings.py (который импортируется, когда вы говорите
from django.conf import settings
) необходимо выполнитьfrom myapp.defaults import *
(чтобы настройки приложения можно было переопределять для каждого сайта). Еслиmyapp/defaults.py
оборачивается и импортируетсяfrom django.conf import settings
, то у вас есть циклический импорт.5. @thebjorn не делайте
from myapp.defaults import *
этого в настройках вашего сайта. В этом весь смыслclass Conf
Ответ №1:
Я только что создал django-app-defaults на основе всех требований. По сути, это обобщение второго подхода, выделенного в вопросе ( class Conf(object):
) .
Использование:
# my_app/defaults.py
# `django.conf.settings` or any other module can be imported if needed
# required
DEFAULT_SETTINGS_MODULE = True
# define default settings below
MY_DEFAULT_SETTING = "yey"
Затем в любом месте вашего проекта:
from app_defaults import settings
print(settings.MY_DEFAULT_SETTING)
# yey
# All `django.conf.settings` are also available
print(settings.DEBUG)
# True
Чтобы загрузить настройки по умолчанию для одного приложения вместо всех приложений, просто выполните:
# Note: when apps or modules are explicitly passed,
# the `DEFAULT_SETTINGS_MODULE` var is not required
from app_defaults import Settings
settings = Settings(apps=["my_app"])
# or
from my_app import defaults
settings = Settings(modules=[defaults])
Комментарии:
1. Мне нравится этот подход.
Ответ №2:
Я написал пакет django для управления настройками приложения под названием django-pluggableappsettings.
Это пакет, который позволяет вам определять разумные значения по умолчанию для ваших настроек, но также добавляет дополнительные функции, такие как проверка типов или вызываемые настройки. Он использует метаклассы, позволяющие легко определять настройки приложений. Конечно, это добавляет внешнюю зависимость к вашему проекту.
Редактировать 1:
Пример использования может быть следующим:
Установите пакет с помощью pip:
pip install django-pluggableappsettings
Создайте свой класс AppSettings в любом из файлов вашего проекта. Например, в ‘app_settings.py «.
app_settings.py
from django_pluggableappsettings import AppSettings, Setting, IntSetting
class MyAppSettings(AppSettings):
MY_SETTING = Setting('DEFAULT_VALUE')
# Checks for "MY_SETTING" in django.conf.settings and
# returns 'DEFAULT_VALUE' if it is not present
ANOTHER_SETTING = IntSetting(42, aliases=['OTHER_SETTING_NAME'])
# Checks for "ANOTHER_SETTING" or "OTHER_SETTING_NAME" in
# django.conf.settings and returns 42 if it is not present.
# also validates that the given value is of type int
Импортируйте свой MyAppSettings
в любой файл и получите доступ к его атрибутам
from mypackage.app_settings import MyAppSettings
MyAppSettings.MY_SETTING
MyAppSettings.ANOTHER_SETTING
Обратите внимание, что настройки инициализируются только при первом доступе, поэтому, если вы никогда не обращаетесь к настройке, ее наличие в django.conf.settings
никогда не проверяется.
Ответ №3:
Проблема № 1: при запуске модульных тестов для приложения, однако, нет сайта, поэтому в настройках не будет ни одного из myapp.defaults.
Эта проблема решается с помощью платформы тестирования, которая поставляется с django (см. Документацию), поскольку она корректно загружает тестовую среду для вас.
Имейте в виду, что тесты django всегда выполняются с DEBUG=False
Проблема № 2: Существует также большая проблема, если myapp.defaults необходимо использовать что-либо из настроек (например, настройки.ОТЛАДКА), поскольку вы не можете импортировать настройки из defaults.py (поскольку это был бы циклический импорт).
На самом деле это не проблема; как только вы импортируете из myapp.defaults
in myapp.settings
, все in settings
будет в области видимости. Итак, вам не нужно импортировать DEBUG
, оно уже доступно вам как в глобальной области видимости.
Комментарии:
1. Как среда тестирования узнает, что ей нужно делать
from myapp.defaults import *
? Что есть / что должно быть внутриmyapp.settings
?2. Я также не уверен, что
manage.py
бы я использовал, посколькуmanage.py
принадлежит проекту / сайту, а не является частью общего приложения. Нужно ли мне создавать / распространять проект с моим многоразовым приложением ..? В настоящее время я запускаю тесты приложенийpy.test
, часто сpytest-django
пакетом.3. Существует только один
manage.py
, который управляет всем проектом . Приложения не имеютmanage.py
файлов, предоставляемых django; это не мешает вам создавать файл, вызываемыйmanage.py
в вашем приложении, но это может иметь непреднамеренные побочные эффекты.4. Мне интересно, говорим ли мы друг о друге ..? Я разрабатываю / тестирую несколько (более 100) повторно используемых приложений, которые используются с несколькими (более 10) проектами / сайтами. Важно, чтобы приложение можно было тестировать изолированно, т. Е. Не требуя сайта / проекта (или только тривиально созданного проекта, если он необходим). Я не понимаю, как
manage.py test
мне это поможет, поскольку для этого потребуется, чтобы я создал manage.py , и a settings.py на верхнем уровне моего приложения (не кажется идиоматичным). Даже если бы я это сделал, я не понимаю, как это решило бы переопределяемые значения по умолчанию (# 1) или доступ к settings.py по умолчанию (#2)..5. Приложение, которое вы тестируете, полагается на фреймворк Django, и вы не можете обойти тот факт, что вам нужно сначала загрузить фреймворк, прежде чем вы сможете протестировать приложение. Частью процесса начальной загрузки является создание значения по умолчанию
settings.py
и дальнейшее использование тестовых модулей, поставляемых django, чтобы убедиться, что ваши приложения тестируются в среде, в которой они, вероятно, будут запущены.
Ответ №4:
Когда я структурирую приложения, я пытаюсь определить функциональность в виде миксинов. Каждая настройка должна быть подобрана одним сочетанием функций, если это возможно.
Итак, в вашем примере выше:
from django.conf import settings
class AppHomeRootMixin:
home_root = getattr(settings, "MYAPP_HOME_ROOT", "/default/path/here")
Использование:
class MyView(AppHomeRootMixin, TemplateView):
def dispatch(self, *args, **kwargs):
print(self.home_root)
return super().dispatch(*args, **kwargs)
Другому разработчику действительно легко увидеть, что именно происходит, устраняет необходимость в стороннем или первичном «волшебстве» и позволяет нам думать о вещах с точки зрения функциональности, а не с точки зрения индивидуальных настроек.
Я просто думаю, что уровень настроек Django должен быть максимально простым, и за любую сложность должен отвечать уровень представления. Я столкнулся с множеством запутанных конфигураций настроек, которые были созданы другими разработчиками, и эти конфигурации потребляли много моего времени, когда не было никого, кто мог бы объяснить, что происходит под капотом.