Example #1
0
    def _generate_derived_type_dictionary (cls, options):
        """
        Generate an iterable of pairs in the form (Python identifier, value) for a new
        type created by C{L{derive_type}}.  Exact pairs should be influenced by
        C{options}, which are C{options} as passed to C{derive_type} plus C{cls} (for
        convenience) and C{new_class_name}.

        This method is not meant to be callable from outside, use C{L{derive_type}} for
        that instead.

        Overriden implementations of this method are recommended but not required to be
        generator functions.  They should generally start like this:

            >>> def _generate_derived_type_dictionary (cls, options):
            ...     for attribute in super (..., cls)._generate_derived_type_dictionary (options):
            ...         yield attribute
            ...
            ...     ...

        That is only an approximation and you can, for instance, change or override
        attributes returned by super-method.

        C{options} dictionary is constructed in such a way you should be able to evaluate
        all function-defining statements in it.  For instance, you can write own
        C{_generate_derived_type_dictionary} like this:

            >>> def _generate_derived_type_dictionary (cls, options):
            ...     ...
            ...
            ...     functions = {}
            ...
            ...     if 'foo_value' in options:
            ...         exec 'def foo (self): return foo_value' \
            ...              in { 'foo_value': options['foo_value'] }, functions
            ...
            ...     ...
            ...
            ...     for function in functions.iteritems ():
            ...         yield function

        Returned value for C{__slots__} is treated specially.  While normally values
        associated with the same name override previous values, values for C{__slots__}
        are combined into a tuple instead.

        Note that it is not recommended to use C{options} for execution globals or locals
        dictionary directly.  This way your code may become vulnerable to other option
        addition, e.g. for some derivative of the class.  For instance, you may use
        C{property} built-in, then setting it in C{options} will hide the built-in from
        your code.  Consider using C{L{_filter_options}} utility method.

        @param options:    dictionary of options passed to C{L{derive_type}} method, plus
                           C{cls} and C{new_class_name}.
        @type  options:    C{dict}

        @rtype:            iterable
        @returns:          Pairs of (Python identifier, value) for the new type.

        @raises exception: if there is any error in C{options}.
        """

        functions        = {}
        filtered_options = AbstractValueObject._filter_options (options, 'cls', 'getter', 'setter')


        if 'object' in options:
            object = options['object']
            if not is_valid_identifier (object):
                raise ValueError ("'%s' is not a valid Python identifier" % object)

            yield '__slots__', mangle_identifier (options['new_class_name'], object)

            execute (('def __init__(self, %s):\n'
                      '    cls.__init__(self)\n'
                      '    %s = %s')
                     % (object, AbstractValueObject._get_object (options), object),
                     filtered_options, functions)

            if 'property' in options:
                property = options['property']
                if property == object:
                    raise ValueError ("'property' option cannot be the same as 'object'")

                if not is_valid_identifier (property):
                    raise ValueError ("'%s' is not a valid Python identifier" % property)

                execute ('%s = property (lambda self: %s)'
                         % (mangle_identifier (options['new_class_name'], property),
                            AbstractValueObject._get_object (options)),
                         functions)

        else:
            if 'property' in options:
                raise ValueError ("'property' without 'object' option doesn't make sense")

        if 'dict' in options and options['dict']:
            # Gracefully ignore if this type already has a dict.
            if not _type_has_dictionary (cls):
                yield '__slots__', '__dict__'

        if 'getter' in options:
            if not is_callable (options['getter']):
                raise TypeError ("'getter' must be a callable")

            execute ('def get (self): return getter (%s)'
                     % AbstractValueObject._get_object (options),
                     filtered_options, functions)

        if 'setter' in options:
            if not is_callable (options['setter']):
                raise TypeError ("'setter' must be a callable")

            execute ('def set (self, value): return setter (%s, value)'
                     % AbstractValueObject._get_object (options),
                     filtered_options, functions)

        for function in functions.items ():
            yield function
Example #2
0
    def derive_type (cls, new_class_name, **options):
        """
        Derive and return a new type named C{new_class_name}.  Various C{options} define
        behaviour of instances of the new type.  Their set and sometimes semantics are
        defined by exact class this method is called for.

        @param new_class_name: name of the new class—important mostly for C{__str__} and
                               C{__repr__} implementations.
        @type  new_class_name: C{basestring}

        @param options:        options for the new type, as listed below.

        @newfield option:      Option, Options

        @option:               C{object} — valid Python identifier.  If specified, derived
                               type’s constructor will accept one parameter and store it
                               inside the created instance.  It will be used for calling
                               C{getter} and C{setter} functions.

        @option:               C{property} — valid Python identifier.  If specified,
                               C{object}’s value will be readable through a property, but
                               not writable.

        @option:               C{dict} — if true, derived type will have a C{__dict__}
                               slot, allowing setting any attribute on it.  This is
                               silently ignored if this class objects already have a dict.

        @option:               C{getter} — a callable accepting one argument, whose return
                               value will be used as C{L{get}} method result.  If
                               C{object} option is specified, the only argument will be
                               the one passed to instance constructor, else it will be
                               C{self} as passed to C{get} method.

        @option:               C{setter} — a callable accepting two argument, which will
                               be called from C{L{set}} method.  The first argument is
                               described in C{getter} option; the second is the C{value}
                               as passed to C{L{set}} method.

        @rtype:                C{type}

        @raises TypeError:     if C{new_class_name} is not a string or is not a valid
                               Python identifier.
        @raises exception:     whatever C{L{_generate_derived_type_dictionary}} raises, if
                               anything.
        """

        if not is_valid_identifier (new_class_name):
            raise TypeError ("'%s' is not a valid Python identifier" % new_class_name)

        full_options                   = dict (options)
        full_options['cls']            = cls
        full_options['new_class_name'] = new_class_name

        dictionary = { '__slots__': () }

        for value in cls._generate_derived_type_dictionary (full_options):
            if value[0] != '__slots__':
                dictionary[value[0]] = value[1]
            else:
                if isinstance (value[1], StringType):
                    dictionary['__slots__'] += (value[1],)
                else:
                    dictionary['__slots__'] += tuple (value[1])

        metaclass = dictionary.get ('__metaclass__', type (cls))
        new_type  = metaclass (new_class_name, (cls,), dictionary)

        try:
            raise Exception
        except Exception:
            try:
                # We try to pretend that the new type is created by the caller module, not
                # by `notify.base'.  That will give more helpful __repr__ result.
                traceback           = sys.exc_info () [2]
                new_type.__module__ = traceback.tb_frame.f_back.f_globals['__name__']
            except RuntimeError:
                # We can do nothing, ignore.
                pass

        return new_type
Example #3
0
    def derive_type(cls, new_class_name, **options):
        """
        Derive and return a new type named C{new_class_name}.  Various C{options} define
        behaviour of instances of the new type.  Their set and sometimes semantics are
        defined by exact class this method is called for.

        @param new_class_name: name of the new class—important mostly for C{__str__} and
                               C{__repr__} implementations.
        @type  new_class_name: C{basestring}

        @param options:        options for the new type, as listed below.

        @newfield option:      Option, Options

        @option:               C{object} — valid Python identifier.  If specified, derived
                               type’s constructor will accept one parameter and store it
                               inside the created instance.  It will be used for calling
                               C{getter} and C{setter} functions.

        @option:               C{property} — valid Python identifier.  If specified,
                               C{object}’s value will be readable through a property, but
                               not writable.

        @option:               C{dict} — if true, derived type will have a C{__dict__}
                               slot, allowing setting any attribute on it.  This is
                               silently ignored if this class objects already have a dict.

        @option:               C{getter} — a callable accepting one argument, whose return
                               value will be used as C{L{get}} method result.  If
                               C{object} option is specified, the only argument will be
                               the one passed to instance constructor, else it will be
                               C{self} as passed to C{get} method.

        @option:               C{setter} — a callable accepting two argument, which will
                               be called from C{L{set}} method.  The first argument is
                               described in C{getter} option; the second is the C{value}
                               as passed to C{L{set}} method.

        @rtype:                C{type}

        @raises TypeError:     if C{new_class_name} is not a string or is not a valid
                               Python identifier.
        @raises exception:     whatever C{L{_generate_derived_type_dictionary}} raises, if
                               anything.
        """

        if not is_valid_identifier(new_class_name):
            raise TypeError("'%s' is not a valid Python identifier" %
                            new_class_name)

        full_options = dict(options)
        full_options['cls'] = cls
        full_options['new_class_name'] = new_class_name

        dictionary = {'__slots__': ()}

        for value in cls._generate_derived_type_dictionary(full_options):
            if value[0] != '__slots__':
                dictionary[value[0]] = value[1]
            else:
                if isinstance(value[1], StringType):
                    dictionary['__slots__'] += (value[1], )
                else:
                    dictionary['__slots__'] += tuple(value[1])

        metaclass = dictionary.get('__metaclass__', type(cls))
        new_type = metaclass(new_class_name, (cls, ), dictionary)

        try:
            raise Exception
        except Exception:
            try:
                # We try to pretend that the new type is created by the caller module, not
                # by `notify.base'.  That will give more helpful __repr__ result.
                traceback = sys.exc_info()[2]
                new_type.__module__ = traceback.tb_frame.f_back.f_globals[
                    '__name__']
            except RuntimeError:
                # We can do nothing, ignore.
                pass

        return new_type
Example #4
0
    def _generate_derived_type_dictionary(cls, options):
        """
        Generate an iterable of pairs in the form (Python identifier, value) for a new
        type created by C{L{derive_type}}.  Exact pairs should be influenced by
        C{options}, which are C{options} as passed to C{derive_type} plus C{cls} (for
        convenience) and C{new_class_name}.

        This method is not meant to be callable from outside, use C{L{derive_type}} for
        that instead.

        Overriden implementations of this method are recommended but not required to be
        generator functions.  They should generally start like this:

            >>> def _generate_derived_type_dictionary (cls, options):
            ...     for attribute in super (..., cls)._generate_derived_type_dictionary (options):
            ...         yield attribute
            ...
            ...     ...

        That is only an approximation and you can, for instance, change or override
        attributes returned by super-method.

        C{options} dictionary is constructed in such a way you should be able to evaluate
        all function-defining statements in it.  For instance, you can write own
        C{_generate_derived_type_dictionary} like this:

            >>> def _generate_derived_type_dictionary (cls, options):
            ...     ...
            ...
            ...     functions = {}
            ...
            ...     if 'foo_value' in options:
            ...         exec 'def foo (self): return foo_value' \
            ...              in { 'foo_value': options['foo_value'] }, functions
            ...
            ...     ...
            ...
            ...     for function in functions.iteritems ():
            ...         yield function

        Returned value for C{__slots__} is treated specially.  While normally values
        associated with the same name override previous values, values for C{__slots__}
        are combined into a tuple instead.

        Note that it is not recommended to use C{options} for execution globals or locals
        dictionary directly.  This way your code may become vulnerable to other option
        addition, e.g. for some derivative of the class.  For instance, you may use
        C{property} built-in, then setting it in C{options} will hide the built-in from
        your code.  Consider using C{L{_filter_options}} utility method.

        @param options:    dictionary of options passed to C{L{derive_type}} method, plus
                           C{cls} and C{new_class_name}.
        @type  options:    C{dict}

        @rtype:            iterable
        @returns:          Pairs of (Python identifier, value) for the new type.

        @raises exception: if there is any error in C{options}.
        """

        functions = {}
        filtered_options = AbstractValueObject._filter_options(
            options, 'cls', 'getter', 'setter')

        if 'object' in options:
            object = options['object']
            if not is_valid_identifier(object):
                raise ValueError("'%s' is not a valid Python identifier" %
                                 object)

            yield '__slots__', mangle_identifier(options['new_class_name'],
                                                 object)

            execute(('def __init__(self, %s):\n'
                     '    cls.__init__(self)\n'
                     '    %s = %s') %
                    (object, AbstractValueObject._get_object(options), object),
                    filtered_options, functions)

            if 'property' in options:
                property = options['property']
                if property == object:
                    raise ValueError(
                        "'property' option cannot be the same as 'object'")

                if not is_valid_identifier(property):
                    raise ValueError("'%s' is not a valid Python identifier" %
                                     property)

                execute(
                    '%s = property (lambda self: %s)' %
                    (mangle_identifier(options['new_class_name'], property),
                     AbstractValueObject._get_object(options)), functions)

        else:
            if 'property' in options:
                raise ValueError(
                    "'property' without 'object' option doesn't make sense")

        if 'dict' in options and options['dict']:
            # Gracefully ignore if this type already has a dict.
            if not _type_has_dictionary(cls):
                yield '__slots__', '__dict__'

        if 'getter' in options:
            if not is_callable(options['getter']):
                raise TypeError("'getter' must be a callable")

            execute(
                'def get (self): return getter (%s)' %
                AbstractValueObject._get_object(options), filtered_options,
                functions)

        if 'setter' in options:
            if not is_callable(options['setter']):
                raise TypeError("'setter' must be a callable")

            execute(
                'def set (self, value): return setter (%s, value)' %
                AbstractValueObject._get_object(options), filtered_options,
                functions)

        for function in functions.items():
            yield function
Example #5
0
    def test_is_valid_identifier (self):
        self.assert_(is_valid_identifier ('foo'))
        self.assert_(is_valid_identifier ('_foo'))
        self.assert_(is_valid_identifier ('__foo'))
        self.assert_(is_valid_identifier ('foo2'))
        self.assert_(is_valid_identifier ('foo_bar'))
        self.assert_(is_valid_identifier ('FooBar'))
        self.assert_(is_valid_identifier ('fooBar'))

        self.assert_(not is_valid_identifier (''))
        self.assert_(not is_valid_identifier ('2foo'))
        self.assert_(not is_valid_identifier ('foo bar'))
        self.assert_(not is_valid_identifier ('foo.bar'))
        self.assert_(not is_valid_identifier ('-fooBar'))
        self.assert_(not is_valid_identifier ('foo_bar '))

        # Keywords are not valid identifiers.
        self.assert_(not is_valid_identifier ('class'))
        self.assert_(not is_valid_identifier ('def'))
        self.assert_(not is_valid_identifier ('in'))
        self.assert_(not is_valid_identifier ('and'))

        self.assert_(not is_valid_identifier (None))
        self.assert_(not is_valid_identifier (1))
        self.assert_(not is_valid_identifier (()))
        self.assert_(not is_valid_identifier ([]))