Skip to content

Commit

Permalink
Merge branch 'release/0.6'
Browse files Browse the repository at this point in the history
  • Loading branch information
jezdez committed Jan 28, 2013
2 parents 01f2d55 + d6219be commit 0c9835f
Show file tree
Hide file tree
Showing 17 changed files with 135 additions and 93 deletions.
3 changes: 0 additions & 3 deletions .gitignore
Expand Up @@ -3,8 +3,5 @@ dist
MANIFEST
*.pyc
*.egg-info
.tox/
*.egg
docs/_build/
reports/
.tox
35 changes: 35 additions & 0 deletions .travis.yml
@@ -0,0 +1,35 @@
language: python
python:
- "2.5"
- "2.6"
- "2.7"
- "3.2"
before_install:
- export PIP_USE_MIRRORS=true
- export PIP_INDEX_URL=https://simple.crate.io/
- export DJANGO_SETTINGS_MODULE=appconf.test_settings
install:
- pip install -e .
- pip install https://github.com/django/django/archive/${DJANGO}.zip#egg=django
- pip install -r requirements/tests.txt
before_script:
- flake8 appconf --ignore=E501
script:
- coverage run --branch --source=appconf `which django-admin.py` test appconf
- coverage report --omit=appconf/test*
env:
- DJANGO=1.3.5
- DJANGO=1.4.3
- DJANGO=1.5b2
branches:
except:
- master

matrix:
exclude:
- python: "2.5"
env: DJANGO=1.5b2
- python: "3.2"
env: DJANGO=1.3.5
- python: "3.2"
env: DJANGO=1.4.3
6 changes: 5 additions & 1 deletion AUTHORS
@@ -1 +1,5 @@
Jannis Leidel <jannis@leidel.info>
Christopher Grebs
Jannis Leidel
Matthew Tretter
Rafal Stozek
Chris Streeter
2 changes: 1 addition & 1 deletion LICENSE
@@ -1,4 +1,4 @@
Copyright (c) 2011-2012, Jannis Leidel and individual contributors.
Copyright (c) 2011-2013, Jannis Leidel and individual contributors.
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
6 changes: 5 additions & 1 deletion README.rst
@@ -1,6 +1,10 @@
django-appconf
==============

.. image:: https://secure.travis-ci.org/jezdez/django-appconf.png?branch=develop
:alt: Build Status
:target: http://travis-ci.org/jezdez/django-appconf

A helper class for handling configuration defaults of packaged Django
apps gracefully.

Expand Down Expand Up @@ -69,7 +73,7 @@ In case you want to use a different settings object instead of the default
holder = 'acme.conf.settings'

If you ship an ``AppConf`` class with your reusable Django app, it's
recommended to put it in a ``conf.py`` file of you app package and
recommended to put it in a ``conf.py`` file of your app package and
import ``django.conf.settings`` in it, too::

from django.conf import settings
Expand Down
2 changes: 1 addition & 1 deletion appconf/__init__.py
Expand Up @@ -2,4 +2,4 @@
from .base import AppConf # noqa

# following PEP 386
__version__ = "0.5"
__version__ = "0.6"
22 changes: 16 additions & 6 deletions appconf/base.py
@@ -1,4 +1,6 @@
from django.core.exceptions import ImproperlyConfigured
import sys
import six
from .utils import import_attribute


Expand All @@ -9,6 +11,7 @@ def __init__(self, meta, prefix=None):
self.holder_path = getattr(meta, 'holder', 'django.conf.settings')
self.holder = import_attribute(self.holder_path)
self.proxy = getattr(meta, 'proxy', False)
self.required = getattr(meta, 'required', [])
self.configured_data = {}

def prefixed_name(self, name):
Expand Down Expand Up @@ -57,7 +60,7 @@ def __new__(cls, name, bases, attrs):
new_class._meta.configured_data.update(
parent._meta.configured_data)

for name in filter(lambda name: name == name.upper(), attrs):
for name in filter(str.isupper, list(attrs.keys())):
prefixed_name = new_class._meta.prefixed_name(name)
new_class._meta.names[name] = prefixed_name
new_class._meta.defaults[prefixed_name] = attrs.pop(name)
Expand All @@ -67,10 +70,18 @@ def __new__(cls, name, bases, attrs):
new_class.add_to_class(name, value)

new_class._configure()
for name, value in new_class._meta.configured_data.iteritems():
for name, value in six.iteritems(new_class._meta.configured_data):
prefixed_name = new_class._meta.prefixed_name(name)
setattr(new_class._meta.holder, prefixed_name, value)
new_class.add_to_class(name, value)

# Confirm presence of required settings.
for name in new_class._meta.required:
prefixed_name = new_class._meta.prefixed_name(name)
if not hasattr(new_class._meta.holder, prefixed_name):
raise ImproperlyConfigured('The required setting %s is'
' missing.' % prefixed_name)

return new_class

def add_to_class(cls, name, value):
Expand All @@ -82,7 +93,7 @@ def add_to_class(cls, name, value):
def _configure(cls):
# the ad-hoc settings class instance used to configure each value
obj = cls()
for name, prefixed_name in obj._meta.names.iteritems():
for name, prefixed_name in six.iteritems(obj._meta.names):
default_value = obj._meta.defaults.get(prefixed_name)
value = getattr(obj._meta.holder, prefixed_name, default_value)
callback = getattr(obj, "configure_%s" % name.lower(), None)
Expand All @@ -92,15 +103,14 @@ def _configure(cls):
cls._meta.configured_data = obj.configure()


class AppConf(object):
class AppConf(six.with_metaclass(AppConfMetaClass)):
"""
An app setting object to be used for handling app setting defaults
gracefully and providing a nice API for them.
"""
__metaclass__ = AppConfMetaClass

def __init__(self, **kwargs):
for name, value in kwargs.iteritems():
for name, value in six.iteritems(kwargs):
setattr(self, name, value)

def __dir__(self):
Expand Down
9 changes: 2 additions & 7 deletions appconf/test_settings.py
Expand Up @@ -12,13 +12,8 @@
'django.contrib.sites',
'django.contrib.auth',
'django.contrib.admin',
'django_jenkins',
'appconf.tests',
]

JENKINS_TASKS = (
'django_jenkins.tasks.run_pyflakes',
'django_jenkins.tasks.run_pep8',
'django_jenkins.tasks.with_coverage',
'django_jenkins.tasks.django_tests',
)
TEST_RUNNER = 'discover_runner.DiscoverRunner'
SECRET_KEY = 'local'
2 changes: 1 addition & 1 deletion appconf/tests/models.py
Expand Up @@ -2,7 +2,7 @@


class CustomHolder(object):
pass
HOLDER_VALUE = True

custom_holder = CustomHolder()

Expand Down
37 changes: 32 additions & 5 deletions appconf/tests/tests.py
@@ -1,10 +1,11 @@
from __future__ import absolute_import
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase

from .models import (TestConf, PrefixConf, YetAnotherPrefixConf,
SeparateConf, ProxyConf, CustomHolderConf,
custom_holder)
from appconf.tests.models import (AppConf, TestConf, PrefixConf,
YetAnotherPrefixConf, SeparateConf, ProxyConf,
CustomHolderConf, custom_holder)


class TestConfTests(TestCase):
Expand Down Expand Up @@ -49,14 +50,15 @@ def test_proxy(self):
def test_dir_members(self):
custom_conf = TestConf()
self.assertTrue('TESTS_SIMPLE_VALUE' in dir(settings))
self.assertTrue('TESTS_SIMPLE_VALUE' in settings.__members__)
if hasattr(settings, '__members__'): # django 1.5 removed __members__
self.assertTrue('TESTS_SIMPLE_VALUE' in settings.__members__)
self.assertTrue('SIMPLE_VALUE' in dir(custom_conf))
self.assertTrue('SIMPLE_VALUE' in custom_conf.__members__)
self.assertFalse('TESTS_SIMPLE_VALUE' in dir(custom_conf))
self.assertFalse('TESTS_SIMPLE_VALUE' in custom_conf.__members__)

def test_custom_holder(self):
custom_conf = CustomHolderConf()
CustomHolderConf()
self.assertTrue(hasattr(custom_holder, 'CUSTOM_HOLDER_SIMPLE_VALUE'))
self.assertEquals(custom_holder.CUSTOM_HOLDER_SIMPLE_VALUE, True)

Expand Down Expand Up @@ -115,3 +117,28 @@ def test_prefix(self):
def test_simple(self):
self.assertTrue(hasattr(settings, 'PREFIX_SEPARATE_VALUE'))
self.assertEquals(settings.PREFIX_SEPARATE_VALUE, True)


class RequiredSettingsTests(TestCase):

def create_invalid_conf(self):
class RequirementConf(AppConf):
class Meta:
required = ['NOT_PRESENT']

def test_value_is_defined(self):
class RequirementConf(AppConf):
class Meta:
holder = 'appconf.tests.models.custom_holder'
prefix = 'holder'
required = ['VALUE']

def test_default_is_defined(self):
class RequirementConf(AppConf):
SIMPLE_VALUE = True

class Meta:
required = ['SIMPLE_VALUE']

def test_missing(self):
self.assertRaises(ImproperlyConfigured, self.create_invalid_conf)
12 changes: 12 additions & 0 deletions docs/changelog.rst
@@ -1,6 +1,18 @@
Changelog
=========

0.6 (2013-01-28)
----------------

* Added ``required`` attribute to ``Meta`` to be able to specify which
settings are required to be set.

* Moved to Travis for the tests: http://travis-ci.org/jezdez/django-appconf

* Stopped support for Django 1.2.X.

* Introduced support for Python >= 3.2.

0.5 (2012-02-20)
----------------

Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Expand Up @@ -41,7 +41,7 @@

# General information about the project.
project = u'django-appconf'
copyright = u'2011-2012, Jannis Leidel and individual contributors'
copyright = u'2011-2013, Jannis Leidel and individual contributors'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
Expand Down
8 changes: 8 additions & 0 deletions docs/reference.rst
Expand Up @@ -52,6 +52,7 @@ Reference
class Meta:
proxy = False
prefix = 'myapp'
required = ['SETTING_3', 'SETTING_4']
holder = 'django.conf.settings'

.. attribute:: prefix
Expand All @@ -63,6 +64,13 @@ Reference
For example, ``acme`` would turn into settings like
``ACME_SETTING_1``.

.. attribute:: required

A list of settings that must be defined. If any of the specified
settings are not defined, ``ImproperlyConfigured`` will be raised.

.. versionadded:: 0.6

.. attribute:: holder

The global settings holder to use when looking for overrides and
Expand Down
8 changes: 7 additions & 1 deletion docs/usage.rst
Expand Up @@ -43,7 +43,7 @@ simply pass the value when instantiating the ``AppConf`` class::
myapp_settings = MyAppConf(SETTING_1='something completely different')

if 'different' in myapp_settings.SETTINGS_1:
print 'yay, I'm different!'
print "yay, I'm different!"

Custom configuration
--------------------
Expand Down Expand Up @@ -93,3 +93,9 @@ is provided in the setting instance::
enabled = self.configured_data['ENABLED']
if not enabled and mode != 'development':
print "WARNING: app not enabled in %s mode!" % mode
return self.configured_data

.. note::

Don't forget to return the configured data in your custom ``configure``
method if you edit it.
3 changes: 3 additions & 0 deletions requirements/tests.txt
@@ -0,0 +1,3 @@
flake8
coverage
django-discover-runner==0.3
7 changes: 6 additions & 1 deletion setup.py
Expand Up @@ -6,7 +6,7 @@

def read(*parts):
file_path = path.join(path.dirname(__file__), *parts)
return open(file_path).read()
return codecs.open(file_path, encoding='utf-8').read()


def find_version(*parts):
Expand All @@ -32,6 +32,9 @@ def find_version(*parts):
'appconf',
'appconf.tests',
],
install_requires=[
'six'
],
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
Expand All @@ -43,6 +46,8 @@ def find_version(*parts):
'Programming Language :: Python :: 2.5',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Topic :: Utilities',
],
)

0 comments on commit 0c9835f

Please sign in to comment.