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
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
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
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
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 ([]))