
__author__    = "Andre Merzky, Ole Weidner"
__copyright__ = "Copyright 2012-2013, The SAGA Project"
__license__   = "MIT"


""" Attribute interface """

import radical.utils            as ru
import radical.utils.signatures as rus

from . import exceptions as se

# ------------------------------------------------------------------------------

import datetime
import datetime
import traceback
import inspect
import string
import copy
import re
from   pprint import pprint

# FIXME: add a tagging 'Monitorable' interface, which enables callbacks.


now   = datetime.datetime.now
never = datetime.datetime.min

# ------------------------------------------------------------------------------
#
# define a couple of constants for the attribute API, mostly for registering
# attributes.
#
# type enums
ANY         = 'any'        # any python type can be set
URL         = 'url'        # URL type (string + URL parser checks)
INT         = 'int'        # Integer type
FLOAT       = 'float'      # float type
STRING      = 'string'     # string, duh!
BOOL        = 'bool'       # True or False or Maybe
ENUM        = 'enum'       # value is any one of a list of candidates
TIME        = 'time'       # seconds since epoch, or any py time thing
                           # which can be converted into such
                           # FIXME: conversion not implemented

# mode enums
WRITEABLE   = 'writeable'  # the consumer of the interface can change
                           # the attrib value
READONLY    = 'readonly'   # the consumer of the interface can not
                           # change the attrib value.  The
                           # implementation can still change it.
FINAL       = 'final'      # neither consumer nor implementation can
                           # change the value anymore
ALIAS       = 'alias'      # variable is deprecated, and alias'ed to
                           # a different variable.

# attrib extensions
EXTENDED    = 'extended'   # attribute added as extension
PRIVATE     = 'private'    # attribute added as private

# flavor enums
SCALAR      = 'scalar'     # the attribute value is a single data element
DICT        = 'dict'       # the attribute value is a dict of data elements
VECTOR      = 'vector'     # the attribute value is a list of data elements

# ------------------------------------------------------------------------------
#
# Callback (Abstract) Class
#
class Callback () :
    """
    Callback base class.

    All objects using the Attribute Interface allow to register a callback for
    any changes of its attributes, such as 'state' and 'state_detail'.  Those
    callbacks can be python call'ables, or derivates of this callback base
    class.  Instances which inherit this base class MUST implement (overload)
    the cb() method.

    The callable, or the callback's cb() method is what is invoked whenever the
    SAGA implementation is notified of an change on the monitored object's
    attribute.

    The cb instance receives three parameters upon invocation:


      - obj: the watched object instance
      - key:  the watched attribute (e.g. 'state' or 'state_detail')
      - val:  the new value of the watched attribute

    If the callback returns 'True', it will remain registered after invocation,
    to monitor the attribute for the next subsequent state change.  On returning
    'False' (or nothing), the callback will not be called again.

    To register a callback on a object instance, use::

      class MyCallback (saga.Callback):

          def __init__ (self):
              pass

          def cb (self, obj, key, val) :
              print(" %s\\n %s (%s) : %s"  %  self._msg, obj, key, val)

      jd  = saga.job.Description ()
      jd.executable = "/bin/date"

      js  = saga.job.Service ("fork://localhost/")
      job = js.create_job(jd)

      cb = MyCallback()
      job.add_callback(saga.STATE, cb)
      job.run()


    See documentation of the :class:`saga.Attribute` interface for further
    details and examples.
    """

    def __call__ (self, obj, key, val) :
        return self.cb (obj, key, val)

    def cb (self, obj, key, val) :
        """ This is the method that needs to be implemented by the application

            Keyword arguments::

                obj:  the watched object instance
                key:  the watched attribute
                val:  the new value of the watched attribute

            Return::

                keep:   bool, signals to keep (True) or remove (False) the callback
                        after invocation

            Callback invocation MAY (and in general will) happen in a separate
            thread -- so the application need to make sure that the callback
            code is thread-safe.

            The boolean return value is used to signal if the callback should
            continue to listen for events (return True) , or if it rather should
            get unregistered after this invocation (return False).
        """
        pass



# ------------------------------------------------------------------------------
#
class _AttributesBase (object) :
    """
    This class only exists to host properties -- as object itself does *not* have
    properties!  This class is not part of the public attribute API.
    """

    # --------------------------------------------------------------------------
    #
    @rus.takes   ('_AttributesBase')
    @rus.returns (rus.nothing)
    def __init__ (self) :
        pass


# ------------------------------------------------------------------------------
#
class Attributes (_AttributesBase, ru.DictMixin) :
    """
    Attribute Interface Class

    The Attributes interface implements the attribute semantics of the SAGA Core
    API specification (http://ogf.org/documents/GFD.90.pdf).  Additionally, this
    implementation provides that semantics the python property interface.  Note
    that a *single* set of attributes is internally managed, no matter what
    interface is used for access.

    A class which uses this interface can internally specify which attributes
    can be set, and what type they have.  Also, default values can be specified,
    and the class provides a rudimentary support for converting scalar
    attributes into vector attributes and back.

    Also, the consumer of this API can register callbacks, which get triggered
    on changes to specific attribute values.

    Example use case::


        # --------------------------------------------------------------------------------
        class Transliterator ( saga.Attributes ) :

            def __init__ (self, *args, **kwargs) :
                # setting attribs to non-extensible will cause the cal to init below to
                # complain if attributes are specified.  Default is extensible.
              # self._attributes_extensible (False)

                # pass args to base class init (requires 'extensible')
                super (Transliterator, self).__init__ (*args, **kwargs)

                # setup class attribs
                self._attributes_register   ('apple', 'Appel', URL,    SCALAR, WRITEABLE)
                self._attributes_register   ('plum',  'Pruim', STRING, SCALAR, READONLY)

                # setting attribs to non-extensible at *this* point will have allowed
                # custom user attribs on __init__ time (via args), but will then forbid
                # any additional custom attributes.
              # self._attributes_extensible (False)


        # --------------------------------------------------------------------------------
        if __name__ == "__main__":

            # define a callback method.  This callback can get registered for
            # attribute changes later.

            # ----------------------------------------------------------------------------
            def cb (key, val, obj) :
                # the callback gets information about what attribute was changed
                # on what object:
                print("called: %s - %s - %s"  %  (key, str(val), type (obj)))

                # returning True will keep the callback registered for further
                # attribute changes.
                return True
            # ----------------------------------------------------------------------------

            # create a class instance and add a 'cherry' attribute/value on
            # creation.
            trans = Transliterator (cherry='Kersche')

            # use the property interface to mess with the pre-defined
            # 'apple' attribute
            print("\\n -- apple")
            print(trans.apple)
            trans.apple = 'Abbel'
            print(trans.apple)

            # add our callback to the apple attribute, and trigger some changes.
            # Note that the callback is also triggered when the attribute's
            # value changes w/o user control, e.g. by some internal state
            # changes.
            trans.add_callback ('apple', cb)
            trans.apple = 'Apfel'

            # Setting an attribute final is actually an internal method, used by
            # the implementation to signal that no further changes on that
            # attribute are expected.  We use that here for demonstrating the
            # concept though.  Callback is invoked on set_final().
            trans._attributes_set_final ('apple')
            trans.apple = 'Abbel'
            print(trans.apple)

            # mess around with the 'plum' attribute, which was marked as
            # ReadOnly on registration time.
            print("\\n -- plum")
            print(trans.plum)
          # trans.plum    = 'Pflaume'  # raises readonly exception
          # trans['plum'] = 'Pflaume'  # raises readonly exception
            print(trans.plum)

            # check if the 'cherry' attribute exists, which got created on
            # instantiation time.
            print("\\n -- cherry")
            print(trans.cherry)

            # as we have 'extensible' set, we can add a attribute on the fly,
            # via either the property interface, or via the GFD.90 API of
            # course.
            print("\\n -- peach")
            print(trans.peach)
            trans.peach = 'Birne'
            print(trans.peach)


    This example will result in::

        -- apple
        Appel
        Appel
        Abbel
        called: apple - Abbel Appel  - <class '__main__.Transliterator'>
        called: apple - Apfel - <class '__main__.Transliterator'>
        called: apple - Apfel - <class '__main__.Transliterator'>
        Apfel

        -- plum
        Pruim
        Pruim

        -- cherry
        Kersche

        -- peach
        Berne
        Birne


    """

    # internally used constants to distinguish API from adaptor calls
    _UP    = '_up'
    _DOWN  = '_down'

    # two regexes for converting CamelCase into under_score_casing, as static
    # class vars to avoid frequent recompilation
    _camel_case_regex_1 = re.compile('(.)([A-Z][a-z]+)')
    _camel_case_regex_2 = re.compile('([a-z0-9])([A-Z])')


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  rus.anything,
                  rus.anything)
    @rus.returns (rus.nothing)
    def __init__ (self, *args, **kwargs) :
        """
        This method is not supposed to be directly called by the consumer of
        this API -- it should be called via derived object construction.

        _attributes_t_init makes sure that the basic structures are in place on
        the attribute dictionary - this saves us ton of safety checks later on.
        """
        # initialize state
        d = self._attributes_t_init ()

        # call to update and the args/kwargs handling seems to be part of the
        # dict interface conventions *shrug*
        # we use similar mechanism to initialize attribs here:

        for arg in args :
            if arg == None:
                # be resiliant to empty initialization
                pass
            elif isinstance (arg, dict):
                d['extensible']  = True   # it is just being extended ;)
                d['camelcasing'] = True   # default for dict inits
                for key in list(arg.keys()):
                    us_key = self._attributes_t_underscore(key)
                    self._attributes_i_set(us_key, arg[key], force=True, flow=self._UP)
            else:
                raise se.BadParameter("initialization expects dictionary")

        for key in list(kwargs.keys ()) :
            self.set_attribute (key, kwargs[key])

        # make iterable
        d['_iterpos'] = 0
        self.list_attributes ()



    # --------------------------------------------------------------------------
    #
    # Internal interface tools.
    #
    # These tools are only for internal use, and should never be called from
    # outside of this module.
    #
    # Naming: _attributes_t_*
    #
    @rus.takes   ('Attributes',
                  rus.optional (str))
    @rus.returns (dict)
    def _attributes_t_init (self, key=None) :
        """
        This internal function is not to be used by the consumer of this API.

        The _attributes_t_init method initializes the interface's internal data
        structures.  We always need the attribute dict, and the extensible flag.
        Everything else can be added on the fly.  The method will not overwrite
        any settings -- initialization occurs only once!

        If a key is given, the existence of this key is checked.  An exception
        is raised if the key does not exist.

        The internal data are stored as property on the _AttributesBase class.
        Storing them as property on *this* class would obviously result in
        recursion...
        """

        d = {}

        try :
            d = _AttributesBase.__getattribute__ (self, '_d')
        except :
            # need to initialize -- any exceptions in the code below should fall through
            d['attributes']  = {}
            d['extensible']  = True
            d['private']     = True
            d['camelcasing'] = False
            d['getter']      = None
            d['setter']      = None
            d['lister']      = None
            d['caller']      = None
            d['recursion']   = False
            d['_iterpos']    = 0

            _AttributesBase.__setattr__ (self, '_d', d)


        # check if we know about the given attribute
        if  key :
            if key not in d['attributes'] :
                raise se.DoesNotExist ("attribute key is invalid: %s"  %  (key))

        # all is well
        return d


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str)
    @rus.returns (str)
    def _attributes_t_keycheck (self, key) :
        """
        This internal function is not to be used by the consumer of this API.

        For the given key, check if the key name is valid, and/or if it is
        aliased.

        If the does not yet exist, the validity check is performed, and allows
        to limit dynamically added attribute names (for 'extensible' sets).

        If the key does exist, the alias check triggers a deprecation warning,
        and returns the aliased key for transparent operation.
        """

        # make sure interface is ready to use
        d = self._attributes_t_init ()

        # perform name validity checks if key is new
        if  not key in d['attributes'] :
            # FIXME: we actually don't have any tests, yet.  We should allow to
            # configure such via, say, _attributes_add_check (callable (key))
            pass


        # if key is known, check for aliasing
        else:
            # check if we know about the given attribute
            if  d['attributes'][key]['mode'] == ALIAS :
                alias = d['attributes'][key]['alias']
                print("attribute '%s' is deprecated - use '%s'"  %  (key, alias))
                key   = alias

        return key


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.anything)
    @rus.returns (rus.nothing)
    def _attributes_t_call_cb (self, key, val) :
        """
        This internal function is not to be used by the consumer of this API.

        It triggers the invocation of all callbacks for a given attribute.
        Callbacks returning False (or nothing at all) will be unregistered after
        their invocation.
        """

        # make sure interface is ready to use
        d = self._attributes_t_init (key)

        # avoid recursion
        if  d['attributes'][key]['recursion'] :
            return

        callbacks = d['attributes'][key]['callbacks']

        # iterate over a copy of the callback list, so that remove does not
        # screw up the iteration
        for cb in list (callbacks) :

            call = cb

            # got the callable - call it!
            # raise and lower recursion shield as needed
            ret = False
            try :
                d['attributes'][key]['recursion'] = True
                ret = call (self, key, val)
            finally :
                d['attributes'][key]['recursion'] = False

            # remove callbacks which return 'False', or raised and exception
            if  not ret :
                callbacks.remove (cb)



    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.anything)
    @rus.returns (rus.nothing)
    def _attributes_t_call_setter (self, key, val) :
        """
        This internal function is not to be used by the consumer of this API.

        It triggers the setter callbacks, to signal that the attribute value
        has just been set and should be propagated as needed.
        """

        # make sure interface is ready to use.
        d = self._attributes_t_init (key)

        # avoid recursion
        if  d['attributes'][key]['recursion'] :
            return

        # no callbacks for private keys
        if  key[0] == '_' and d['private'] :
            return

        # key_setter overwrites results from all_setter
        all_setter = d['setter']
        key_setter = d['attributes'][key]['setter']

        # Get the value via the attribute setter.  The setter will not call
        # attrib setters or callbacks, due to the recursion guard.
        # Set the value via the native setter (to the backend),
        # always raise and lower the recursion shield
        #
        # If both are present, we can ignore *one* exception.  If one
        # is present, exceptions are not ignored.
        #
        # always raise and lower the recursion shield.
        can_ignore = 0
        if  all_setter and key_setter : can_ignore = 1

        if  all_setter :
            try :
                d['attributes'][key]['recursion'] = True
                all_setter (key, val)
            except Exception as e :
                # ignoring failures from setter
                pass
            except Exception as e :
                can_ignore -= 1
                if not can_ignore : raise e
            finally :
                d['attributes'][key]['recursion'] = False

        if  key_setter :
            try :
                d['attributes'][key]['recursion'] = True
                key_setter (val)
            except:
                can_ignore -= 1
                if not can_ignore : raise
            finally :
                d['attributes'][key]['recursion'] = False



    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str)
    @rus.returns (rus.nothing)
    def _attributes_t_call_getter (self, key) :
        """
        This internal function is not to be used by the consumer of this API.

        It triggers the getter callbacks, to signal that the attribute value
        is about to be accesses and should be updated as needed.
        """

        # make sure interface is ready to use.
        d = self._attributes_t_init (key)

        # avoid recursion
        if  d['attributes'][key]['recursion'] :
            return

        # no callbacks for private keys
        if  key[0] == '_' and d['private'] :
            return

        # key getter overwrites results from all_getter
        all_getter = d['getter']
        key_getter = d['attributes'][key]['getter']


        # # Note that attributes have a time-to-live (ttl).  If a _attributes_i_get
        # # operation is attempted within 'time-of-last-update + ttl', the operation
        # # is not triggering backend getter hooks, to avoid trashing (hooks are
        # # expected to be costly).  The force flag set to True will request to call
        # # registered getter hooks even if ttl is not yet expired.
        #
        # # For example, job.wait() will update the plugin level state to 'Done',
        # # but the cached job.state attribute will remain 'New' as the plugin does
        # # not push the state change upward
        #
        # age = self._attributes_t_get_age (key)
        # ttl = d['attributes'][key]['ttl']
        #
        # if age < ttl :
        #     return


        # get the value from the native getter (from the backend), and
        # get it via the attribute getter.  The getter will not call
        # attrib setters or callbacks, due to the recursion guard.
        #
        # If both are present, we can ignore *one* exception.  If one
        # is present, exceptions are not ignored.
        #
        # always raise and lower the recursion shield.
        retries = 1
        if  all_getter and key_getter : retries = 2

        if  all_getter :
            try :
                d['attributes'][key]['recursion'] = True
                val=all_getter (key)
                d['attributes'][key]['value'] = val
            except Exception:
                retries -= 1
                if not retries : raise
            finally :
              d['attributes'][key]['recursion'] = False

        if  key_getter :
            try :
                d['attributes'][key]['recursion'] = True
                val=key_getter ()
                d['attributes'][key]['value'] = val
            except Exception:
                retries -= 1
                if not retries : raise
            finally :
                d['attributes'][key]['recursion'] = False



    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str)
    @rus.returns (rus.list_of (str))
    def _attributes_t_call_lister (self) :
        """
        This internal function is not to be used by the consumer of this API.

        It triggers the lister callback, to signal that the attribute list
        is about to be accesses and should be updated as needed.
        """

        # make sure interface is ready to use.
        d = self._attributes_t_init ()

        # avoid recursion
        if  d['recursion'] :
            return

        lister = d['lister']

        if lister :

            # the lister is simply called, and it is expected that it internally
            # adds/removes attributes as needed.
            #
            # always raise and lower the recursion shield
            try :
                d['recursion'] = True
                lister ()
            finally :
                d['recursion'] = False



    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  int,
                  callable)
    @rus.returns (rus.anything)
    def _attributes_t_call_caller (self, key, id, cb) :
        """
        This internal function is not to be used by the consumer of this API.

        It triggers the invocation of any registered caller function, usually
        after an 'add_callback()' call.
        """

        # make sure interface is ready to use.
        d = self._attributes_t_init (key)

        # avoid recursion
        if  d['recursion'] :
            return

        # no callbacks for private keys
        if  key[0] == '_' and d['private'] :
            return

        caller = d['caller']

        if  caller :

            # the caller is simply called, and it is expected that it internally
            # adds/removes callbacks as needed
            #
            # always raise and lower the recursion shield
            try :
                d['recursion'] = True
                return caller (key, id, cb)
            finally :
                d['recursion'] = False

        return



    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str)
    @rus.returns (str)
    def _attributes_t_underscore (self, key, force=False) :
        """
        This internal function is not to be used by the consumer of this API.

        The method accepts a CamelCased word, and translates that into
        'under_score' notation -- IFF 'camelcasing' is set

        Kudos: http://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-camel-case
        """

        # make sure interface is ready to use
        d = self._attributes_t_init ()


        if  force or d['camelcasing'] :
            temp = Attributes._camel_case_regex_1.sub(r'\1_\2', key)
            return Attributes._camel_case_regex_2.sub(r'\1_\2', temp).lower()
        else :
            return key



    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str)
    @rus.returns (rus.anything)
    def _attributes_t_conversion (self, key, val) :
        """
        This internal function is not to be used by the consumer of this API.

        The method checks a given attribute value against the attribute's
        flags, and performs some simple type conversion as needed.  Also, the
        method will restore a 'None' value to the attribute's default value.

        A deriving class can add additional value checks for attributes by
        calling :func:`_attributes_add_check` (key, check).
        """

        # make sure interface is ready to use.  We do not check for keys, that
        # needs to be done in the calling method.  For example, on 'set', type
        # conversions will be performed, but the key will not exist previously.
        d = self._attributes_t_init ()

        # if the key is not known
        if  not key in d['attributes'] :
            # cannot handle unknown attributes.  Attributes which have been
            # registered earlier will be fine, as they have type information.
            return val

        # check if a value is given.  If not, revert to the default value
        # (if available)
        if  val == None :
            if 'default' in d['attributes'][key] :
                val = d['attributes'][key]['default']


        # perform flavor and type conversion
        val = self._attributes_t_conversion_flavor (key, val)

        # apply all value checks on the conversion result
        for check in d['attributes'][key]['checks'] :
            ret = check (key, val)
            if  ret != True :
                raise se.BadParameter ("attribute value %s is not valid: %s"  %  (key, ret))

        # aaaand done
        return val


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.anything)
    @rus.returns (rus.anything)
    def _attributes_t_conversion_flavor (self, key, val) :
        """
        This internal function is not to be used by the consumer of this API.
        This method should ONLY be called by _attributes_t_conversion!
        """
        # FIXME: there are possibly nicer and more reversible ways to
        #        convert the flavors...

        # easiest conversion of them all... ;-)
        if  val == None :
            return None

        # make sure interface is ready to use.
        d = self._attributes_t_init (key)

        # check if we need to serialize a list into a scalar
        f = d['attributes'][key]['flavor']
        t = d['attributes'][key]['type']
        if  f == ANY :
            # leave it alone
            return val

        elif  f == VECTOR :
            # we want a vector
            if  isinstance (val, list) :
                # val is already vec - apply type conversion on all elems
                ret = []
                for elem in val :
                    ret.append (self._attributes_t_conversion_type (key, elem))
                return ret
            else :

                # need to create vec from scalar
                if  isinstance (val, str) :

                    # for string values, we split on white spaces and type-convert
                    # all elements
                    vec = val.split ()
                    ret = []
                    for element in vec :
                        ret.append (self._attributes_t_conversion_type (key, element))
                    return ret
                else :
                    # all non-string types are interpreted as only element of
                    # a single-member list
                    return [self._attributes_t_conversion_type (key, val)]



        elif f == DICT :
            # we want a dict
            if  isinstance (val, dict) :
                # done :-)
                return val

            if  isinstance (val, list) :
                # if target type is a dict, we parse the values and
                # split on '=', creating the dict.  That will only work for
                # string typed values
                out = {}
                for elem in val :
                    (key, val) = str(elem).split ('=', 1)
                    out[key] = val
                return out

            if  isinstance (val, str) :
                # we assume a colon or comma separated list of = separated
                # key/value pairs
                elems = val.split (':')
                out   = {}
                if  len(elems) == 1 :
                    elems = val.split (',')

                for elem in elems :
                    (key, val) = str(elem).strip ().split ('=', 1)
                    out[key] = val
                return out


            # can't handle any other types...


        elif f == SCALAR :
            # we want a scalar

            if  t == ANY :
                # no need to do anything, really
                return val

            if isinstance (val, list) :
                # need to create scalar from vec
                if len (val) > 1 :
                    # if the list has more than one element, we use an intermediate
                    # string representation of the list before converting to a scalar
                    # This is the weakest conversion mode, and will not very
                    # likely yield useful results.
                    tmp = ""
                    for i in val :
                        tmp += str(i) + " "
                    return self._attributes_t_conversion_type (key, tmp)
                elif len (val) == 1 :
                    # for single element lists, we simply use the one element as
                    # scalar value
                    return self._attributes_t_conversion_type (key, val[0])
                else :
                    # no value in list
                    return None
            else :
                # scalar is already scalar, just do type conversion
                return self._attributes_t_conversion_type (key, val)


        # we should never get here...
        raise se.BadParameter ("Cannot evaluate attribute flavor (%s) : %s"  %  (key, str(f)))


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.anything)
    @rus.returns (rus.anything)
    def _attributes_t_conversion_type (self, key, val) :
        """
        This internal function is not to be used by the consumer of this API.
        This method should ONLY be called by _attributes_t_conversion!
        """

        # make sure interface is ready to use.
        d = self._attributes_t_init (key)

        # oh python, how about a decent switch statement???
        t   = d['attributes'][key]['type']
        ret = None
        try :
            # FIXME: add time/date conversion to/from string
            if   t == ANY    : return        val
            elif t == INT    : return int   (val)
            elif t == FLOAT  : return float (val)
            elif t == BOOL   : return bool  (val)
            elif t == STRING : return str   (val)
            else             : return        val
        except ValueError as e:
            raise se.BadParameter ("attribute value %s has incorrect type: %s" %  (key, val)) \
                  from e

        # we should never get here...
        raise se.BadParameter ("Cannot evaluate attribute type (%s) : %s"  %  (key, str(t)))

    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str)
    @rus.returns (str)
    def _attributes_t_wildcard2regex (self, pattern) :
        """
        This internal function is not to be used by the consumer of this API.

        This method converts a string containing POSIX shell wildcards into
        a regular expression with the same matching properties::

            *       -> .*
            ?       -> .
            {a,b,c} -> (a|b|c)
            [abc]   -> [abc]
            [!abc]  -> [^abc]
        """

        re = pattern

        re.replace ('*', '.*')  # set of characters
        re.replace ('?', '.' )  # single character

        # character classes
        match = re.find ('[', 0)
        while match >= 0 :
            if  re[match + 1] == '!' :
                re[match + 1] =  '^'
            match = re.find ('[', match + 1)

        # find opening { and closing }
        first = re.find ('{', 0)
        last  = re.find ('}', first + 1)

        # while match
        while first >= 0 and last  >= 0 :
            # replace with ()
            re[first] = '('
            re[last] = '('

            # also, replace all ',' with with '|' for alternatives
            comma = re.find (',', first)
            while comma >= 0 :
                re[comma] = '|'
                comma = re.find (',', comma + 1)

            # done - find next bracket pair...
            first = re.find ('{', last + 1)
            last  = re.find ('}', first + 1)

        return re


    # --------------------------------------------------------------------------
    #
    def _attributes_t_get_age (self, key) :
        """ get the age of the attribute, i.e. seconds.microseconds since last set """

        # make sure interface is ready to use.
        d    = self._attributes_t_init (key)
        last = d['attributes'][key]['last']
        age  = now() - last

        return (age.microseconds + (age.seconds + age.days * 24 * 3600) * 1e6) / 1e6



    # --------------------------------------------------------------------------
    #
    # internal interface
    #
    # This internal interface is used by the public interfaces (dict,
    # properties, GFD.90).  We assume that CamelCasing and under_scoring is
    # sorted out before this internal interface is called.  All other tests,
    # verifications, and conversion are done here though.
    #
    # Naming: _attributes_i_*
    #
    def _attributes_i_set (self, key, val=None, force=False, flow=_DOWN) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        See :func:`set_attribute` (key, val) for details.

        New value checks can be added dynamically, and per attribute, by calling
        :func:`_attributes_add_check` (key, callable).

        Some internal methods can set the 'force' flag, and will be able to set
        attributes even in ReadOnly mode.  That is, for example, used for getter
        hooks.  Note that the Final flag will be honored even if Force is set,
        and will result in the set request being ignored.
        """

        # make sure interface is ready to use
        d = self._attributes_t_init ()

        # if the key is not known
        if not key in d['attributes'] :

            if key[0] == '_' and d['private'] :
                # if the set is private, we can register the new key.  It
                # won't have any callbacks at this point.
                self._attributes_register (key, None, ANY, ANY, WRITEABLE, EXTENDED, flow=flow)

            elif flow==self._UP or d['extensible'] :
                # if the set is extensible, we can register the new key.  It
                # won't have any callbacks at this point.
                self._attributes_register (key, None, ANY, ANY, WRITEABLE, EXTENDED, flow=flow)

            elif force :
                # someone *really* wants this attrib to be set...
                self._attributes_register (key, None, ANY, SCALAR, WRITEABLE, EXTENDED, flow=flow)

            else :
                # we cannot add new keys on non-extensible / non-private sets
                raise se.IncorrectState ("attribute set is not extensible/private (key %s)" %  key)


        # known attribute
        else :

            # check if we are allowed to change the attribute - complain if not.
            # Also, simply ignore write attempts to finalized keys.
            if 'mode' in  d['attributes'][key] :

                mode = d['attributes'][key]['mode']

                if FINAL == mode :
                    return

                elif READONLY == mode :
                    if not force :
                        raise se.BadParameter ("attribute %s is not writeable" %  key)


        # permissions are confirmed, set the attribute with conversion etc.

        # NOTE: keep the original value around for the setter
        orig_val = val

        # apply any attribute conversion
        val = self._attributes_t_conversion (key, val)

        # make sure the key's value entry exists
        if not 'value' in d['attributes'][key] :
            d['attributes'][key]['value'] = None

        # only once an attribute is explicitly set, it 'exists' for the purpose
        # of the 'attribute_exists' call, and the key iteration
        d['attributes'][key]['exists'] = True

        # # only actually change the attribute when the new value differs --
        # # and only then invoke any callbacks and hooked setters
        # if val != d['attributes'][key]['value'] :
        #
        # NOTE: this check is disabled now: we certainly want to update 'last',
        # and IMHO that should also imply a notification call, etc.  FWIW, the
        # spec is inconclusive here.
        #
        # if val != d['attributes'][key]['value'] :


        d['attributes'][key]['value'] = val
        d['attributes'][key]['last']  = now ()

        if flow==self._DOWN :
            # NOTE: we use the orig_val here, to make the environment hooks
            # happy which we introduced for BJ backward compatibility (FIXME)
            self._attributes_t_call_setter (key, orig_val)

        self._attributes_t_call_cb (key, val)


    # --------------------------------------------------------------------------
    #
    def _attributes_i_get (self, key, flow) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        see :func:`get_attribute` (key) for details.

        Note that this method is not performing any checks or conversions --
        those are all performed when *setting* an attribute.  So, any attribute
        flags (type, mode, flavor) are evaluated on setting, not on getting.
        This implementation does not account for resulting race conditions
        (changing attribute types after setting for example) -- but the public
        API does not allow that anyway.
        """

        # make sure interface is ready to use
        d = self._attributes_t_init (key)

        if flow == self._DOWN :
            self._attributes_t_call_getter (key)

        if 'value' in d['attributes'][key] :
            return d['attributes'][key]['value']

        if 'default' in d['attributes'][key] :
            return d['attributes'][key]['default']

        return None



    # --------------------------------------------------------------------------
    #
    def _attributes_i_list (self, ext=True, priv=False, CamelCase=True, flow=_DOWN) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        see :func:`list_attributes` () for details.

        Note that registration alone does not qualify for listing.  If 'ext' is
        True (default),extended attributes are listed, too.
        """

        # make sure interface is ready to use
        d = self._attributes_t_init ()

        # call list hooks to update state for listing
        self._attributes_t_call_lister ()

        ret    = []
        for key in sorted(d['attributes'].keys()) :
            if d['attributes'][key]['mode'] != ALIAS :
                if d['attributes'][key]['exists'] :

                    e = d['attributes'][key]['extended']
                    p = d['attributes'][key]['private']
                    k = key

                    if CamelCase :
                        k = d['attributes'][key]['camelcase']

                    if e and ext :
                        if p and priv :
                            ret.append (k)
                        elif not p :
                            ret.append (k)
                    elif not e :
                        if p and priv :
                            ret.append (k)
                        elif not p :
                            ret.append (k)

        return ret


    # --------------------------------------------------------------------------
    #
    def _attributes_i_find (self, pattern, flow) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        see :func:`find_attributes` (pattern) for details.
        """

        # FIXME: wildcard-to-regex

        # make sure interface is ready to use
        d = self._attributes_t_init ()


        # separate key and value pattern
        p_key  = ""    # string pattern
        p_val  = ""    # string pattern
        pc_key = None  # compiled pattern
        pc_val = None  # compiled pattern


        if pattern[0] == '=' :
            # no key pattern present, only grep on values
              p_val = self._attributes_t_wildcard2regex (pattern[1:])
        else :
          p = re.compile (r'[^\]=')
          tmp = p.split (pattern, 2)  # only split on first '='

          if len (tmp) >  0 :
              # at least one elem: only key pattern present
              p_key = self._attributes_t_wildcard2regex (tmp[0])

          if len (tmp) == 2 :
              # two elems: val pattern is also present
              p_val = self._attributes_t_wildcard2regex (tmp[1])

        # compile the found pattern
        if len (p_key) : pc_key = re.compile (p_key)
        if len (p_val) : pc_val = re.compile (p_val)

        # now dig out matching keys. List hooks are triggered in
        # _attributes_i_list(flow).
        matches = []
        for key in self._attributes_i_list (flow=flow) :
            val = str(self._attributes_i_get (key, flow=flow))

            if ( (pc_key == None) or pc_key.search (key) ) and \
               ( (pc_val == None) or pc_val.search (val) )     :
                matches.append (key)

        return matches


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.one_of (_UP, _DOWN))
    @rus.returns (bool)
    def _attributes_i_exists (self, key, flow) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        see :func:`attribute_exists` (key) for details.

        Registered keys which have never been explicitly set to a value do not
        exist for the purpose of this call.
        """

        # make sure interface is ready to use
        d = self._attributes_t_init ()

        # check if we know about that attribute
        if key in d['attributes'] :
            if 'exists' in d['attributes'][key] :
                if  d['attributes'][key]['exists'] :
                    return True

        return False


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.one_of (_UP, _DOWN))
    @rus.returns (bool)
    def _attributes_i_is_extended (self, key, flow) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        This method will check if the given key is extended, i.e. was registered
        on the fly, vs. registered explicitly.

        This method is not used by, and not exposed via the public API, yet.
        """

        # make sure interface is ready to use
        d = self._attributes_t_init (key)

        return d['attributes'][key]['extended']


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.one_of (_UP, _DOWN))
    @rus.returns (bool)
    def _attributes_i_is_private (self, key, flow) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        This method will check if the given key is private, i.e. starts with an
        underscore and 'allow_private' is enabled.

        This method is not used by, and not exposed via the public API, yet.
        """

        # make sure interface is ready to use
        d = self._attributes_t_init (key)

        return d['attributes'][key]['private']


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.one_of (_UP, _DOWN))
    @rus.returns (bool)
    def _attributes_i_is_readonly (self, key, flow) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        see L{attribute_is_readonly} (key) for details.

        This method will check if the given key is readonly, i.e. cannot be
        'set'.  The call will also return 'True' if the attribute is final
        """

        # make sure interface is ready to use
        d = self._attributes_t_init (key)

        # check if we know about that attribute
        if  d['attributes'][key]['mode'] == FINAL or \
            d['attributes'][key]['mode'] == READONLY :
            return True

        return False


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.one_of (_UP, _DOWN))
    @rus.returns (bool)
    def _attributes_i_is_writeable (self, key, flow) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        see :func:`attribute_is_writable` (key) for details.

        This method will check if the given key is writeable - i.e. not readonly.
        """

        return not self._attributes_i_is_readonly (key, flow=flow)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.one_of (_UP, _DOWN))
    @rus.returns (bool)
    def _attributes_i_is_removable (self, key, flow) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        see :func:`attribute_is_removable` (key) for details.

        'True' if the attrib is writeable and Extended.
        """

        if self._attributes_i_is_writeable (key, flow=flow) and \
           self._attributes_i_is_extended  (key, flow=flow)     :
            return True

        return False

    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.one_of (_UP, _DOWN))
    @rus.returns (bool)
    def _attributes_i_is_vector (self, key, flow) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        see :func:`attribute_is_vector` (key) for details.
        """

        # make sure interface is ready to use
        d = self._attributes_t_init (key)

        # check if we know about that attribute
        if  d['attributes'][key]['flavor'] == VECTOR :
            return True

        return False


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.one_of (_UP, _DOWN))
    @rus.returns (bool)
    def _attributes_i_is_final (self, key, flow) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        This method will query the 'final' flag for an attribute, which signals
        that the attribute will never change again.

        This method is not used by, and not exposed via the public API, yet.
        """

        # make sure interface is ready to use
        d = self._attributes_t_init (key)

        if FINAL == d['attributes'][key]['mode'] :
             return True

        # no final flag found -- assume non-finality!
        return False


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  callable,
                  rus.one_of (_UP, _DOWN))
    @rus.returns (int)
    def _attributes_i_add_cb (self, key, cb, flow) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        see :func:`add_callback` (key, cb) for details.
        """

        # make sure interface is ready to use
        d = self._attributes_t_init (key)

        d['attributes'][key]['callbacks'].append (cb)

        id = len (d['attributes'][key]['callbacks']) - 1

        if flow==self._DOWN :
            self._attributes_t_call_caller (key, id, cb)

        return id


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (int),
                  rus.one_of (_UP, _DOWN))
    @rus.returns (rus.nothing)
    def _attributes_i_del_cb (self, key, id=None, flow=_DOWN) :
        """
        This internal method should not be explicitly called by consumers of
        this API, but is indirectly used via the different public interfaces.

        see :func:`remove_callback` (key, cb) for details.
        """

        # make sure interface is ready to use
        d = self._attributes_t_init (key)

        if flow==self._DOWN :
            self._attributes_t_call_caller (key, id, None)

        # id == None: remove all callbacks
        if not id :
            d['attributes'][key]['callbacks'] = []
        else :
            if len (d['attributes'][key]['callbacks']) < id :
                raise se.BadParameter ("invalid callback cookie for attribute %s"  %  key)
            else :
                # do not pop from list, that would invalidate the id's!
                d['attributes'][key]['callbacks'][id] = None



    # --------------------------------------------------------------------------
    #
    # This part of the interface is primarily for use in deriving classes, which
    # thus provide the Attributes interface.
    #
    # Keys should be provided as CamelCase (only relevant if camelcasing is
    # set).
    #
    # Naming: _attributes_*
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (rus.optional (rus.anything)),
                  rus.optional (rus.one_of (ANY, URL, INT, FLOAT, STRING, BOOL, ENUM, TIME)),
                  rus.optional (rus.one_of (ANY, SCALAR, VECTOR, DICT)),
                  rus.optional (rus.one_of (READONLY, WRITEABLE, ALIAS, FINAL)),
                  rus.optional (rus.one_of (bool, EXTENDED)),
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_register (self, key,      default=None, typ=ANY, flavor=ANY,
                              mode=WRITEABLE, ext=False,    flow=_DOWN) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        Register a new attribute.

        This function ignores extensible, private, final and readonly flags.  It
        can also be used to re-register an existing attribute with new
        properties -- the old attribute value, callbacks etc. will be lost
        though.  Using this call that way may result in confusing behaviour on
        the public API level.
        """
        # FIXME: check for valid mode and flavor settings

        # make sure interface is ready to use
        d = self._attributes_t_init ()

        priv = False
        if d['private'] and key[0] == '_' :
            priv = True

        # we expect keys to be registered as CamelCase (in those cases where
        # that matters).  But we store attributes in 'under_score' version.
        us_key = self._attributes_t_underscore (key)

        # retain old values
        val    = default
        exists = False

        if  default != None :
            exists = True

        if us_key in d['attributes'] :
            val    = d['attributes'][us_key]['value']
            exists = True

        # register the attribute and properties
        d['attributes'][us_key]                 = {}
        d['attributes'][us_key]['value']        = val     # initial value
        d['attributes'][us_key]['default']      = default # default value
        d['attributes'][us_key]['type']         = typ     # int, float, enum, ...
        d['attributes'][us_key]['exists']       = exists  # no value set, yet?
        d['attributes'][us_key]['flavor']       = flavor  # scalar / vector
        d['attributes'][us_key]['mode']         = mode    # readonly / writeable / final
        d['attributes'][us_key]['extended']     = ext     # is an extended attribute
        d['attributes'][us_key]['private']      = priv    # is a  private attribute
        d['attributes'][us_key]['camelcase']    = key     # keep original key name
        d['attributes'][us_key]['underscore']   = us_key  # keep under_scored name
        d['attributes'][us_key]['enums']        = []      # list of valid enum values
        d['attributes'][us_key]['checks']       = []      # list of custom value checks
        d['attributes'][us_key]['callbacks']    = []      # list of callbacks
        d['attributes'][us_key]['recursion']    = False   # recursion check for callbacks
        d['attributes'][us_key]['setter']       = None    # custom attribute setter
        d['attributes'][us_key]['getter']       = None    # custom attribute getter
        d['attributes'][us_key]['last']         = never   # time of last refresh (never)
        d['attributes'][us_key]['ttl']          = 0.0     # refresh delay (none)

        # for enum types, we add a value checker
        if typ == ENUM :

            def _enum_check (key, val) :
                if None == val  :
                    # None is always allowed
                    return True

                us_key = self._attributes_t_underscore (key)
                d      = self._attributes_t_init       (us_key)

                vals   = d['attributes'][us_key]['enums']

                # check if there is anything to check
                if not vals :
                    return True

                # value must be one of allowed enums
                if val in vals :
                        return True

                # Houston, we got a problem...
                msg = "incorrect value (%s) for Enum typed attribute (%s)." \
                      "Allowed values: %s"  %  (str(val), key, str(vals))
                raise se.BadParameter (msg)

            self._attributes_add_check (key, _enum_check, flow=flow)



    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  str,
                  rus.one_of (_UP, _DOWN))
    @rus.returns (rus.nothing)
    def _attributes_register_deprecated (self, key, alias, flow=_DOWN) :
        """
        Often enough, there is the need to use change attribute names.  It is
        good practice to not simply rename attributes, and thus effectively
        remove old ones, as that is likely to break existing code.  Instead, new
        names are added, and old names are kept for a certain time for backward
        compatibility.  To support migration to the new names, the old names
        should be marked as 'deprecated' though - which can be configured to
        print a warning whenever an old, deprecated attribute is used.

        This method allows to register such deprecated attribute names.  They
        can thus be used just like new ones, and in fact are implemented as
        aliases to the new ones -- but they will print a deprecated warning on
        usage.

        The first parameter is the old name of the attribute, the second
        parameter is the aliased new name.  Note that the new name needs to be
        registered before (via :class:`saga._attributes_register`)::

            # old code:
            self._attributes_register ('apple', 'Appel', STRING, SCALAR, WRITEABLE)

            # new code
            self._attributes_register ('fruit', 'Appel', STRING, SCALAR, WRITEABLE)
            self._attributes_register_deprecated ('apple', 'fruit')

        In some cases, you may want to deprecate a variable and not replace it
        with a new one.  In order to keep this interface simple, this can be
        achieved via::

            # new code
            self._attributes_register ('deprecated_apple', 'Appel', STRING, SCALAR, WRITEABLE)
            self._attributes_register_deprecated ('apple', 'deprecated_apple')

        This way, the user will either see a warning, or has to explicitly use
        'deprecated_apple' as attribute name -- which should be warning enough,
        at least for the programmer ;o)
        """

        # we expect keys to be registered as CamelCase (in those cases where
        # that matters).  But we store attributes in 'under_score' version.
        us_alias = self._attributes_t_underscore (alias)
        us_key   = self._attributes_t_underscore (key)

        # make sure interface is ready to use
        # This check will throw if 'alias' was not registered before.
        d = self._attributes_t_init (us_alias)

        # remove any old instance of this attribute
        if us_key in  d['attributes'] :
            self._attributes_unregister (us_key, flow=flow)

        # register the attribute and properties
        d['attributes'][us_key]               = {}
        d['attributes'][us_key]['mode']       = ALIAS      # alias
        d['attributes'][us_key]['alias']      = us_alias   # aliased var
        d['attributes'][us_key]['camelcase']  = key        # keep original key name
        d['attributes'][us_key]['underscore'] = us_key     # keep under_scored name



    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.one_of (_UP, _DOWN))
    @rus.returns (rus.nothing)
    def _attributes_unregister (self, key, flow) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        Unregister an attribute.

        This function ignores the extensible, private, final and readonly flag,
        and is supposed to be used by derived classes, not by the consumer of
        the API.

        Note that unregistering is different from setting the value to 'None' --
        all meta information about the attribute will be removed.  Further
        attempts to access the attribute from the public API will result in an
        DoesNotExist exception.  This method should be used sparingly -- in
        fact, GFD.90 requires final attributes to stay around forever (frozen).
        """

        # make sure interface is ready to use
        us_key = self._attributes_t_underscore (key)
        d      = self._attributes_t_init       (us_key)

        # if the attribute exists, purge it
        if us_key in d['attributes'] :
            del (d['attributes'][us_key])


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.one_of (_UP, _DOWN))
    @rus.returns (rus.nothing)
    def _attributes_remove (self, key, flow) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        Remove an extended an attribute.

        This function allows to safely remove any attribute which is 'private'
        or 'extended' and has write permissions.
        """

        # make sure interface is ready to use
        us_key = self._attributes_t_underscore (key)
        d      = self._attributes_t_init       (us_key)

        if self._attributes_i_is_removable (key, flow=flow) :
            del (d['attributes'][us_key])


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (rus.list_of (rus.anything)),
                  rus.optional (rus.one_of  (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_set_enums (self, key, enums=None, flow=_DOWN) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        Specifies the set of allowed values for Enum typed attributes.  If not
        set, or if list is None, any values are allowed.
        """

        us_key = self._attributes_t_underscore (key)
        d      = self._attributes_t_init       (us_key)

        d['attributes'][us_key]['enums'] = enums


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  rus.optional (bool),
                  rus.optional (callable),
                  rus.optional (callable),
                  rus.optional (callable),
                  rus.optional (callable),
                  rus.optional (rus.one_of  (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_extensible (self, e=True,
                                getter=None, setter=None,
                                lister=None, caller=None,
                                flow=_DOWN) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        Allow (or forbid) the on-the-fly creation of new attributes.  Note that
        this method also allows to *remove* the extensible flag -- that leaves
        any previously created extended attributes untouched, but just prevents
        the creation of new extended attributes.
        """

        d = self._attributes_t_init ()
        d['extensible'] = e

        if getter : self._attributes_set_global_getter (getter, flow=flow)
        if setter : self._attributes_set_global_setter (setter, flow=flow)
        if lister : self._attributes_set_global_lister (lister, flow=flow)
        if caller : self._attributes_set_global_caller (caller, flow=flow)



    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  rus.optional (bool),
                  rus.optional (rus.one_of  (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_allow_private (self, p=True, flow=_DOWN) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        Allow (or forbid) the on-the-fly creation of private attributes
        (starting with underscore).  Note that this method also allows to
        *remove* the respective flag -- that leaves any previously created
        private attributes untouched, but just prevents the creation of new
        private attributes.
        """

        d = self._attributes_t_init ()
        d['private'] = p


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  rus.optional (bool),
                  rus.optional (rus.one_of  (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_camelcasing (self, c=True, flow=_DOWN) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        Use 'CamelCase' for dict entries and the GFD.90 API, but 'under_score'
        for properties.

        Note that we do not provide an option to turn CamelCasing off - once it
        is turned on, it stays on -- otherwise we would loose attributes...
        """

        d = self._attributes_t_init ()
        d['camelcasing'] = c


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  'Attributes',
                  rus.optional (rus.one_of  (_UP, _DOWN)))
    @rus.returns ('Attributes')
    def _attributes_deep_copy (self, other, flow=_DOWN) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        This method can be used to make sure that deep copies of derived classes
        are also deep copies of the respective attributes.  In accordance with
        GFD.90, the deep copy will ignore callbacks.  It will copy checks
        though, as the assumption is that value constraints stay valid.

        Note that we don't copy private keys.
        """


        # make sure interface is ready to use
        d = self._attributes_t_init ()

        other_d = {}
        orig_d  = other._attributes_t_init ()

        # for some reason, deep copy won't work on the 'attributes' dict, so we
        # do it manually.  Use the list copy c'tor to copy list elements.
        other_d['camelcasing']  = d['camelcasing']
        other_d['attributes']   = d['attributes']
        other_d['extensible']   = d['extensible']
        other_d['private']      = d['private']
        other_d['camelcasing']  = d['camelcasing']
        other_d['recursion']    = d['recursion']
        other_d['getter']       = d['setter']
        other_d['setter']       = d['setter']
        other_d['lister']       = d['lister']
        other_d['caller']       = d['caller']

        other_d['attributes'] = {}

        for key in d['attributes'] :
            other_d['attributes'][key] = {}
            other_d['attributes'][key]['default']      =       d['attributes'][key]['default']
            other_d['attributes'][key]['exists']       =       d['attributes'][key]['exists']
            other_d['attributes'][key]['type']         =       d['attributes'][key]['type']
            other_d['attributes'][key]['flavor']       =       d['attributes'][key]['flavor']
            other_d['attributes'][key]['mode']         =       d['attributes'][key]['mode']
            other_d['attributes'][key]['extended']     =       d['attributes'][key]['extended']
            other_d['attributes'][key]['private']      =       d['attributes'][key]['private']
            other_d['attributes'][key]['camelcase']    =       d['attributes'][key]['camelcase']
            other_d['attributes'][key]['underscore']   =       d['attributes'][key]['underscore']
            other_d['attributes'][key]['enums']        = list (d['attributes'][key]['enums'])
            other_d['attributes'][key]['checks']       = list (d['attributes'][key]['checks'])
            other_d['attributes'][key]['callbacks']    = list (d['attributes'][key]['callbacks'])
            other_d['attributes'][key]['recursion']    =       d['attributes'][key]['recursion']
            other_d['attributes'][key]['setter']       =       d['attributes'][key]['setter']
            other_d['attributes'][key]['getter']       =       d['attributes'][key]['getter']
            other_d['attributes'][key]['last']         =       d['attributes'][key]['last']
            other_d['attributes'][key]['ttl']          =       d['attributes'][key]['ttl']

            if d['attributes'][key]['private' ] and key in orig_d['attributes'] :
                # don't copy private keys
                other_d['attributes'][key] = orig_d['attributes'][key]

            else :
                other_d['attributes'][key]['value']  = copy.deepcopy (d['attributes'][key]['value'])

        # set the new dictionary as state for copied class
        _AttributesBase.__setattr__ (other, '_d', other_d)

        return other


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  ('Attributes', dict))
    @rus.returns ('Attributes')
    def __deepcopy__ (self, memo) :
        other = Attributes ()
        return self._attributes_deep_copy (other)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  rus.optional (str),
                  rus.optional (rus.one_of  (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_dump (self, msg=None, flow=_DOWN) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        Debugging dump to stdout.
        """

        # make sure interface is ready to use
        d = self._attributes_t_init ()


        keys_all = sorted (d['attributes'].keys ())

        print("---------------------------------------")
        print(str (type (self)))

        if msg :
            print("---------------------------------------")
            print(msg)

        print("---------------------------------------")
        print(" %-30s : %s"  %  ("Extensible"  , d['extensible']))
        print(" %-30s : %s"  %  ("Private"     , d['private']))
        print(" %-30s : %s"  %  ("CamelCasing" , d['camelcasing']))
        print("---------------------------------------")

        keys_exist = []
        for key in keys_all :
            if  'exists' in d['attributes'][key] and \
                d['attributes'][key]['exists']   :
                keys_exist.append (key)

        print("'Registered' attributes")
        for key in keys_all :
            if key not in keys_exist :
                if not  d['attributes'][key]['mode'] == ALIAS and \
                   not  d['attributes'][key]['extended'] :
                    print(" %-30s [%6s, %6s, %9s, %3d]: %s"  % \
                             (d['attributes'][key]['camelcase'],
                              d['attributes'][key]['type'],
                              d['attributes'][key]['flavor'],
                              d['attributes'][key]['mode'],
                          len(d['attributes'][key]['callbacks']),
                              d['attributes'][key]['value']
                              ))

        print("---------------------------------------")

        print("'Existing' attributes")
        keys_exist.sort ()
        for key in keys_exist :
            if not  d['attributes'][key]['mode'] == ALIAS :
                print(" %-30s [%6s, %6s, %9s, %3d]: %s"  % \
                         (d['attributes'][key]['camelcase'],
                          d['attributes'][key]['type'],
                          d['attributes'][key]['flavor'],
                          d['attributes'][key]['mode'],
                      len(d['attributes'][key]['callbacks']),
                          d['attributes'][key]['value']
                          ))

        print("---------------------------------------")

        print("'Extended' attributes")
        for key in keys_all :
            if key not in keys_exist :
                if not  d['attributes'][key]['mode'] == ALIAS and \
                        d['attributes'][key]['extended'] :
                    print(" %-30s [%6s, %6s, %9s, %3d]: %s"  % \
                             (d['attributes'][key]['camelcase'],
                              d['attributes'][key]['type'],
                              d['attributes'][key]['flavor'],
                              d['attributes'][key]['mode'],
                          len(d['attributes'][key]['callbacks']),
                              d['attributes'][key]['value']
                              ))

        print("---------------------------------------")

        print("'Deprecated' attributes (aliases)")
        for key in keys_all :
            if key not in keys_exist :
                if d['attributes'][key]['mode'] == ALIAS :
                    print(" %-30s [%24s]:  %s"  % \
                             (d['attributes'][key]['camelcase'],
                              ' ',
                              d['attributes'][key]['alias']
                              ))

        print("---------------------------------------")


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (rus.anything),
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_set_final (self, key, val=None, flow=_DOWN) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        This method will set the 'final' flag for an attribute, signalling that
        the attribute will never change again.  The ReadOnly flag is ignored.
        A final value can optionally be provided -- otherwise the attribute is
        frozen with its current value.

        Note that attributes_set_final() will trigger callbacks, even if the
        value was not set, or did not change.
        """

        # make sure interface is ready to use
        us_key = self._attributes_t_underscore (key)
        d      = self._attributes_t_init       (us_key)

        newval = val
        oldval = d['attributes'][us_key]['value']
        if None == newval :
            # freeze at current value unless indicated otherwise
            val = oldval

        # flag as final, and set the final value (this order to avoid races in
        # callbacks)
        d['attributes'][us_key]['mode'] = FINAL
        self._attributes_i_set (us_key, val, flow=flow)

        # callbacks are not invoked if the value did not change -- we take care
        # of that here.
        #
        # if  None == newval or oldval == newval :

        self._attributes_t_call_cb (key, val)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (float),
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_set_ttl (self, key, ttl=0.0, flow=_DOWN) :
        """ set attributes TTL in seconds (float) -- see L{_attributes_i_set} """

        # make sure interface is ready to use.
        us_key = self._attributes_t_underscore (key)
        d      = self._attributes_t_init       (us_key)

        d['attributes'][us_key]['ttl'] = ttl



    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  callable,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_add_check (self, key, check, flow=_DOWN) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        Value checks can be added dynamically, and per attribute.  'callable'
        needs to be a python callable, and will be invoked as::

            callable (key, val)

        Those checks will be invoked whenever a new attribute value is set.  If
        that call then returns 'True', the value is accepted.  Otherwise, the
        value will be considered to be invalid, which results in an exception as
        per above.  'callable' can return a string as error message.
        """

        # make sure interface is ready to use
        us_key = self._attributes_t_underscore (key)
        d = self._attributes_t_init (us_key)

        # register the attribute and properties
        d['attributes'][us_key]['checks'].append (check)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  callable,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_set_getter (self, key, getter, flow=_DOWN) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        The Attribute API makes no assumptions on how attribute values are kept
        up-to-date.  In general, it expects an asynchronous thread to set
        attribute values as needed.  To keep the complexity low for backend
        developers, it also supports the registration of 'setter' and 'getter'
        hooks.  Those are very similar to callbacks, but kind of inversed: where
        frontend callbacks are invoked on backend attribute changes, backend
        hooks are invoked on frontend attribute queries.  They are expected to
        internally trigger state updates.

        For example, on::

            print(c.attrib)

        The attribute getter for the 'attrib' attribute will be invoked.  If for
        that attribute a getter hook is registered, that hook will first query
        the backend for value updates.  After that update has been performed,
        the getter will retrieve the (updated) value.

        Similarly, setter hooks will be invoked *after* the attribute setter
        method, to inform the implementation of the updated attribute value.

        Further, list hooks are invoked before a list or find operation is
        really internally executed, to allow the implementation to updated the
        list of available attributes.

        Note that only one setter/getter/lister method can be registered (for
        setters/getters per key, for listers per class instance).

        Hooks have a different call signature than callbacks::

            setter        (self, value)
            getter        (self)
            global_setter (self, key, value)
            global_setter (self, key)
            global_lister (self)
            global_caller (self, key)

        Note that frequent setter, and even more list and getter calls are very
        common -- the implementation of hooks should consider to cache the
        respective values.
        """

        # make sure interface is ready to use
        us_key = self._attributes_t_underscore (key)
        d      = self._attributes_t_init       (us_key)

        # register the attribute and properties
        d['attributes'][us_key]['getter'] = getter


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  callable,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_set_setter (self, key, setter, flow=_DOWN) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        See documentation of L{_attributes_set_getter } for details.
        """

        # make sure interface is ready to use
        us_key = self._attributes_t_underscore (key)
        d      = self._attributes_t_init       (us_key)

        # register the attribute and properties
        d['attributes'][us_key]['setter'] = setter


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  callable,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_set_global_lister (self, lister, flow) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        See documentation of L{_attributes_set_getter } for details.
        """

        d = self._attributes_t_init ()

        # register the attribute and properties
        d['lister'] = lister


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  callable,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_set_global_caller (self, caller, flow) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        See documentation of :class:`saga._attributes_set_setter ` for details.
        """

        d = self._attributes_t_init ()

        # register the attribute and properties
        d['caller'] = caller


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  callable,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_set_global_getter (self, getter, flow=_DOWN) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        See documentation of L{_attributes_set_getter } for details.
        """

        d = self._attributes_t_init ()

        # register the attribute and properties
        d['getter'] = getter


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  callable,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def _attributes_set_global_setter (self, setter, flow) :
        """
        This interface method is not part of the public consumer API, but can
        safely be called from within derived classes.

        See documentation of L{_attributes_set_getter } for details.
        """

        d = self._attributes_t_init ()

        # register the attribute and properties
        d['setter'] = setter


    # --------------------------------------------------------------------------
    #
    # the GFD.90 attribute interface
    #
    # The GFD.90 interface supports CamelCasing, and thus converts all keys to
    # underscore before using them.
    @rus.takes   ('Attributes',
                  str,
                  rus.anything,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def set_attribute (self, key, val, _flow=_DOWN) :
        """
        set_attribute(key, val)

        This method sets the value of the specified attribute.  If that
        attribute does not exist, DoesNotExist is raised -- unless the attribute
        set is marked 'extensible' or 'private'.  In that case, the attribute is
        created and set on the fly (defaulting to mode=writeable, flavor=Scalar,
        type=ANY, default=None).  A value of 'None' may reset the attribute to
        its default value, if such one exists (see documentation).

        Note that this method is performing a number of checks and conversions,
        to match the value type to the attribute properties (type, mode, flavor).
        Those conversions are not guaranteed to yield the expected result -- for
        example, the conversion from 'scalar' to 'vector' is, for complex types,
        ambiguous at best, and somewhat stupid.  The consumer of the API SHOULD
        ensure correct attribute values.  The conversions are intended to
        support the most trivial and simple use cases (int to string etc).
        Failed conversions will result in an BadParameter exception.

        Attempts to set a 'final' attribute are silently ignored.  Attempts to
        set a 'readonly' attribute will result in an IncorrectState exception
        being raised.

        Note that set_attribute() will trigger callbacks, if a new value
        (different from the old value) is given.
        """

        key    = self._attributes_t_keycheck   (key)
        us_key = self._attributes_t_underscore (key)
        return   self._attributes_i_set        (us_key, val, flow=_flow)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.anything)
    def get_attribute (self, key, _flow=_DOWN) :
        """
        get_attribute(key)

        This method returns the value of the specified attribute.  If that
        attribute does not exist, an DoesNotExist is raised.  It is not an
        error to query an existing, but unset attribute though -- that will
        result in 'None' to be returned (or the default value, if available).
        """

        key    = self._attributes_t_keycheck   (key)
        us_key = self._attributes_t_underscore (key)
        return   self._attributes_i_get        (us_key, _flow)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.list_of (rus.anything),
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def set_vector_attribute (self, key, val, _flow=_DOWN) :
        """
        set_vector_attribute (key, val)

        See also: :func:`saga.Attributes.set_attribute` (key, val).

        As python can handle scalar and vector types transparently, this method
        is in fact not very useful.  For that reason, it maps internally to the
        set_attribute method.
        """

        key    = self._attributes_t_keycheck   (key)
        us_key = self._attributes_t_underscore (key)
        return   self._attributes_i_set        (us_key, val, _flow)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.list_of (rus.anything))
    def get_vector_attribute (self, key, _flow=_DOWN) :
        """
        get_vector_attribute (key)

        See also: :func:`saga.Attributes.get_attribute` (key).

        As python can handle scalar and vector types transparently, this method
        is in fact not very useful.  For that reason, it maps internally to the
        get_attribute method.
        """

        key    = self._attributes_t_keycheck   (key)
        us_key = self._attributes_t_underscore (key)
        return   self._attributes_i_get        (us_key, _flow)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def remove_attribute (self, key, _flow=_DOWN) :
        """
        remove_attribute (key)

        Removing an attribute is actually different from unsetting it, or from
        setting it to 'None'.  On remove, all traces of the attribute are
        purged, and the key will not be listed on
        :func:`saga.Attributes.list_attributes` () anymore.
        """

        key    = self._attributes_t_keycheck   (key)
        us_key = self._attributes_t_underscore (key)
        return   self._attributes_remove       (us_key, _flow)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.list_of (str))
    def list_attributes (self, _flow=_DOWN) :
        """
        list_attributes ()

        List all attributes which have been explicitly set.
        """

        return self._attributes_i_list (flow=_flow)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.list_of (str))
    def find_attributes (self, pattern, _flow=_DOWN) :
        """
        find_attributes (pattern)

        Similar to list(), but also grep for a given attribute pattern.  That
        pattern is of the form 'key=val', where both 'key' and 'val' can contain
        POSIX shell wildcards.  For non-string typed attributes, the pattern is
        applied to a string serialization of the typed value, if that exists.
        """

        return self._attributes_i_find (pattern, _flow)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (bool)
    def attribute_exists (self, key, _flow=_DOWN) :
        """
        attribute_exist (key)

        This method will check if the given key is known and was set explicitly.
        The call will also return 'True' if the value for that key is 'None'.
        """

        key    = self._attributes_t_keycheck   (key)
        us_key = self._attributes_t_underscore (key)
        return self._attributes_i_exists (us_key, _flow)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (bool)
    def attribute_is_readonly (self, key, _flow=_DOWN) :
        """
        attribute_is_readonly (key)

        This method will check if the given key is readonly, i.e. cannot be
        'set'.  The call will also return 'True' if the attribute is final
        """

        key    = self._attributes_t_keycheck   (key)
        us_key = self._attributes_t_underscore (key)
        return self._attributes_i_is_readonly (us_key, _flow)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (bool)
    def attribute_is_writeable (self, key, _flow=_DOWN) :
        """
        attribute_is_writeable (key)

        This method will check if the given key is writeable - i.e. not readonly.
        """

        key    = self._attributes_t_keycheck   (key)
        us_key = self._attributes_t_underscore (key)
        return self._attributes_i_is_writeable (us_key, _flow)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (bool)
    def attribute_is_removable (self, key, _flow=_DOWN) :
        """
        attribute_is_writeable (key)

        This method will check if the given key can be removed.
        """

        key    = self._attributes_t_keycheck   (key)
        us_key = self._attributes_t_underscore (key)
        return self._attributes_i_is_removable (us_key, _flow)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (bool)
    def attribute_is_vector (self, key, _flow=_DOWN) :
        """
        attribute_is_vector (key)

        This method will check if the given attribute has a vector value type.
        """

        key    = self._attributes_t_keycheck   (key)
        us_key = self._attributes_t_underscore (key)
        return self._attributes_i_is_vector (us_key, _flow)


    # --------------------------------------------------------------------------
    #
    # fold the GFD.90 monitoring API into the attributes API
    #
    @rus.takes   ('Attributes',
                  str,
                  callable,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (int)
    def add_callback (self, key, cb, _flow=_DOWN) :
        """
        add_callback (key, cb)

        For any attribute change, the API will check if any callbacks are
        registered for that attribute.  If so, those callbacks will be called
        in order of registration.  This registration function will return an
        id (cookie) identifying the callback -- that id can be used to
        remove the callback.

        A callback is any callable python construct, and MUST accept three
        arguments::

            - STRING key: the name of the attribute which changed
            - ANY    val: the new value of the attribute
            - ANY    obj: the object on which this attribute interface was called

        The 'obj' can be any python object type, but is guaranteed to expose
        this attribute interface.

        The callback SHOULD return 'True' or 'False' -- on 'True', the callback
        will remain registered, and will thus be called again on the next
        attribute change.  On returning 'False', the callback will be
        unregistered, and will thus not be called again.  Returning nothing is
        interpreted as 'False', other return values lead to undefined behavior.

        Note that callbacks will not be called on 'Final' attributes (they will
        be called once as that attribute enters finality).
        """

        key    = self._attributes_t_keycheck   (key)
        us_key = self._attributes_t_underscore (key)
        return self._attributes_i_add_cb (us_key, cb, _flow)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  int,
                  rus.optional (rus.one_of (_UP, _DOWN)))
    @rus.returns (rus.nothing)
    def remove_callback (self, key, id, _flow=_DOWN) :
        """
        remove_callback (key, id)

        This method allows to unregister a previously registered callback, by
        providing its id.  It is not an error to remove a non-existing cb, but
        a valid ID MUST be provided -- otherwise, a BadParameter is raised.

        If no ID is provided (id == None), all callbacks are removed for this
        attribute.
        """

        key    = self._attributes_t_keycheck   (key)
        us_key = self._attributes_t_underscore (key)
        return self._attributes_i_del_cb (us_key, id, _flow)



    # --------------------------------------------------------------------------
    #
    # Python property interface
    #
    # we assume that properties are always used in under_score notation.
    #
    @rus.takes   ('Attributes',
                  str)
    @rus.returns (rus.anything)
    def __getattr__ (self, key) :
        """ see L{get_attribute} (key) for details. """

        key  = self._attributes_t_keycheck (key)
        return self._attributes_i_get      (key, flow=self._DOWN)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str,
                  rus.anything)
    @rus.returns (rus.nothing)
    def __setattr__ (self, key, val) :
        """ see L{set_attribute} (key, val) for details. """

        key  = self._attributes_t_keycheck (key)
        return self._attributes_i_set      (key, val, flow=self._DOWN)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  str)
    @rus.returns (rus.nothing)
    def __delattr__ (self, key) :
        """ see L{remove_attribute} (key) for details. """

        key  = self._attributes_t_keycheck (key)
        return self._attributes_remove     (key, flow=self._DOWN)

    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes')
    @rus.returns (str)
    def __str__  (self) :
        """ return a string representation of all set attributes """

        s = "%s %s" % (type(self), str(self.as_dict()))

        return s


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes',
                  dict)
    @rus.returns (dict)
    def from_dict (self, seed):
        """ set attributes from dict """

        for k,v in seed.items():
            self.set_attribute(k,v)


    # --------------------------------------------------------------------------
    #
    @rus.takes   ('Attributes')
    @rus.returns (dict)
    def as_dict (self) :
        """ return a dict representation of all set attributes """

        d = {}

        for a in self.list_attributes () :
            d[a] = self.get_attribute (a)

        return d


    # --------------------------------------------------------------------------
    #
    # Python dictionary interface, via the DictMixin
    #
    # we assume that keys are always used in under_score notation.
    #
    # --------------------------------------------------------------------------
    #
    def __getitem__ (self, key) :
        return self.get_attribute(key)

    # --------------------------------------------------------------------------
    #
    def __setitem__ (self, key, value) :
        return self.set_attribute (key, value)

    # --------------------------------------------------------------------------
    #
    def __delitem__ (self, key) :
        return self.remove_attribute (key)

    # --------------------------------------------------------------------------
    #
    def keys (self) :
        return self._attributes_i_list (CamelCase=False)

    # --------------------------------------------------------------------------
    #
    def __iter__ (self) :
        return self

    # --------------------------------------------------------------------------
    #
    def __next__ (self) :

        iterlist = self._attributes_i_list (CamelCase=False)

        d = self._attributes_t_init ()

        if  d['_iterpos'] >= len(iterlist) :
            d['_iterpos']  = 0
            raise StopIteration

        if  not len(iterlist) :
            d['_iterpos']  = 0
            raise StopIteration

        d['_iterpos'] += 1

        return iterlist[d['_iterpos']-1]



# ------------------------------------------------------------------------------

# FIXME: add
#   - class metric()
#   - add_metric()
#   - remove_metric()
#   - fire_metric()
#   - list_metrics()
#   - get_metric()
#   - list_callbacks()

# ------------------------------------------------------------------------------



