diff --git a/configurations/utils.py b/configurations/utils.py index f864b48..ce5e598 100644 --- a/configurations/utils.py +++ b/configurations/utils.py @@ -1,3 +1,4 @@ +import inspect import sys from django.core.exceptions import ImproperlyConfigured @@ -122,3 +123,76 @@ except ImportError: raise Exception except: # Needed because we might need to catch a SystemExit largs.append(arg) + + +# Copied over from Sphinx +if sys.version_info >= (3, 0): + from functools import partial + + def getargspec(func): + """Like inspect.getargspec but supports functools.partial as well.""" + if inspect.ismethod(func): + func = func.__func__ + if type(func) is partial: + orig_func = func.func + argspec = getargspec(orig_func) + args = list(argspec[0]) + defaults = list(argspec[3] or ()) + kwoargs = list(argspec[4]) + kwodefs = dict(argspec[5] or {}) + if func.args: + args = args[len(func.args):] + for arg in func.keywords or (): + try: + i = args.index(arg) - len(args) + del args[i] + try: + del defaults[i] + except IndexError: + pass + except ValueError: # must be a kwonly arg + i = kwoargs.index(arg) + del kwoargs[i] + del kwodefs[arg] + return inspect.FullArgSpec(args, argspec[1], argspec[2], + tuple(defaults), kwoargs, + kwodefs, argspec[6]) + while hasattr(func, '__wrapped__'): + func = func.__wrapped__ + if not inspect.isfunction(func): + raise TypeError('%r is not a Python function' % func) + return inspect.getfullargspec(func) + +else: # 2.6, 2.7 + from functools import partial + + def getargspec(func): + """Like inspect.getargspec but supports functools.partial as well.""" + if inspect.ismethod(func): + func = func.im_func + parts = 0, () + if type(func) is partial: + keywords = func.keywords + if keywords is None: + keywords = {} + parts = len(func.args), keywords.keys() + func = func.func + if not inspect.isfunction(func): + raise TypeError('%r is not a Python function' % func) + args, varargs, varkw = inspect.getargs(func.func_code) + func_defaults = func.func_defaults + if func_defaults is None: + func_defaults = [] + else: + func_defaults = list(func_defaults) + if parts[0]: + args = args[parts[0]:] + if parts[1]: + for arg in parts[1]: + i = args.index(arg) - len(args) + del args[i] + try: + del func_defaults[i] + except IndexError: + pass + return inspect.ArgSpec(args, varargs, varkw, func_defaults) diff --git a/configurations/values.py b/configurations/values.py index 1b2b1c7..35287eb 100644 --- a/configurations/values.py +++ b/configurations/values.py @@ -8,7 +8,7 @@ from django.core import validators from django.core.exceptions import ValidationError, ImproperlyConfigured from django.utils import six -from .utils import import_by_path +from .utils import import_by_path, getargspec def setup_value(target, name, value): @@ -140,10 +140,20 @@ class CastingMixin(object): error = 'Cannot use caster of {0} ({1!r})'.format(self, self.caster) raise ValueError(error) + try: + arg_names = getargspec(self._caster)[0] + self._params = dict((name, kwargs[name]) + for name in arg_names + if name in kwargs) + except TypeError: + self._params = {} def to_python(self, value): try: - return self._caster(value) + if self._params: + return self._caster(value, **self._params) + 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 5d94fe1..83ded5d 100644 --- a/tests/test_values.py +++ b/tests/test_values.py @@ -295,6 +295,21 @@ class ValueTests(TestCase): 'USER': '', }}) + 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, {})