# Copyright (c) 2007, Enthought, Inc. # License: BSD Style. #--(Creating New Trait Types)--------------------------------------------------- """ Creating New Trait Types ======================== You create a *new style* trait type by subclassing the **TraitType** class or one of its subclasses, such as **Float** or **Str**. **TraitType** provides the infrastructure for creating a trait type and allows subclasses to define specific methods and class constants used to create either a new trait *type* or *property*. In the next section, we'll cover the methods and class constants used to define a new trait *type*, and in the section following that we'll show how to define a new trait *property*. Defining a New Trait Type ------------------------- The thing that distinguishes a trait *type* from a *property* is the existence of a *validate* method in the subclass. The *validate* method should have the following signature: validate ( self, object, name, value ) This method validates, coerces, or adapts the specified *value* as the value of the *name* trait of the *object* object. This method is called when a value is assigned to an object trait that is based on this subclass of *TraitType* and the class does not contain a definition for either the *get()* or *set()* methods. The method must return the original *value* or any suitably coerced or adapted value that is a legal value for the trait. If *value* is not a legal value for the trait, and cannot be coerced or adapted to a legal value, the method should either raise a **TraitError** or call the *error()* method to raise a **TraitError** on its behalf. In addition to *validate*, the subclass can also define the *post_setattr* method, which should have the following signature: post_setattr ( self, object, name, value ) This method allows the trait to do additional processing after *value* has been successfully assigned to the *name* trait of the *object* object. For most traits there is no additional processing that needs to be done, and this method need not be defined. It is normally used for creating *shadow* (i.e., *mapped* traits), but other uses may arise as well. This method does not need to return a value, and should normally not raise any exceptions. The subclass can also define a constant default value by setting the class-level *default_value* attribute to the desired constant value. For example:: class OddInt ( Int ): default_value = 1 ... If a non-constant default value is desired, you should override the **TraitType** class's *get_default_value* method. Refer to the documentation for the **TraitType** class for more information on what this method does. If you have a constant string that can be used as the type's *info* value, you can provide it by simple setting the string as the value of the class-level *info_text* attribute:: class OddInt ( Int ): info_text = 'an odd integer' ... If you have a type info string which depends upon the internal state of the trait, then you should override the **TraitType's** *info* method. This method has no arguments, and should return a string describing the values accepted by the trait type (e.g. 'an integer in the range from 1 to 5'). If you also have some type specific initialization that needs to be performed when the trait type is created, you can also override the **TraitType** class's *init* method. This method has no arguments and is automatically called from the **TraitType** class constructor. If you would like to specify a default Traits UI editor for your new trait type, you can also override the **TraitType** class's *create_editor* method, which has no arguments and should return the default **TraitEditor** for any instances of the type to use. This provides a basic overview of the basic methods and class constants needed to define a new trait type. Refer to the complete documentation for the **TraitType** and **BaseTraitHandler** classes for more information on other methods that can be overridden if necessary. Defining a New Trait Property ----------------------------- You can also define new trait *properties* by subclassing from **TraitType** or one of its subclasses. A *property* is distinguished from a *type* by the existence of a *get* and/or *set* method in the **TraitType** subclass. The signature for these two methods is as follows: get ( self, object, name ) This is the *getter* method of a trait that behaves like a property. It has the following arguments: object The object that the property applies to. name The name of the *object* property. If this method is not defined, but the *set* method is defined, the trait behaves like a *write-only* property. This method should return the value of the *name* property for the *object* object. set ( self, object, name, value ) This is the *setter* method of a trait that behaves like a property. It has the following arguments: object The object the property applies to. name The name of the property on *object*. value The value being assigned as the value of the property. If this method is not defined, but the *get* method is defined, the trait behaves like a *read-only* property. This method does not need to return a value, but it should raise a **TraitError** exception if the specified *value* is not valid and cannot be coerced or adapted to a valid value. Because the value of a *property* is determined by the *get* method, the *default_value* class constant and *get_default_value* method are not used. However, all other values and methods, such as the *info_text* class attribute and *info* method apply to a *property* as well as a normal type. Please refer to the preceding section on defining a trait type for additional information that applies to properties as well. """ #--<Imports>-------------------------------------------------------------------- from traits.api import * #--[DiceRoll Type]-------------------------------------------------------------- # Define a type whose value represents the roll of a pair of dice: class DiceRoll ( TraitType ): # Set default value to 'snake-eyes': default_value = ( 1, 1 ) # Describe the type: info_text = ('a tuple of the form (n,m), where both n and m are integers ' 'in the range from 1 to 6 representing a roll of a pair of ' 'dice') # Validate any value assigned to the trait to make sure it is a valid # dice roll: def validate ( self, object, name, value ): if (isinstance( value, tuple ) and (len( value ) == 2) and (1 <= value[0] <= 6) and (1 <= value[1] <= 6)): return value self.error( object, name, value ) #--[RandInt Property]----------------------------------------------------------- from random import randint # Define a read-only property whose value is a random integer in a specified # range: class RandInt ( TraitType ): # Define the type's constructor: def __init__( self, low = 1, high = 10, **metadata ): super( RandInt, self ).__init__( **metadata ) self.low = int( low ) self.high = int( high ) # Define the property's getter: def get ( self ): return randint( self.low, self.high ) # Define the type's type information: def info ( self ): return ('a random integer in the range from %d to %d' % ( self.low, self.high)) #--[Craps Class]---------------------------------------------------------------- # Define a test class containing both new trait types/properties: class Craps ( HasTraits ): rolls = List( DiceRoll ) die = RandInt( 1, 6 ) #--[Example*]-------------------------------------------------------------------- # Create a test object: craps = Craps() # Add a number of test dice rolls: for i in range( 10 ): craps.rolls.append( ( craps.die, craps.die ) ) # Display the results: print craps.rolls # Try to assign an invalid dice roll: try: craps.rolls.append( ( 0, 0 ) ) except TraitError: print 'Assigning an invalid dice roll failed.'