Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge branch 'release/0.6'

  • Loading branch information...
commit 0c9835f95b44db1b3eb1a9409f95a3ecd63b8ff5 2 parents 01f2d55 + d6219be
Jannis Leidel authored January 28, 2013
3  .gitignore
@@ -3,8 +3,5 @@ dist
3 3
 MANIFEST
4 4
 *.pyc
5 5
 *.egg-info
6  
-.tox/
7 6
 *.egg
8 7
 docs/_build/
9  
-reports/
10  
-.tox
35  .travis.yml
... ...
@@ -0,0 +1,35 @@
  1
+language: python
  2
+python:
  3
+  - "2.5"
  4
+  - "2.6"
  5
+  - "2.7"
  6
+  - "3.2"
  7
+before_install:
  8
+  - export PIP_USE_MIRRORS=true
  9
+  - export PIP_INDEX_URL=https://simple.crate.io/
  10
+  - export DJANGO_SETTINGS_MODULE=appconf.test_settings
  11
+install:
  12
+  - pip install -e .
  13
+  - pip install https://github.com/django/django/archive/${DJANGO}.zip#egg=django
  14
+  - pip install -r requirements/tests.txt
  15
+before_script:
  16
+  - flake8 appconf --ignore=E501
  17
+script:
  18
+  - coverage run --branch --source=appconf `which django-admin.py` test appconf
  19
+  - coverage report --omit=appconf/test*
  20
+env:
  21
+  - DJANGO=1.3.5
  22
+  - DJANGO=1.4.3
  23
+  - DJANGO=1.5b2
  24
+branches:
  25
+  except:
  26
+    - master
  27
+
  28
+matrix:
  29
+  exclude:
  30
+    - python: "2.5"
  31
+      env: DJANGO=1.5b2
  32
+    - python: "3.2"
  33
+      env: DJANGO=1.3.5
  34
+    - python: "3.2"
  35
+      env: DJANGO=1.4.3
6  AUTHORS
... ...
@@ -1 +1,5 @@
1  
-Jannis Leidel <jannis@leidel.info>
  1
+Christopher Grebs
  2
+Jannis Leidel
  3
+Matthew Tretter
  4
+Rafal Stozek
  5
+Chris Streeter
2  LICENSE
... ...
@@ -1,4 +1,4 @@
1  
-Copyright (c) 2011-2012, Jannis Leidel and individual contributors.
  1
+Copyright (c) 2011-2013, Jannis Leidel and individual contributors.
2 2
 All rights reserved.
3 3
 
4 4
 Redistribution and use in source and binary forms, with or without
6  README.rst
Source Rendered
... ...
@@ -1,6 +1,10 @@
1 1
 django-appconf
2 2
 ==============
3 3
 
  4
+.. image:: https://secure.travis-ci.org/jezdez/django-appconf.png?branch=develop
  5
+    :alt: Build Status
  6
+    :target: http://travis-ci.org/jezdez/django-appconf
  7
+
4 8
 A helper class for handling configuration defaults of packaged Django
5 9
 apps gracefully.
6 10
 
@@ -69,7 +73,7 @@ In case you want to use a different settings object instead of the default
69 73
             holder = 'acme.conf.settings'
70 74
 
71 75
 If you ship an ``AppConf`` class with your reusable Django app, it's
72  
-recommended to put it in a ``conf.py`` file of you app package and
  76
+recommended to put it in a ``conf.py`` file of your app package and
73 77
 import ``django.conf.settings`` in it, too::
74 78
 
75 79
     from django.conf import settings
2  appconf/__init__.py
@@ -2,4 +2,4 @@
2 2
 from .base import AppConf  # noqa
3 3
 
4 4
 # following PEP 386
5  
-__version__ = "0.5"
  5
+__version__ = "0.6"
22  appconf/base.py
... ...
@@ -1,4 +1,6 @@
  1
+from django.core.exceptions import ImproperlyConfigured
1 2
 import sys
  3
+import six
2 4
 from .utils import import_attribute
3 5
 
4 6
 
@@ -9,6 +11,7 @@ def __init__(self, meta, prefix=None):
9 11
         self.holder_path = getattr(meta, 'holder', 'django.conf.settings')
10 12
         self.holder = import_attribute(self.holder_path)
11 13
         self.proxy = getattr(meta, 'proxy', False)
  14
+        self.required = getattr(meta, 'required', [])
12 15
         self.configured_data = {}
13 16
 
14 17
     def prefixed_name(self, name):
@@ -57,7 +60,7 @@ def __new__(cls, name, bases, attrs):
57 60
                 new_class._meta.configured_data.update(
58 61
                     parent._meta.configured_data)
59 62
 
60  
-        for name in filter(lambda name: name == name.upper(), attrs):
  63
+        for name in filter(str.isupper, list(attrs.keys())):
61 64
             prefixed_name = new_class._meta.prefixed_name(name)
62 65
             new_class._meta.names[name] = prefixed_name
63 66
             new_class._meta.defaults[prefixed_name] = attrs.pop(name)
@@ -67,10 +70,18 @@ def __new__(cls, name, bases, attrs):
67 70
             new_class.add_to_class(name, value)
68 71
 
69 72
         new_class._configure()
70  
-        for name, value in new_class._meta.configured_data.iteritems():
  73
+        for name, value in six.iteritems(new_class._meta.configured_data):
71 74
             prefixed_name = new_class._meta.prefixed_name(name)
72 75
             setattr(new_class._meta.holder, prefixed_name, value)
73 76
             new_class.add_to_class(name, value)
  77
+
  78
+        # Confirm presence of required settings.
  79
+        for name in new_class._meta.required:
  80
+            prefixed_name = new_class._meta.prefixed_name(name)
  81
+            if not hasattr(new_class._meta.holder, prefixed_name):
  82
+                raise ImproperlyConfigured('The required setting %s is'
  83
+                                           ' missing.' % prefixed_name)
  84
+
74 85
         return new_class
75 86
 
76 87
     def add_to_class(cls, name, value):
@@ -82,7 +93,7 @@ def add_to_class(cls, name, value):
82 93
     def _configure(cls):
83 94
         # the ad-hoc settings class instance used to configure each value
84 95
         obj = cls()
85  
-        for name, prefixed_name in obj._meta.names.iteritems():
  96
+        for name, prefixed_name in six.iteritems(obj._meta.names):
86 97
             default_value = obj._meta.defaults.get(prefixed_name)
87 98
             value = getattr(obj._meta.holder, prefixed_name, default_value)
88 99
             callback = getattr(obj, "configure_%s" % name.lower(), None)
@@ -92,15 +103,14 @@ def _configure(cls):
92 103
         cls._meta.configured_data = obj.configure()
93 104
 
94 105
 
95  
-class AppConf(object):
  106
+class AppConf(six.with_metaclass(AppConfMetaClass)):
96 107
     """
97 108
     An app setting object to be used for handling app setting defaults
98 109
     gracefully and providing a nice API for them.
99 110
     """
100  
-    __metaclass__ = AppConfMetaClass
101 111
 
102 112
     def __init__(self, **kwargs):
103  
-        for name, value in kwargs.iteritems():
  113
+        for name, value in six.iteritems(kwargs):
104 114
             setattr(self, name, value)
105 115
 
106 116
     def __dir__(self):
9  appconf/test_settings.py
@@ -12,13 +12,8 @@
12 12
     'django.contrib.sites',
13 13
     'django.contrib.auth',
14 14
     'django.contrib.admin',
15  
-    'django_jenkins',
16 15
     'appconf.tests',
17 16
 ]
18 17
 
19  
-JENKINS_TASKS = (
20  
-    'django_jenkins.tasks.run_pyflakes',
21  
-    'django_jenkins.tasks.run_pep8',
22  
-    'django_jenkins.tasks.with_coverage',
23  
-    'django_jenkins.tasks.django_tests',
24  
-)
  18
+TEST_RUNNER = 'discover_runner.DiscoverRunner'
  19
+SECRET_KEY = 'local'
2  appconf/tests/models.py
@@ -2,7 +2,7 @@
2 2
 
3 3
 
4 4
 class CustomHolder(object):
5  
-    pass
  5
+    HOLDER_VALUE = True
6 6
 
7 7
 custom_holder = CustomHolder()
8 8
 
37  appconf/tests/tests.py
... ...
@@ -1,10 +1,11 @@
1 1
 from __future__ import absolute_import
2 2
 from django.conf import settings
  3
+from django.core.exceptions import ImproperlyConfigured
3 4
 from django.test import TestCase
4 5
 
5  
-from .models import (TestConf, PrefixConf, YetAnotherPrefixConf,
6  
-                     SeparateConf, ProxyConf, CustomHolderConf,
7  
-                     custom_holder)
  6
+from appconf.tests.models import (AppConf, TestConf, PrefixConf,
  7
+                                  YetAnotherPrefixConf, SeparateConf, ProxyConf,
  8
+                                  CustomHolderConf, custom_holder)
8 9
 
9 10
 
10 11
 class TestConfTests(TestCase):
@@ -49,14 +50,15 @@ def test_proxy(self):
49 50
     def test_dir_members(self):
50 51
         custom_conf = TestConf()
51 52
         self.assertTrue('TESTS_SIMPLE_VALUE' in dir(settings))
52  
-        self.assertTrue('TESTS_SIMPLE_VALUE' in settings.__members__)
  53
+        if hasattr(settings, '__members__'):  # django 1.5 removed __members__
  54
+            self.assertTrue('TESTS_SIMPLE_VALUE' in settings.__members__)
53 55
         self.assertTrue('SIMPLE_VALUE' in dir(custom_conf))
54 56
         self.assertTrue('SIMPLE_VALUE' in custom_conf.__members__)
55 57
         self.assertFalse('TESTS_SIMPLE_VALUE' in dir(custom_conf))
56 58
         self.assertFalse('TESTS_SIMPLE_VALUE' in custom_conf.__members__)
57 59
 
58 60
     def test_custom_holder(self):
59  
-        custom_conf = CustomHolderConf()
  61
+        CustomHolderConf()
60 62
         self.assertTrue(hasattr(custom_holder, 'CUSTOM_HOLDER_SIMPLE_VALUE'))
61 63
         self.assertEquals(custom_holder.CUSTOM_HOLDER_SIMPLE_VALUE, True)
62 64
 
@@ -115,3 +117,28 @@ def test_prefix(self):
115 117
     def test_simple(self):
116 118
         self.assertTrue(hasattr(settings, 'PREFIX_SEPARATE_VALUE'))
117 119
         self.assertEquals(settings.PREFIX_SEPARATE_VALUE, True)
  120
+
  121
+
  122
+class RequiredSettingsTests(TestCase):
  123
+
  124
+    def create_invalid_conf(self):
  125
+        class RequirementConf(AppConf):
  126
+            class Meta:
  127
+                required = ['NOT_PRESENT']
  128
+
  129
+    def test_value_is_defined(self):
  130
+        class RequirementConf(AppConf):
  131
+            class Meta:
  132
+                holder = 'appconf.tests.models.custom_holder'
  133
+                prefix = 'holder'
  134
+                required = ['VALUE']
  135
+
  136
+    def test_default_is_defined(self):
  137
+        class RequirementConf(AppConf):
  138
+            SIMPLE_VALUE = True
  139
+
  140
+            class Meta:
  141
+                required = ['SIMPLE_VALUE']
  142
+
  143
+    def test_missing(self):
  144
+        self.assertRaises(ImproperlyConfigured, self.create_invalid_conf)
12  docs/changelog.rst
Source Rendered
... ...
@@ -1,6 +1,18 @@
1 1
 Changelog
2 2
 =========
3 3
 
  4
+0.6 (2013-01-28)
  5
+----------------
  6
+
  7
+* Added ``required`` attribute to ``Meta`` to be able to specify which
  8
+  settings are required to be set.
  9
+
  10
+* Moved to Travis for the tests: http://travis-ci.org/jezdez/django-appconf
  11
+
  12
+* Stopped support for Django 1.2.X.
  13
+
  14
+* Introduced support for Python >= 3.2.
  15
+
4 16
 0.5 (2012-02-20)
5 17
 ----------------
6 18
 
2  docs/conf.py
@@ -41,7 +41,7 @@
41 41
 
42 42
 # General information about the project.
43 43
 project = u'django-appconf'
44  
-copyright = u'2011-2012, Jannis Leidel and individual contributors'
  44
+copyright = u'2011-2013, Jannis Leidel and individual contributors'
45 45
 
46 46
 # The version info for the project you're documenting, acts as replacement for
47 47
 # |version| and |release|, also used in various other places throughout the
8  docs/reference.rst
Source Rendered
@@ -52,6 +52,7 @@ Reference
52 52
             class Meta:
53 53
                 proxy = False
54 54
                 prefix = 'myapp'
  55
+                required = ['SETTING_3', 'SETTING_4']
55 56
                 holder = 'django.conf.settings'
56 57
 
57 58
     .. attribute:: prefix
@@ -63,6 +64,13 @@ Reference
63 64
         For example, ``acme`` would turn into settings like
64 65
         ``ACME_SETTING_1``.
65 66
 
  67
+    .. attribute:: required
  68
+
  69
+        A list of settings that must be defined. If any of the specified
  70
+        settings are not defined, ``ImproperlyConfigured`` will be raised.
  71
+
  72
+        .. versionadded:: 0.6
  73
+
66 74
     .. attribute:: holder
67 75
 
68 76
         The global settings holder to use when looking for overrides and
8  docs/usage.rst
Source Rendered
@@ -43,7 +43,7 @@ simply pass the value when instantiating the ``AppConf`` class::
43 43
     myapp_settings = MyAppConf(SETTING_1='something completely different')
44 44
 
45 45
     if 'different' in myapp_settings.SETTINGS_1:
46  
-        print 'yay, I'm different!'
  46
+        print "yay, I'm different!"
47 47
 
48 48
 Custom configuration
49 49
 --------------------
@@ -93,3 +93,9 @@ is provided in the setting instance::
93 93
             enabled = self.configured_data['ENABLED']
94 94
             if not enabled and mode != 'development':
95 95
                 print "WARNING: app not enabled in %s mode!" % mode
  96
+            return self.configured_data
  97
+
  98
+.. note::
  99
+
  100
+    Don't forget to return the configured data in your custom ``configure``
  101
+    method if you edit it.
3  requirements/tests.txt
... ...
@@ -0,0 +1,3 @@
  1
+flake8
  2
+coverage
  3
+django-discover-runner==0.3
7  setup.py
@@ -6,7 +6,7 @@
6 6
 
7 7
 def read(*parts):
8 8
     file_path = path.join(path.dirname(__file__), *parts)
9  
-    return open(file_path).read()
  9
+    return codecs.open(file_path, encoding='utf-8').read()
10 10
 
11 11
 
12 12
 def find_version(*parts):
@@ -32,6 +32,9 @@ def find_version(*parts):
32 32
         'appconf',
33 33
         'appconf.tests',
34 34
     ],
  35
+    install_requires=[
  36
+        'six'
  37
+    ],
35 38
     classifiers=[
36 39
         'Development Status :: 4 - Beta',
37 40
         'Environment :: Web Environment',
@@ -43,6 +46,8 @@ def find_version(*parts):
43 46
         'Programming Language :: Python :: 2.5',
44 47
         'Programming Language :: Python :: 2.6',
45 48
         'Programming Language :: Python :: 2.7',
  49
+        'Programming Language :: Python :: 3.2',
  50
+        'Programming Language :: Python :: 3.3',
46 51
         'Topic :: Utilities',
47 52
     ],
48 53
 )
64  tox.ini
... ...
@@ -1,64 +0,0 @@
1  
-[testenv]
2  
-downloadcache = {toxworkdir}/_download/
3  
-commands =
4  
-    {envbindir}/python {envbindir}/django-admin.py jenkins {posargs:tests}
5  
-setenv =
6  
-    DJANGO_SETTINGS_MODULE = appconf.test_settings
7  
-
8  
-[testenv:docs]
9  
-basepython = python2.7
10  
-deps =
11  
-    Sphinx==1.0.7
12  
-    Django==1.3.1
13  
-commands =
14  
-    rm -rf docs/_build
15  
-    {envbindir}/sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html
16  
-
17  
-[testenv:py25-1.2.X]
18  
-basepython = python2.5
19  
-deps =
20  
-    django==1.2.5
21  
-    pep8
22  
-    pyflakes
23  
-    django-jenkins
24  
-
25  
-[testenv:py26-1.2.X]
26  
-basepython = python2.6
27  
-deps =
28  
-    django==1.2.5
29  
-    pep8
30  
-    pyflakes
31  
-    django-jenkins
32  
-
33  
-[testenv:py27-1.2.X]
34  
-basepython = python2.7
35  
-deps =
36  
-    django==1.2.5
37  
-    pep8
38  
-    pyflakes
39  
-    django-jenkins
40  
-
41  
-
42  
-[testenv:py25-1.3.X]
43  
-basepython = python2.5
44  
-deps =
45  
-    django==1.3
46  
-    pep8
47  
-    pyflakes
48  
-    django-jenkins
49  
-
50  
-[testenv:py26-1.3.X]
51  
-basepython = python2.6
52  
-deps =
53  
-    django==1.3
54  
-    pep8
55  
-    pyflakes
56  
-    django-jenkins
57  
-
58  
-[testenv:py27-1.3.X]
59  
-basepython = python2.7
60  
-deps =
61  
-    django==1.3
62  
-    pep8
63  
-    pyflakes
64  
-    django-jenkins

0 notes on commit 0c9835f

Please sign in to comment.
Something went wrong with that request. Please try again.