Learn what there is to be learned from our target.

        Given an object at `obj`, which must be a function, method or
        class, return a configuration *discovered* from the name of
        the object and its parameter list. This function is
        responsible for doing runtime reflection and providing
        understandable failure modes.

        The return value is a dictionary with three keys: ``name``,
        ``required`` and ``defaults``. ``name`` is the name of the
        function/method/class. ``required`` is a list of parameters
        *without* default values. ``defaults`` is a dictionary mapping
        parameter names to default values. The sets of parameter names in
        ``required`` and ``defaults`` are disjoint.

        When given a class, the parameters are taken from its ``__init__``
        method.

        Note that this function is purposefully conservative in the things
        that is will auto-configure. All of the following things will result
        in a :exc:`yakonfig.ProgrammerError` exception being raised:

        1. A parameter list that contains tuple unpacking. (This is invalid
           syntax in Python 3.)
        2. A parameter list that contains variable arguments (``*args``) or
           variable keyword words (``**kwargs``). This restriction forces
           an auto-configurable to explicitly state all configuration.

        Similarly, if given an object that isn't a function/method/class, a
        :exc:`yakonfig.ProgrammerError` will be raised.

        If reflection cannot be performed on ``obj``, then a ``TypeError``
        is raised.

def inspect_obj(obj):
        '''Learn what there is to be learned from our target.

        Given an object at `obj`, which must be a function, method or
        class, return a configuration *discovered* from the name of
        the object and its parameter list. This function is
        responsible for doing runtime reflection and providing
        understandable failure modes.

        The return value is a dictionary with three keys: ``name``,
        ``required`` and ``defaults``. ``name`` is the name of the
        function/method/class. ``required`` is a list of parameters
        *without* default values. ``defaults`` is a dictionary mapping
        parameter names to default values. The sets of parameter names in
        ``required`` and ``defaults`` are disjoint.

        When given a class, the parameters are taken from its ``__init__``
        method.

        Note that this function is purposefully conservative in the things
        that is will auto-configure. All of the following things will result
        in a :exc:`yakonfig.ProgrammerError` exception being raised:

        1. A parameter list that contains tuple unpacking. (This is invalid
           syntax in Python 3.)
        2. A parameter list that contains variable arguments (``*args``) or
           variable keyword words (``**kwargs``). This restriction forces
           an auto-configurable to explicitly state all configuration.

        Similarly, if given an object that isn't a function/method/class, a
        :exc:`yakonfig.ProgrammerError` will be raised.

        If reflection cannot be performed on ``obj``, then a ``TypeError``
        is raised.

        '''
        skip_params = 0
        if inspect.isfunction(obj):
            name = obj.__name__
            inspect_obj = obj
            skip_params = 0
        elif inspect.ismethod(obj):
            name = obj.im_func.__name__
            inspect_obj = obj
            skip_params = 1  # self
        elif inspect.isclass(obj):
            inspect_obj = None
            if hasattr(obj, '__dict__') and '__new__' in obj.__dict__:
                inspect_obj = obj.__new__
            elif hasattr(obj, '__init__'):
                inspect_obj = obj.__init__
            else:
                raise ProgrammerError(
                    'Class "%s" does not have a "__new__" or "__init__" '
                    'method, so it cannot be auto configured.' % str(obj))
            name = obj.__name__
            if hasattr(obj, 'config_name'):
                name = obj.config_name
            if not inspect.ismethod(inspect_obj) \
                    and not inspect.isfunction(inspect_obj):
                raise ProgrammerError(
                    '"%s.%s" is not a method/function (it is a "%s").'
                    % (str(obj), inspect_obj.__name__, type(inspect_obj)))
            skip_params = 1  # self
        else:
            raise ProgrammerError(
                'Expected a function, method or class to '
                'automatically configure, but got a "%s" '
                '(type: "%s").' % (repr(obj), type(obj)))

        argspec = inspect.getargspec(inspect_obj)
        if argspec.varargs is not None or argspec.keywords is not None:
            raise ProgrammerError(
                'The auto-configurable "%s" cannot contain '
                '"*args" or "**kwargs" in its list of '
                'parameters.' % repr(obj))
        if not all(isinstance(arg, string_types) for arg in argspec.args):
            raise ProgrammerError(
                'Expected an auto-configurable with no nested '
                'parameters, but "%s" seems to contain some '
                'tuple unpacking: "%s"'
                % (repr(obj), argspec.args))

        defaults = argspec.defaults or []
        # The index into `argspec.args` at which keyword arguments with default
        # values starts.
        i_defaults = len(argspec.args) - len(defaults)
        return {
            'name': name,
            'required': argspec.args[skip_params:i_defaults],
            'defaults': dict([(k, defaults[i])
                              for i, k in enumerate(argspec.args[i_defaults:])]),
        }