From e33eeeb871e4a93c16afe76095a0c198d8365e4b Mon Sep 17 00:00:00 2001 From: Dmitry Kozhedubov Date: Sun, 2 Mar 2014 13:30:27 +0400 Subject: [PATCH 01/17] Ability to pass keyword args to CastingMixin casters --- configurations/values.py | 14 +++++++++++++- tests/test_values.py | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/configurations/values.py b/configurations/values.py index 1e51bdb..771843f 100644 --- a/configurations/values.py +++ b/configurations/values.py @@ -3,6 +3,7 @@ import copy import decimal import os import sys +import inspect from django.core import validators from django.core.exceptions import ValidationError, ImproperlyConfigured @@ -95,6 +96,7 @@ class BooleanValue(Value): class CastingMixin(object): exception = (TypeError, ValueError) message = 'Cannot interpret value {0!r}' + kwargs = {} def __init__(self, *args, **kwargs): super(CastingMixin, self).__init__(*args, **kwargs) @@ -106,10 +108,20 @@ class CastingMixin(object): error = 'Cannot use caster of {0} ({1!r})'.format(self, self.caster) raise ValueError(error) + self.process_kwargs(kwargs) + + def process_kwargs(self, kwargs): + if kwargs: + arg_names = inspect.getargspec(self._caster)[0] + self.kwargs = dict((name, kwargs[name]) for name in arg_names \ + if name in kwargs) def to_python(self, value): try: - return self._caster(value) + if self.kwargs: + return self._caster(value, **self.kwargs) + else: + return self._caster(value) except self.exception: raise ValueError(self.message.format(value)) diff --git a/tests/test_values.py b/tests/test_values.py index 191595b..de4482c 100644 --- a/tests/test_values.py +++ b/tests/test_values.py @@ -253,6 +253,21 @@ class ValueTests(TestCase): 'USER': None, }}) + def test_database_url_additional_args(self): + + def mock_database_url_caster(self, url, engine=None): + return { 'URL': url, 'ENGINE': engine } + + with patch('configurations.values.DatabaseURLValue.caster', mock_database_url_caster): + value = DatabaseURLValue(engine='django_mysqlpool.backends.mysqlpool') + with env(DATABASE_URL='sqlite://'): + self.assertEqual(value.setup('DATABASE_URL'), { + 'default': { + 'URL': 'sqlite://', + 'ENGINE': 'django_mysqlpool.backends.mysqlpool' + } + }) + def test_email_url_value(self): value = EmailURLValue() self.assertEqual(value.default, {}) From 8a8f99ab68b958a9382fc607a0cfe5c9b038d394 Mon Sep 17 00:00:00 2001 From: joke2k Date: Thu, 17 Apr 2014 16:22:31 +0200 Subject: [PATCH 02/17] add *.pyc to .gitignore; remove repeated INSTALLED_APPS from test_project.settings; fix check dj-database-url returned None values to empty string --- .gitignore | 3 ++- test_project/test_project/settings.py | 14 -------------- tests/test_values.py | 8 ++++---- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index 4401d89..636d3f0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ docs/_build *.egg test.db build/ -.tox/ \ No newline at end of file +.tox/ +*.pyc diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py index 334cee6..1ffe526 100644 --- a/test_project/test_project/settings.py +++ b/test_project/test_project/settings.py @@ -160,20 +160,6 @@ class Base(Configuration): } } - INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - # Uncomment the next line to enable the admin: - # 'django.contrib.admin', - # Uncomment the next line to enable admin documentation: - # 'django.contrib.admindocs', - 'configurations', - ) - class Debug(Base): YEAH = True diff --git a/tests/test_values.py b/tests/test_values.py index ad1ed90..314529d 100644 --- a/tests/test_values.py +++ b/tests/test_values.py @@ -246,11 +246,11 @@ class ValueTests(TestCase): self.assertEqual(value.setup('DATABASE_URL'), { 'default': { 'ENGINE': 'django.db.backends.sqlite3', - 'HOST': None, + 'HOST': '', 'NAME': ':memory:', - 'PASSWORD': None, - 'PORT': None, - 'USER': None, + 'PASSWORD': '', + 'PORT': '', + 'USER': '', }}) def test_email_url_value(self): From 5279ae4acebf5da4cec9ddd63d2c67a36054df28 Mon Sep 17 00:00:00 2001 From: joke2k Date: Thu, 17 Apr 2014 16:23:36 +0200 Subject: [PATCH 03/17] add support to load .env file into os.environ --- configurations/base.py | 33 +++++++++++++++++++++++++++++++++ test_project/.env | 1 + tests/settings/main.py | 5 +++++ tests/test_env.py | 10 ++++++++++ 4 files changed, 49 insertions(+) create mode 100644 test_project/.env create mode 100644 tests/test_env.py diff --git a/configurations/base.py b/configurations/base.py index 8601c73..b4ea2ab 100644 --- a/configurations/base.py +++ b/configurations/base.py @@ -41,6 +41,39 @@ class ConfigurationBase(type): return "".format(self.__module__, self.__name__) + @staticmethod + def read_env(env_file='.env', **overrides): + """ + Pulled from Honcho code with minor updates, reads local default + environment variables from a .env file located in the project root + directory. + + http://www.wellfireinteractive.com/blog/easier-12-factor-django/ + https://gist.github.com/bennylope/2999704 + """ + import re + import os + + with open(env_file) as f: + content = f.read() + + for line in content.splitlines(): + m1 = re.match(r'\A([A-Za-z_0-9]+)=(.*)\Z', line) + if not m1: + continue + key, val = m1.group(1), m1.group(2) + m2 = re.match(r"\A'(.*)'\Z", val) + if m2: + val = m2.group(1) + m3 = re.match(r'\A"(.*)"\Z', val) + if m3: + val = re.sub(r'\\(.)', r'\1', m3.group(1)) + os.environ.setdefault(key, val) + + # set defaults + for key, value in overrides.items(): + os.environ.setdefault(key, value) + class Configuration(six.with_metaclass(ConfigurationBase)): """ diff --git a/test_project/.env b/test_project/.env new file mode 100644 index 0000000..977f829 --- /dev/null +++ b/test_project/.env @@ -0,0 +1 @@ +DJANGO_ENV_LOADED=True \ No newline at end of file diff --git a/tests/settings/main.py b/tests/settings/main.py index dd425fd..9693f5b 100644 --- a/tests/settings/main.py +++ b/tests/settings/main.py @@ -3,9 +3,13 @@ import uuid import django from configurations import Configuration, pristinemethod +from configurations.values import BooleanValue class Test(Configuration): + + ENV_LOADED = BooleanValue(False) + DEBUG = True SITE_ID = 1 @@ -60,6 +64,7 @@ class Test(Configuration): @classmethod def pre_setup(cls): cls.PRE_SETUP_TEST_SETTING = 6 + cls.read_env('test_project/.env') @classmethod def post_setup(cls): diff --git a/tests/test_env.py b/tests/test_env.py new file mode 100644 index 0000000..637f188 --- /dev/null +++ b/tests/test_env.py @@ -0,0 +1,10 @@ +import os +from django.test import TestCase +from configurations.values import BooleanValue +from mock import patch + +class EnvValueTests(TestCase): + + def test_env_loaded(self): + check_value = BooleanValue(False) + self.assertEqual(check_value.setup('ENV_LOADED'), True) \ No newline at end of file From 27eb748d68f4593643804c2ff03e6438ea0c091a Mon Sep 17 00:00:00 2001 From: joke2k Date: Thu, 17 Apr 2014 18:07:33 +0200 Subject: [PATCH 04/17] refactoring of DotConfiguration --- configurations/__init__.py | 4 +- configurations/base.py | 80 ++++++++++++++++----------- test_project/test_project/settings.py | 2 +- tests/settings/dot_env.py | 6 ++ tests/settings/main.py | 1 - tests/test_env.py | 11 ++-- 6 files changed, 63 insertions(+), 41 deletions(-) create mode 100644 tests/settings/dot_env.py diff --git a/configurations/__init__.py b/configurations/__init__.py index 1ae1e42..373fa53 100644 --- a/configurations/__init__.py +++ b/configurations/__init__.py @@ -1,9 +1,9 @@ # flake8: noqa -from .base import Settings, Configuration +from .base import Settings, Configuration, DotConfiguration from .decorators import pristinemethod __version__ = '0.8' -__all__ = ['Configuration', 'pristinemethod', 'Settings'] +__all__ = ['Configuration', 'DotConfiguration', 'pristinemethod', 'Settings'] def load_ipython_extension(ipython): diff --git a/configurations/base.py b/configurations/base.py index b4ea2ab..658cf12 100644 --- a/configurations/base.py +++ b/configurations/base.py @@ -41,39 +41,6 @@ class ConfigurationBase(type): return "".format(self.__module__, self.__name__) - @staticmethod - def read_env(env_file='.env', **overrides): - """ - Pulled from Honcho code with minor updates, reads local default - environment variables from a .env file located in the project root - directory. - - http://www.wellfireinteractive.com/blog/easier-12-factor-django/ - https://gist.github.com/bennylope/2999704 - """ - import re - import os - - with open(env_file) as f: - content = f.read() - - for line in content.splitlines(): - m1 = re.match(r'\A([A-Za-z_0-9]+)=(.*)\Z', line) - if not m1: - continue - key, val = m1.group(1), m1.group(2) - m2 = re.match(r"\A'(.*)'\Z", val) - if m2: - val = m2.group(1) - m3 = re.match(r'\A"(.*)"\Z', val) - if m3: - val = re.sub(r'\\(.)', r'\1', m3.group(1)) - os.environ.setdefault(key, val) - - # set defaults - for key, value in overrides.items(): - os.environ.setdefault(key, value) - class Configuration(six.with_metaclass(ConfigurationBase)): """ @@ -114,6 +81,53 @@ class Configuration(six.with_metaclass(ConfigurationBase)): setup_value(cls, name, value) +class DotConfiguration(Configuration): + + DOT_ENV = None + DOT_ENV_LOADED = False + + @classmethod + def pre_setup(cls): + Configuration.pre_setup() + if not cls.DOT_ENV_LOADED and cls.DOT_ENV: + cls.read_env(cls.DOT_ENV) + + @classmethod + def read_env(cls, env_file='.env', **overrides): + """ + Pulled from Honcho code with minor updates, reads local default + environment variables from a .env file located in the project root + or provided directory. + + http://www.wellfireinteractive.com/blog/easier-12-factor-django/ + https://gist.github.com/bennylope/2999704 + """ + import re + import os + + with open(env_file) as f: + content = f.read() + + for line in content.splitlines(): + m1 = re.match(r'\A([A-Za-z_0-9]+)=(.*)\Z', line) + if not m1: + continue + key, val = m1.group(1), m1.group(2) + m2 = re.match(r"\A'(.*)'\Z", val) + if m2: + val = m2.group(1) + m3 = re.match(r'\A"(.*)"\Z', val) + if m3: + val = re.sub(r'\\(.)', r'\1', m3.group(1)) + os.environ.setdefault(key, val) + + # set defaults + for key, value in overrides.items(): + os.environ.setdefault(key, value) + + cls.DOT_ENV_LOADED = True + + class Settings(Configuration): @classmethod diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py index 1ffe526..db6500c 100644 --- a/test_project/test_project/settings.py +++ b/test_project/test_project/settings.py @@ -1,7 +1,7 @@ from configurations import Configuration, values -class Base(Configuration): +class Base(DotConfiguration): # Django settings for test_project project. DEBUG = values.BooleanValue(True, environ=True) diff --git a/tests/settings/dot_env.py b/tests/settings/dot_env.py new file mode 100644 index 0000000..6d7acbb --- /dev/null +++ b/tests/settings/dot_env.py @@ -0,0 +1,6 @@ +from configurations import DotConfiguration + + +class DotEnvConfiguration(DotConfiguration): + + DOT_ENV = 'test_project/.env' diff --git a/tests/settings/main.py b/tests/settings/main.py index 9693f5b..d646271 100644 --- a/tests/settings/main.py +++ b/tests/settings/main.py @@ -64,7 +64,6 @@ class Test(Configuration): @classmethod def pre_setup(cls): cls.PRE_SETUP_TEST_SETTING = 6 - cls.read_env('test_project/.env') @classmethod def post_setup(cls): diff --git a/tests/test_env.py b/tests/test_env.py index 637f188..70e7a53 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -1,10 +1,13 @@ import os from django.test import TestCase -from configurations.values import BooleanValue from mock import patch -class EnvValueTests(TestCase): +class DotEnvLoadingTests(TestCase): + + @patch.dict(os.environ, clear=True, + DJANGO_CONFIGURATION='DotEnvConfiguration', + DJANGO_SETTINGS_MODULE='tests.settings.dot_env') def test_env_loaded(self): - check_value = BooleanValue(False) - self.assertEqual(check_value.setup('ENV_LOADED'), True) \ No newline at end of file + from tests.settings import dot_env + self.assertTrue(dot_env.DOT_ENV_LOADED) \ No newline at end of file From 1998ba8d8c326800036e2075137a25af1c7fc3b7 Mon Sep 17 00:00:00 2001 From: Viktor Nagy Date: Thu, 22 May 2014 09:36:11 +0200 Subject: [PATCH 05/17] Added recipe to work with Sphinx --- docs/cookbook.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/cookbook.rst b/docs/cookbook.rst index 4575b8d..bc407db 100644 --- a/docs/cookbook.rst +++ b/docs/cookbook.rst @@ -196,3 +196,18 @@ As you can see django-configurations provides a helper module ``configurations.fastcgi`` that handles the setup of your configurations. .. _runfcgi: https://docs.djangoproject.com/en/1.5/howto/deployment/fastcgi/ + + +Sphinx +------ + +In case you would like to user the amazing `autodoc` feature of `http://sphinx-doc.org/`_, +you can initialize you django project the following way in your `conf.py`:: + + ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../')) + sys.path.append(ROOT) + os.environ['DJANGO_SETTINGS_MODULE'] = 'myproject.settings' + os.environ['DJANGO_CONFIGURATION'] = 'Dev' + + from configurations.importer import install + install() From 738b79bc70a95f0ad60374b2a25378295f80e15f Mon Sep 17 00:00:00 2001 From: Viktor Nagy Date: Tue, 12 Aug 2014 23:10:05 +0200 Subject: [PATCH 06/17] more generic path given --- docs/cookbook.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cookbook.rst b/docs/cookbook.rst index bc407db..82d109e 100644 --- a/docs/cookbook.rst +++ b/docs/cookbook.rst @@ -204,7 +204,7 @@ Sphinx In case you would like to user the amazing `autodoc` feature of `http://sphinx-doc.org/`_, you can initialize you django project the following way in your `conf.py`:: - ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../')) + ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '/path/to/project/root')) sys.path.append(ROOT) os.environ['DJANGO_SETTINGS_MODULE'] = 'myproject.settings' os.environ['DJANGO_CONFIGURATION'] = 'Dev' From 3f892f9814757b171d2f93500a609486d0a9ff27 Mon Sep 17 00:00:00 2001 From: Mauricio de Abreu Antunes Date: Sat, 27 Dec 2014 10:29:29 -0200 Subject: [PATCH 07/17] Fixed wrong behaviour when assigning BooleanValue When assigning False to a BooleanValue and reusing it was raising a ValueError exception because BooleanValue was evaluating False as None. Thanks to @abbottc for catching this error. --- configurations/values.py | 4 ++-- tests/test_values.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/configurations/values.py b/configurations/values.py index 467a0f9..1b2b1c7 100644 --- a/configurations/values.py +++ b/configurations/values.py @@ -44,7 +44,7 @@ class Value(object): def __new__(cls, *args, **kwargs): """ checks if the creation can end up directly in the final value. - That is the case whenever environ = False or environ_name is given + That is the case whenever environ = False or environ_name is given. """ instance = object.__new__(cls) instance.__init__(*args, **kwargs) @@ -59,7 +59,7 @@ class Value(object): environ_prefix='DJANGO', *args, **kwargs): if 'late_binding' in kwargs: self.late_binding = kwargs.get('late_binding') - if isinstance(default, Value) and default.default: + if isinstance(default, Value) and default.default is not None: self.default = copy.copy(default.default) else: self.default = default diff --git a/tests/test_values.py b/tests/test_values.py index 2c3d5ec..5d94fe1 100644 --- a/tests/test_values.py +++ b/tests/test_values.py @@ -108,6 +108,12 @@ class ValueTests(TestCase): with env(DJANGO_TEST='nonboolean'): self.assertRaises(ValueError, value.setup, 'TEST') + def test_boolean_values_assign_false_to_another_booleanvalue(self): + value1 = BooleanValue(False) + value2 = BooleanValue(value1) + self.assertFalse(value1.setup('TEST1')) + self.assertFalse(value2.setup('TEST2')) + def test_integer_values(self): value = IntegerValue(1) with env(DJANGO_TEST='2'): From 41dfcee46c550d01673d1d3fcba898e49d459852 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Tue, 6 Jan 2015 21:16:14 +0100 Subject: [PATCH 08/17] Backported LaxOptionParser from Django 1.7 to make it work on 1.8. --- configurations/importer.py | 3 +- configurations/utils.py | 62 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/configurations/importer.py b/configurations/importer.py index cc4ad57..a76e773 100644 --- a/configurations/importer.py +++ b/configurations/importer.py @@ -5,10 +5,9 @@ import sys from optparse import make_option from django.core.exceptions import ImproperlyConfigured -from django.core.management import LaxOptionParser from django.conf import ENVIRONMENT_VARIABLE as SETTINGS_ENVIRONMENT_VARIABLE -from .utils import uppercase_attributes, reraise +from .utils import uppercase_attributes, reraise, LaxOptionParser from .values import Value, setup_value installed = False diff --git a/configurations/utils.py b/configurations/utils.py index 59c51db..f864b48 100644 --- a/configurations/utils.py +++ b/configurations/utils.py @@ -60,3 +60,65 @@ def reraise(exc, prefix=None, suffix=None): suffix = '(' + suffix + ')' exc.args = ('{0} {1} {2}'.format(prefix, exc.args[0], suffix),) + args[1:] raise + +try: + from django.core.management import LaxOptionParser +except ImportError: + from optparse import OptionParser + + class LaxOptionParser(OptionParser): + """ + An option parser that doesn't raise any errors on unknown options. + + This is needed because the --settings and --pythonpath options affect + the commands (and thus the options) that are available to the user. + + Backported from Django 1.7.x + + """ + def error(self, msg): + pass + + def print_help(self): + """Output nothing. + + The lax options are included in the normal option parser, so under + normal usage, we don't need to print the lax options. + """ + pass + + def print_lax_help(self): + """Output the basic options available to every command. + + This just redirects to the default print_help() behavior. + """ + OptionParser.print_help(self) + + def _process_args(self, largs, rargs, values): + """ + Overrides OptionParser._process_args to exclusively handle default + options and ignore args and other options. + + This overrides the behavior of the super class, which stop parsing + at the first unrecognized option. + """ + while rargs: + arg = rargs[0] + try: + if arg[0:2] == "--" and len(arg) > 2: + # process a single long option (possibly with value(s)) + # the superclass code pops the arg off rargs + self._process_long_opt(rargs, values) + elif arg[:1] == "-" and len(arg) > 1: + # process a cluster of short options (possibly with + # value(s) for the last one only) + # the superclass code pops the arg off rargs + self._process_short_opts(rargs, values) + else: + # it's either a non-default option or an arg + # either way, add it to the args list so we can keep + # dealing with options + del rargs[0] + raise Exception + except: # Needed because we might need to catch a SystemExit + largs.append(arg) From 9dd8ba2a6e17db1ed38b5c634ca30f43604a9cb6 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Tue, 6 Jan 2015 21:16:41 +0100 Subject: [PATCH 09/17] Ignore dists. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9602a1f..8c57199 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ build/ .tox/ htmlcov/ *.pyc +dist/ \ No newline at end of file From a8643a1af59569b018dc7f4d3d6c868bb9305767 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Tue, 6 Jan 2015 21:20:52 +0100 Subject: [PATCH 10/17] Simplify coverage setup. --- .coveragerc | 6 ++++++ tox.ini | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..0227795 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +source = configurations +branch = 1 + +[report] +omit = *tests*,*migrations* diff --git a/tox.ini b/tox.ini index 310f521..ffb209f 100644 --- a/tox.ini +++ b/tox.ini @@ -46,6 +46,5 @@ commands = flake8 configurations --ignore=E501,E127,E128,E124 [testenv:coverage-py27-dj16] commands = coverage erase - coverage run --source=. manage.py test -v2 {posargs:tests} - coverage report -m configurations/*py - coverage html configurations/*py + coverage run manage.py test -v2 {posargs:tests} + coverage report From 98de57b27e373ce3b04dc95f092a290a8d8fbf87 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Tue, 6 Jan 2015 21:38:08 +0100 Subject: [PATCH 11/17] Added configurations.management.call_command. Fix #72. --- configurations/management.py | 3 ++- docs/cookbook.rst | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/configurations/management.py b/configurations/management.py index 01a7d58..e718ef5 100644 --- a/configurations/management.py +++ b/configurations/management.py @@ -2,4 +2,5 @@ from . import importer importer.install(check_options=True) -from django.core.management import execute_from_command_line # noqa +from django.core.management import (execute_from_command_line, # noqa + call_command) diff --git a/docs/cookbook.rst b/docs/cookbook.rst index 4575b8d..2999a03 100644 --- a/docs/cookbook.rst +++ b/docs/cookbook.rst @@ -1,6 +1,22 @@ Cookbook ======== +Calling a Django management command +----------------------------------- + +.. versionadded:: 1.0 + +If you want to call a Django management command programmatically, say +from a script outside of your usual Django code, you can use the +equivalent of Django's :func:`~django.core.management.call_command` function +with django-configurations, too. + +Simply import it from ``configurations.management`` instead:: + + from configurations.management import call_command + + call_command('dumpdata', exclude=['contenttypes', 'auth']) + Envdir ------ From aba18a4cd838d905a13a53f377a53373e9dea5d3 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Tue, 6 Jan 2015 23:15:57 +0100 Subject: [PATCH 12/17] Fix 1.8 test runner. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ffb209f..4826948 100644 --- a/tox.ini +++ b/tox.ini @@ -36,7 +36,7 @@ deps = coverage: coverage commands = - python manage.py test -v2 {posargs:tests} --failfast + python manage.py test -v2 {posargs:tests} [testenv:flake8-py27] commands = flake8 configurations --ignore=E501,E127,E128,E124 From 7799241900fe8e3cb0832b99e17fa9cd9b4144b4 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Tue, 6 Jan 2015 23:27:00 +0100 Subject: [PATCH 13/17] Use py.test. Signed-off-by: Jannis Leidel --- MANIFEST.in | 1 - manage.py | 11 ----------- setup.cfg | 9 +++++++++ tests/settings/main.py | 3 --- tox.ini | 18 +++++++----------- 5 files changed, 16 insertions(+), 26 deletions(-) delete mode 100755 manage.py diff --git a/MANIFEST.in b/MANIFEST.in index caa1d1a..3cf031f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,6 @@ include README.rst include AUTHORS include .travis.yml -include manage.py include tasks.py recursive-include tests * recursive-include docs * diff --git a/manage.py b/manage.py deleted file mode 100755 index 3dd6dcb..0000000 --- a/manage.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python -import os -import sys - -if __name__ == "__main__": - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings.main') - os.environ.setdefault('DJANGO_CONFIGURATION', 'Test') - - from configurations.management import execute_from_command_line - - execute_from_command_line(sys.argv) diff --git a/setup.cfg b/setup.cfg index 5e40900..68af234 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,11 @@ +[pytest] +django_find_project = false +addopts = --cov configurations +DJANGO_SETTINGS_MODULE = tests.settings.main +DJANGO_CONFIGURATION = Test + [wheel] universal = 1 + +[flake8] +ignore = E124,E501,E127,E128 diff --git a/tests/settings/main.py b/tests/settings/main.py index d646271..c0bb716 100644 --- a/tests/settings/main.py +++ b/tests/settings/main.py @@ -34,9 +34,6 @@ class Test(Configuration): ROOT_URLCONF = 'tests.urls' - if django.VERSION[:2] < (1, 6): - TEST_RUNNER = 'discover_runner.DiscoverRunner' - def TEMPLATE_CONTEXT_PROCESSORS(self): return Configuration.TEMPLATE_CONTEXT_PROCESSORS + ( 'tests.settings.base.test_callback', diff --git a/tox.ini b/tox.ini index 4826948..1be78c7 100644 --- a/tox.ini +++ b/tox.ini @@ -20,31 +20,27 @@ basepython = pypy: pypy usedevelop = true deps = - django-discover-runner mock dj-database-url dj-email-url dj-search-url django-cache-url>=0.6.0 six + pytest-django + pytest-cov dj14: https://github.com/django/django/archive/stable/1.4.x.zip#egg=django dj15: https://github.com/django/django/archive/stable/1.5.x.zip#egg=django dj16: https://github.com/django/django/archive/stable/1.6.x.zip#egg=django dj17: https://github.com/django/django/archive/stable/1.7.x.zip#egg=django dj18: https://github.com/django/django/archive/master.zip#egg=django - flake8: flake8 - coverage: coverage commands = - python manage.py test -v2 {posargs:tests} + py.test {posargs:} [testenv:flake8-py27] -commands = flake8 configurations --ignore=E501,E127,E128,E124 +commands = flake8 configurations +deps = flake8 [testenv:flake8-py33] -commands = flake8 configurations --ignore=E501,E127,E128,E124 - -[testenv:coverage-py27-dj16] -commands = coverage erase - coverage run manage.py test -v2 {posargs:tests} - coverage report +commands = flake8 configurations +deps = flake8 From f1f997354798acb5eb330280b656530d62aec0dd Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Tue, 6 Jan 2015 23:28:33 +0100 Subject: [PATCH 14/17] Remove coverage env from travis config. Signed-off-by: Jannis Leidel --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c63d152..8a92e21 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: python env: - - TOXENV=coverage-py27-dj16 - TOXENV=flake8-py27 - TOXENV=flake8-py33 - TOXENV=py26-dj14 From 8a34b53500c670aec799fedb5da05797eec072f9 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 7 Jan 2015 00:05:47 +0100 Subject: [PATCH 15/17] Fixed version in docs. Signed-off-by: Jannis Leidel --- docs/cookbook.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cookbook.rst b/docs/cookbook.rst index b165bb2..e7b78c6 100644 --- a/docs/cookbook.rst +++ b/docs/cookbook.rst @@ -4,7 +4,7 @@ Cookbook Calling a Django management command ----------------------------------- -.. versionadded:: 1.0 +.. versionadded:: 0.9 If you want to call a Django management command programmatically, say from a script outside of your usual Django code, you can use the From 14dd728ad4dc0fb3b660ce5dbe023a8196f49396 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 7 Jan 2015 00:06:38 +0100 Subject: [PATCH 16/17] Improved docs. --- docs/cookbook.rst | 73 +++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/docs/cookbook.rst b/docs/cookbook.rst index e7b78c6..0cb5d54 100644 --- a/docs/cookbook.rst +++ b/docs/cookbook.rst @@ -11,7 +11,10 @@ from a script outside of your usual Django code, you can use the equivalent of Django's :func:`~django.core.management.call_command` function with django-configurations, too. -Simply import it from ``configurations.management`` instead:: +Simply import it from ``configurations.management`` instead: + +.. code-block:: python + :emphasize-lines: 1 from configurations.management import call_command @@ -30,7 +33,9 @@ Imagine for example you want to set a few environment variables, all you have to do is to create a directory with files that have capitalized names and contain the values you want to set. -Example:: +Example: + +.. code-block:: console $ tree mysite_env/ mysite_env/ @@ -46,7 +51,9 @@ Example:: $ Then, to enable the ``mysite_env`` environment variables, simply use the -``envdir`` command line tool as a prefix for your program, e.g.:: +``envdir`` command line tool as a prefix for your program, e.g.: + +.. code-block:: console $ envdir mysite_env python manage.py runserver @@ -67,13 +74,17 @@ using pip_ to install packages. Django 1.5.x ^^^^^^^^^^^^ -First install Django 1.5.x and django-configurations:: +First install Django 1.5.x and django-configurations: - pip install -r https://raw.github.com/jezdez/django-configurations/templates/1.5.x/requirements.txt +.. code-block:: console -Then create your new Django project with the provided template:: + $ pip install -r https://raw.github.com/jezdez/django-configurations/templates/1.5.x/requirements.txt - django-admin.py startproject mysite -v2 --template https://github.com/jezdez/django-configurations/archive/templates/1.5.x.zip +Then create your new Django project with the provided template: + +.. code-block:: console + + $ django-admin.py startproject mysite -v2 --template https://github.com/jezdez/django-configurations/archive/templates/1.5.x.zip See the repository of the template for more information: @@ -82,13 +93,17 @@ See the repository of the template for more information: Django 1.6.x ^^^^^^^^^^^^ -First install Django 1.6.x and django-configurations:: +First install Django 1.6.x and django-configurations: - pip install -r https://raw.github.com/jezdez/django-configurations/templates/1.6.x/requirements.txt +.. code-block:: console -Or Django 1.6:: + $ pip install -r https://raw.github.com/jezdez/django-configurations/templates/1.6.x/requirements.txt - django-admin.py startproject mysite -v2 --template https://github.com/jezdez/django-configurations/archive/templates/1.6.x.zip +Or Django 1.6: + +.. code-block:: console + + $ django-admin.py startproject mysite -v2 --template https://github.com/jezdez/django-configurations/archive/templates/1.6.x.zip Now you have a default Django 1.5.x or 1.6.x project in the ``mysite`` directory that uses django-configurations. @@ -106,7 +121,9 @@ Celery ^^^^^ Given Celery's way to load Django settings in worker processes you should -probably just add the following to the **beginning** of your settings module:: +probably just add the following to the **beginning** of your settings module: + +.. code-block:: python from configurations import importer importer.install() @@ -161,26 +178,32 @@ enable an extension in your IPython configuration. See the IPython documentation for how to create and `manage your IPython profile`_ correctly. Here's a quick how-to in case you don't have a profile yet. Type in your -command line shell:: +command line shell: - ipython profile create +.. code-block:: console + + $ ipython profile create Then let IPython show you where the configuration file ``ipython_config.py`` -was created:: +was created: - ipython locate profile +.. code-block:: console + + $ ipython locate profile That should print a directory path where you can find the ``ipython_config.py`` configuration file. Now open that file and extend the ``c.InteractiveShellApp.extensions`` configuration value. It may be commented out from when IPython created the file or it may not exist in the file at all. -In either case make sure it's not a Python comment anymore and reads like this:: +In either case make sure it's not a Python comment anymore and reads like this: - # A list of dotted module names of IPython extensions to load. - c.InteractiveShellApp.extensions = [ - # .. your other extensions if available - 'configurations', - ] +.. code-block:: python + + # A list of dotted module names of IPython extensions to load. + c.InteractiveShellApp.extensions = [ + # .. your other extensions if available + 'configurations', + ] That will tell IPython to load django-configurations correctly on startup. It also works with django-extensions's shell_plus_ management command. @@ -195,12 +218,14 @@ FastCGI In case you use FastCGI for deploying Django (you really shouldn't) and aren't allowed to use Django's runfcgi_ management command (that would automatically handle the setup for your if you've followed the quickstart guide above), make -sure to use something like the following script:: +sure to use something like the following script: + +.. code-block:: python #!/usr/bin/env python import os - + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings') os.environ.setdefault('DJANGO_CONFIGURATION', 'MySiteConfiguration') From fe96f5b46a68603f04dcaa94716f4f6abf57feba Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 7 Jan 2015 00:11:15 +0100 Subject: [PATCH 17/17] Install pytest-django master to work around issue with Django 1.8. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1be78c7..aac06f9 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,7 @@ deps = dj-search-url django-cache-url>=0.6.0 six - pytest-django + https://github.com/pytest-dev/pytest-django/archive/master.zip#egg=pytest-django pytest-cov dj14: https://github.com/django/django/archive/stable/1.4.x.zip#egg=django dj15: https://github.com/django/django/archive/stable/1.5.x.zip#egg=django