APPTOOLS(3)

NAME
apptools - apptools 4.5.0

APPLICATION SCRIPTING FRAMEWORK

The Application Scripting Framework is a component of the Enthought Tool Suite that provides developers with an API that allows traits based objects to be made scriptable. Operations on a scriptable object can be recorded in a script and subsequently replayed.

The framework is completely configurable. Alternate implementations of all major components can be provided if necessary.

  Framework Concepts

The following are the concepts supported by the framework.

    •   Scriptable Type

        A scriptable type is a sub-type of HasTraits that has scriptable methods and scriptable traits. If a scriptable method is called, or a scriptable trait is set, then that action can be recorded in a script and subsequently replayed.

        If the __init__() method is scriptable then the creation of an object from the type can be recorded.

        Scriptable types can be explicitly defined or created dynamically from any sub-type of HasTraits.

    •   Scriptable API

        The set of a scriptable type's scriptable methods and traits constitutes the type's scriptable API.

        The API can be defined explicitly using the scriptable decorator (for methods) or the Scriptable wrapper (for traits).

        For scriptable types that are created dynamically then the API can be defined in terms of one or more types or interfaces or an explicit list of method and trait names. By default, all public methods and traits (ie. those whose name does not begin with an underscore) are part of the API. It is also possible to then explicitly exclude a list of method and trait names.

    •   Scriptable Object

        A scriptable object is an instance of a scriptable type.

        Scriptable objects can be explicitly created by calling the scriptable type. Alternatively a non-scriptable object can be made scriptable dynamically.

    •   Script

        A script is a Python script and may be a recording or written from scratch.

        If the creation of scriptable objects can be recorded, then it may be possible for a recording to be run directly by the Python interpreter and independently of the application that made the recording. Otherwise the application must run the script and first create any scriptable objects referred to in the script.

    •   Binding

        A script runs in a namespace which is, by default, empty. If the scriptable objects referred to in a script are not created by the script (because their type's __init__() method isn't scriptable) then they must be created by the application and added to the namespace. Adding an object to the namespace is called binding.

        Scriptable objects whose creation can be recorded will automatically bind themselves when they are created.

        It also possible to bind an object factory rather than the object itself. The factory will be called, and the object created, only if the object is needed by the script when it is run. This is typically used by plugins.

        The name that an object is bound to need bear no relation to the object's name within the application. Names may be dotted names (eg. aaa.bbb.ccc) and appropriate objects representing the intermediate parts of such a name will be created automatically.

        An event is fired whenever an object is bound (or when a bound factory is invoked). This allows other objects (eg. an embedded Python shell) to expose scriptable objects in other ways.

    •   Script Manager

        A script manager is responsible for the recording and subsequent playback of scripts. An application has a single script manager instance which can be explicitly set or created automatically.

  Limitations

In the current implementation scriptable Trait container types (eg. List, Dict) may only contain objects corresponding to fundamental Python types (eg. int, bool, str).

  API Overview

This section gives an overview of the API implemented by the framework. The complete API documentation is available as endo generated HTML.

The example application demonstrates some the features of the framework.

  Module Level Objects

    get_script_manager()
        The application's script manager is returned. One will be created automatically if needed.

    set_script_manager(script_manager)
        The application's script manager will be set to script_manager replacing any existing script manager.

    scriptable
        This is a decorator used to explicitly mark methods as being scriptable. Any call to a scriptable method is recorded. If a type's __init__() method is decorated then the creation of the object will be recorded.

    Scriptable
        This is a wrapper for a trait to explicitly mark it as being scriptable. Any change to the value of the trait will be recorded. Simple reads of the trait will not be recorded unless unless the value read is bound to another scriptable trait or passed as an argument to a scriptable method. Passing has_side_effects=True when wrapping the trait will ensure that a read will always be recorded.

    create_scriptable_type(script_type, name=None, bind_policy='auto', api=None, includes=None, excludes=None, script_init=True)
        This creates a new type based on an existing type but with certain methods and traits marked as being scriptable. Scriptable objects can then be created by calling the type.

        script_type is the existing, non-scriptable, type. The new type will be a sub-type of it. The api, includes and excludes arguments determine which methods and traits are made scriptable. By default, all public methods and traits (ie. those whose name does not begin with an underscore) are made scriptable.

        The name and bind_policy arguments determine how scriptable objects are bound when they are created. name is the name that an object will be bound to. It defaults to the name of script_type with the first character forced to lower case. name may be a dotted name, eg. aaa.bb.c.

        bind_policy determines what happens if an object is already bound to the name. If it is auto then a numerical suffix will be added to the name of the new object. If it is unique then an exception will be raised. If it is rebind then the object currently bound to the name will be unbound.

        api is a class or interface (or a list of classes or interfaces) that is used to provide the names of the methods and traits to be made scriptable. The class or interface effectively defines the scripting API.

        If api is not specified then includes is a list of method and trait names that are made scriptable.

        If api and includes are not specified then excludes is a list of method and trait names that are not made scriptable.

        If script_init is set then the __init__() method is made scriptable irrespective of the api, includes and excludes arguments.

        If script_init is not set then objects must be explicitly bound and name and bind_policy are ignored.

    make_object_scriptable(obj, api=None, includes=None, excludes=None)
        This takes an existing unscriptable object and makes it scriptable. It works by calling create_scriptable_type() on the the objects existing type and replacing that existing type with the new scriptable type.

        See the description of create_scriptable_type() for an explanation of the api, includes and excludes arguments.

  ScriptManager

The ScriptManager class is the default implementation of the IScriptManager interface.

    bind_event
        This event is fired whenever an object is bound or unbound. The event's argument implements the IBindEvent interface.

    recording
        This trait is set if a script is currently being recorded. It is updated automatically by the script manager.

    script
        This trait contains the text of the script currently being recorded (or the last recorded script if one is not being currently recorded). It is updated automatically by the script manager.

    script_updated
        This event is fired whenever the script trait is updated. The event's argument is the script manager.

    bind(self, obj, name=None, bind_policy='unique', api=None, includes=None, excludes=None)
        This method makes an object scriptable and binds it to a name. See the description of create_scriptable_type() for an explanation of the api, includes, excludes, name and bind_policy arguments.

    bind_factory(self, factory, name, bind_policy='unique', api=None, includes=None, excludes=None)
        This method binds an object factory to a name. The factory is called to create the object (and make it scriptable) only when the object is needed by a running script. See the description of create_scriptable_type() for an explanation of the name and bind_policy arguments.

    run(self, script)
        This method runs a script in a namespace containing all currently bound objects. script is any object that can be used by Python's exec statement including a string or a file-like object.

    run_file(self, file_name)
        This method runs a script in a namespace containing all currently bound objects. file_name is the name of a file containing the script.

    start_recording(self)
        This method starts the recording of a script.

    stop_recording(self)
        This method stops the recording of the current script.

  IBindEvent

The IBindEvent interface defines the interface that is implemented by the object passed when the script manager's bind_event is fired.

    name
        This trait is the name being bound or unbound.

    obj This trait is the obj being bound to name or None if name is being unbound.

  StartRecordingAction

The StartRecordingAction class is a canned PyFace action that starts the recording of changes to scriptable objects to a script.

  StopRecordingAction

The StopRecordingAction class is a canned PyFace action that ends the recording of changes to scriptable objects to a script.

  Implementing Application Scripting

The key part of supporting application scripting is to design an appropriate scripting API and to ensure than the application itself uses the API so that changes to the data can be recorded. The framework provides many ways to specify the scripting API. Which approach is appropriate in a particular case will depend on when it is a new application, or whether scripting is being added to an existing application, and how complex the application's data model is.

  Static Specification

A scripting API is specified statically by the explicit use of the scriptable decorator and the Scriptable trait wrapper. For example:

        from apptools.appscripting.api import scriptable, Scriptable
        from traits.api import HasTraits, Int, Str

        class DataModel(HasTraits):

            foo = Scriptable(Str)

            bar = Scriptable(Int, has_side_effects=True)

            @scriptable
            def baz(self):
                pass

            def weeble(self)
                pass

        # Create the scriptable object.  It's creation won't be recorded because
        # __init__() isn't decorated.
        obj = DataModel()

        # These will be recorded.
        obj.foo = ''
        obj.bar = 10
        obj.baz()

        # This will not be recorded.
        obj.weeble()

        # This won't be recorded unless 'f' is passed to something that is
        # recorded.
        f = obj.foo

        # This will be recorded because we set 'has_side_effects'.
        b = obj.bar

  Dynamic Specification

A scripting API can also be specified dynamically. The following example produces a scriptable object with the same scriptable API as above (with the exception that has_side_effects cannot be specified dynamically):

        from apptools.appscripting.api import create_scriptable_type
        from traits.api import HasTraits, Int, Str

        class DataModel(HasTraits):

            foo = Str

            bar = Int

            def baz(self):
                pass

            def weeble(self)
                pass

        # Create a scriptable type based on the above.
        ScriptableDataModel = create_scriptable_type(DataModel, excludes=['weeble'])

        # Now create scriptable objects from the scriptable type.  Note that each
        # object has the same type.
        obj1 = ScriptableDataModel()
        obj2 = ScriptableDataModel()

Instead we could bypass the type and make the objects themselves scriptable as follows:

        from apptools.appscripting.api import make_object_scriptable
        from traits.api import HasTraits, Int, Str

        class DataModel(HasTraits):

            foo = Str

            bar = Int

            def baz(self):
                pass

            def weeble(self)
                pass

        # Create unscriptable objects.
        obj1 = DataModel()
        obj2 = DataModel()

        # Now make the objects scriptable.  Note that each object has a different
        # type, each a sub-type of 'DataModel'.
        make_object_scriptable(obj1, excludes=['weeble'])
        make_object_scriptable(obj2, excludes=['weeble'])

With a more sophisticated design we may choose to specify the scriptable API as an interface as follows:

        from apptools.appscripting.api import make_object_scriptable
        from traits.api import HasTraits, Int, Interface, Str

        class DataModel(HasTraits):

            foo = Str

            bar = Int

            def baz(self):
                pass

            def weeble(self)
                pass

        class IScriptableDataModel(Interface):

            foo = Str

            bar = Int

            def baz(self):
                pass

        # Create an unscriptable object.
        obj = DataModel()

        # Now make the object scriptable.
        make_object_scriptable(obj, api=IScriptableDataModel)

  Scripting __init__()

Making a type's __init__() method has advantages and disadvantages. It means that the creation of scriptable objects will be recorded in a script (along with the necessary import statements). This means that the script can be run independently of your application by the standard Python interpreter.

The disadvantage is that, if you have a complex data model, with many interdependencies, then defining a complete and consistent scripting API that allows a script to run independently may prove difficult. In such cases it is better to have the application create and bind the scriptable objects itself.

PERMISSIONS FRAMEWORK - INTRODUCTION

The Permissions Framework is a component of the Enthought Tool Suite that provides developers with the facility to limit access to parts of an application unless the user is appropriately authorised. In other words it enables and disables different parts of the GUI according to the identity of the user.

The framework includes an API to allow it to be integrated with an organisation's existing security infrastructure, for example to look users up in a corporate LDAP directory.

The framework is completely configurable. Alternate implementations of all major components can be provided if necessary. The default implementations provide a simple local filesystem user database and allows roles to be defined and assigned to users.

The framework does not provide any facility for protecting access to data. It is not possible to implement such protection in Python and using the file security provided by a typical operating system.

  Framework Concepts

The following are the concepts supported by the framework.

    •   Permission

        A permission is the basic tool that a developer uses to specify that access to a part of the application should be restricted. If the current user has the permission then access is granted. A permission may be attached to a PyFace action, to an item of a TraitsUI view, or to a GUI toolkit specific widget. When the user is denied access, the corresponding GUI control is disabled or completely hidden.

    •   User

        Each application has a current user who is either authorised or unauthorised. In order to become authorised a user must identify themselves and authenticate that identity.

        An arbitrary piece of data (called a blob) can be associated with an authorised user which (with user manager support) can be stored securely. This might be used, for example, to store sensitive user preferences, or to implement a roaming profile.

    •   User Manager

        The user manager is responsible for authorising the current user and, therefore, defines how that is done. It also provides information about the user population to the policy manager. It may also, optionally, provide the ability to manage the user population (eg. add or delete users). The user manager must either maintain a persistent record of the user population, or interface with an external user database or directory service.

        The default user manager uses password based authorisation.

        The user manager persists its data in a user database. The default user manager provides an API so that different implementations of the user database can be used (for example to store the data in an RDBMS, or to integrate with an existing directory service). A default user database is provided that pickles the data in a local file.

    •   Policy Manager

        The policy manager is responsible for assigning permissions to users and for determining the permissions assigned to the current user. To do this it must maintain a persistent record of those assignments.

        The default policy manager supplied with the framework uses roles to make it easier for an administrator to manage the relationships between permissions and users. A role is defined as a named set of permissions, and a user may have one or more roles assigned to them.

        The policy manager persists its data in a policy database. The default policy manager provides an API so that different implementations of the policy database can be used (for example to store the data in an RDBMS). A default policy database is provided that pickles the data in a local file.

    •   Permissions Manager

        The permissions manager is a singleton object used to get and set the current policy and user managers.

  Framework APIs

The APIs provided by the permissions framework can be split into the following groups.

    •   Application API

        This part of the API is used by application developers.

    •   Policy Manager API

        This is the interface that an alternative policy manager must implement. The need to implement an alternative is expected to be very rare and so the API isn't covered further. See the definition of the IPolicyManager interface for the details.

    •   Default Policy Manager Data API

        This part of the API is used by developers to store the policy's persistent data in a more secure location (eg. on a remote server) than that provided by the default implementation.

    •   User Manager API

        This is the interface that an alternative user manager must implement. The need to implement an alternative is expected to be very rare and so the API isn't covered further. See the definition of the IUserManager interface for the details.

    •   Default User Manager Data API

        This part of the API is used by developers to store the user database in a more secure location (eg. on a remote server) than that provided by the default implementation.

The complete API documentation is available as endo generated HTML.

  What Do I Need to Reimplement?

The architecture of the permissions framework comprises several layers, each of which can reimplemented to meet the requirements of a particular environment. Hopefully the following questions and answers will clarify what needs to be reimplemented depending on your environment.

Q: Do you want to use roles to group permissions and assign them to users?

    A: If yes then use the supplied PolicyManager, otherwise provide your own
        IPolicyManager implementation.

Q: Do you want users to be authenticated using a password?

    A: If yes then use the supplied UserManager, otherwise provide your own
        IUserManager implementation.

    Q: Does the IUser interface allow you to store all the user specific
        information you need?

    A: If yes then use the supplied UserDatabase, otherwise provide your own
        IUserDatabase implementation.

Q: Do you want to store your user accounts as pickled data in a local file?

    A: If yes then use the supplied default, otherwise provide UserDatabase with
        your own IUserStorage implementation.

    Q: Do you want to store your policy data (ie. roles and role assignments) as
        pickled data in a local file?

    A: If yes then use the supplied default, otherwise provide PolicyManager with
        your own IPolicyStorage implementation.

  Deploying Alternative Managers

The permissions framework will first try to import the different managers from the apptools.permissions.external namespace. The default managers are only used if no alternative was found. Therefore, alternative managers should be deployed as an egg containing that namespace.

Specifically the framework looks for the following classes:
        PolicyManager from apptools.permissions.external.policy_manager

        PolicyStorage from apptools.permissions.external.policy_storage

        UserDatabase from apptools.permissions.external.user_database

        UserManager from apptools.permissions.external.user_manager

        UserStorage from apptools.permissions.external.user_storage

The example server is such a package that provides PolicyStorage and UserStorage implementations that use an XML-RPC based server to provide remote (and consequently more secure) policy and user databases.

  Using the Default Storage Implementations

The default policy and user managers both (again by default) persist their data as pickles in local files called ets_perms_policydb and ets_perms_userdb respectively. By default these are stored in the application's home directory (ie. that returned by ETSConfig.application_home).

Note that this directory is normally in the user's own directory structure whereas it needs to be available to all users of the application.

If the ETS_PERMS_DATA_DIR environment variable is set then its value is used instead.

The directory must be writeable by all users of the application.

It should be restated that the default implementations do not provide secure access to the permissions and user data. They are useful in a cooperative environment and as working examples.

APPLICATION API

This section provides an overview of the part of the ETS Permissions Framework API used by application developers. The Permissions Framework example demonstrates the API in use. An application typically uses the API to do the following:

    •   define permissions

    •   apply permissions

    •   user authentication

    •   getting and setting user data

    •   integrate management actions.

  Defining Permissions

A permission is the object that determines the user's access to a part of an application. While it is possible to apply the same permission to more than one part of an application, it is generally a bad idea to do so as it makes it difficult to separate them at a later date.

A permission has an id and a human readable description. Permission ids must be unique. By convention a dotted notation is used for ids to give them a structure. Ids should at least be given an application or plugin specific prefix to ensure their uniqueness.

Conventionally all an applications permissions are defined in a single permissions.py module. The following is an extract of the example's permissions.py module:

        from apptools.permissions.api import Permission

        # Add a new person.
        NewPersonPerm = Permission(id='ets.permissions.example.person.new',
                description=u"Add a new person")

        # Update a person's age.
        UpdatePersonAgePerm = Permission(id='ets.permissions.example.person.age.update',
                description=u"Update a person's age")

        # View or update a person's salary.
        PersonSalaryPerm = Permission(id='ets.permissions.example.person.salary',
                description=u"View or update a person's salary")

  Applying Permissions

Permissions are applied to different parts of an applications GUI. When the user has been granted a permission then the corresponding part of the GUI is displayed normally. When the user is denied a permission then the corresponding part of the GUI is disabled or completely hidden.

Permissions can be applied to TraitsUI view items and to any object which can be wrapped in a SecureProxy.

  TraitsUI View Items

Items in TraitsUI views have enabled_when and visible_when traits that are evaluated to determine if the item should be enabled or visible respectively. These are used to apply permissions by storing the relevant permissions in the model so that they are available to the view. The enabled_when and visible_when traits then simply reference the permission's granted trait. The granted trait automatically reflects whether or not the user currently has the corresponding permission.

In order for the view to be correctly updated when the user's permissions change (ie. when they become authenticated) the view must use the SecureHandler handler. This handler is a simple sub-class of the standard Traits Handler class.

The following extract from the example shows a default view of the Person object that enables the age item when the user has the UpdatePersonAgePerm permission and shows the salary item when the user has the PersonSalaryPerm permission:

        from apptools.permissions.api import SecureHandler
        from traits.api import HasTraits, Int, Unicode
        from traitsui.api import Item, View

        from permissions import UpdatePersonAgePerm, PersonSalaryPerm

        class Person(HasTraits):
            """A simple example of an object model"""

            # Name.
            name = Unicode

            # Age in years.
            age = Int

            # Salary.
            salary = Int

            # Define the default view with permissions attached.
            age_perm = UpdatePersonAgePerm
            salary_perm = PersonSalaryPerm

            traits_view = View(
                    Item(name='name'),
                    Item(name='age', enabled_when='object.age_perm.granted'),
                    Item(name='salary', visible_when='object.salary_perm.granted'),
                    handler=SecureHandler)

  Wrapping in a SecureProxy

Any object can have permissions applied by wrapping it in a SecureProxy object. An adapter is used that manages the enabled and visible states of the proxied object according to the current user's permissions. Otherwise the proxy behaves just like the object being proxied.

Adapters are included for the following types of object:

    •   PyFace actions

    •   PyFace widgets FIXME: TODO

    •   Qt widgets

    •   wx widgets

See Writing SecureProxy Adapters for a description of how to write adapters for other types of objects.

The following extract from the example shows the wrapping of a standard PyFace action and the application of the NewPersonPerm permission:

        from apptools.permissions.api import SecureProxy

        from permissions import NewPersonPerm

        ...

            def _new_person_action_default(self):
                """Trait initializer."""

                # Create the action and secure it with the appropriate permission.
                act = Action(name='New Person', on_perform=self._new_person)
                act = SecureProxy(act, permissions=[NewPersonPerm])

                return act

A SecureProxy also accepts a show argument that, when set to False, hides the object when it becomes disabled.

  Authenticating the User

The user manager supports the concept of the current user and is responsible for authenticating the user (and subsequently unauthorising the user if required).

The code fragment to authenticate the current user is:

        from apptools.permissions.api import get_permissions_manager

        get_permissions_Manager().user_manager.authenticate_user()

Unauthorising the current user is done using the unauthenticate_user() method.

As a convenience two PyFace actions, called LoginAction and LogoutAction, are provided that wrap these two methods.

As a further convenience a PyFace menu manager, called UserMenuManager, is provided that contains all the user and management actions (see below) in the permissions framework. This is used by the example.

The user menu, login and logout actions can be imported from apptools.permissions.action.api.

  Getting and Setting User Data

The user manager has a user trait that is an object that implements the IUser interface. It is only valid once the user has been authenticated.

The IUser interface has a blob trait that holds any binary data (as a Python string). The data will be read when the user is authenticated. The data will be written whenever it is changed.

  Integrating Management Actions

Both policy and user managers can provide actions that provide access to various management functions. Both have a management_actions trait that is a list of PyFace actions that invoke appropriate dialogs that allow the user to manage the policy and the user population appropriately.

User managers also have a user_actions trait that is a list of PyFace actions that invoke appropriate dialogs that allow the user to manage themselves. For example, the default user manager provides an action that allows a user to change their password.

The default policy manager provides actions that allows roles to be defined in terms of sets of permissions, and allows users to be assigned one or more roles.

The default user manager provides actions that allows users to be added, modified and deleted. A user manager that integrates with an enterprise's secure directory service may not provide any management actions.

All management actions have appropriate permissions attached to them.

  Writing SecureProxy Adapters

SecureProxy will automatically handle most of the object types you will want to apply permissions to. However it is possible to implement additional adapters to support other object types. To do this you need to implement a sub-class of AdapterBase and register it.

Adapters tend to be one of two styles according to how the object's enabled and visible states are changed. If the states are changed via attributes (typically Traits based objects) then the adapter will cause a proxy to be created for the object. If the states are changed via methods (typically toolkit widgets) then the adapter will probably modify the object itself. We will refer to these two styles as wrapping adapters and patching adapters respectively.

The following gives a brief overview of the AdapterBase class:

    proxied
        This instance attribute is a reference to the original object.

    register_adapter(adapter, type, type, ...)
        This is a class method that is used to register your adapter and one or more object types that it handles.

    adapt()
        This is a method that should be reimplemented by patching adapters. (The default implementation will cause a proxy to be created for wrapping adapters.) This is where any patching of the proxied attribute is done. The object returned will be returned by SecureProxy() and would normally be the patched object - but can be any object.

    setattr(name, value)
        This method should be reimplemented by wrapping adapters to intercept the setting of relevant attributes of the proxied object. The default implementation should be used as the fallback for irrelevant attributes.

    get_enabled()
        This method must be reimplemented to return the current enabled state.

    set_enabled(value)
        This method must be reimplemented to set the enabled state to the given value.

    update_enabled(value)
        This method is called by your adapter to set the desired value of the enabled state. The actual state set will depend on the current user's permissions.

    get_visible()
        This method must be reimplemented to return the current visible state.

    set_visible(value)
        This method must be reimplemented to set the visible state to the given value.

    update_visible(value)
        This method is called by your adapter to set the desired value of the visible state. The actual state set will depend on the current user's permissions.

The AdapterBase class is defined in adapter_base.py.

The PyFace action adapter is an example of a wrapping adapter.

The PyQt widget adapter is an example of a patching adapter.

DEFAULT POLICY MANAGER DATA API

This section provides an overview of the part of the ETS Permissions Framework API used by developers who want to store a policy manager's persistent data in a more secure location (eg. a remote server) than that provided by the default implementation.

The API is defined by the default policy manager which uses roles to make it easier to assign permissions to users. If this API isn't sufficiently flexible, or if roles are inappropriate, then an alternative policy manager should be implemented.

The API is fully defined by the IPolicyStorage interface. The default implementation of this interface stores the policy database as a pickle in a local file.

  Overview of IPolicyStorage

The IPolicyStorage interface defines a number of methods that must be implemented to read and write to the policy database. The methods are designed to be implemented using simple SQL statements.

In the event of an error a method must raise the PolicyStorageError exception. The string representation of the exception is used as an error message that is displayed to the user.

DEFAULT USER MANAGER DATA API

This section provides an overview of the part of the ETS Permissions Framework API used by developers who want to store a user database in a more secure location (eg. a remote server) than that provided by the default implementation.

The API is defined by the default user manager which uses password based authorisation. If this API isn't sufficiently flexible, or if another method of authorisation is used (biometrics for example) then an alternative user manager should be implemented.

The API is fully defined by the IUserDatabase interface. This allows user databases to be implemented that extend the IUser interface and store additional user related data. If the user database is being persisted in secure storage (eg. a remote RDBMS) then this could be used to store sensitive data (eg. passwords for external systems) that shouldn't be stored as ordinary preferences.

In most cases there will be no requirement to store additional user related data than that defined by IUser so the supplied UserDatabase implementation (which provides all the GUI code required to implement the IUserDatabase interface) can be used. The UserDatabase implementation delegates the access to the user database to an object implementing the IUserStorage interface. The default implementation of this interface stores the user database as a pickle in a local file.

  Overview of IUserStorage

The IUserStorage interface defines a number of methods that must be implemented to read and write to the user database. The methods are designed to be implemented using simple SQL statements.

In the event of an error a method must raise the UserStorageError exception. The string representation of the exception is used as an error message that is displayed to the user.

  Overview of IUserDatabase

The IUserDatabase interface defines a set of Bool traits, all beginning with can_, that describe the capabilities of a particular implementation. For example, the can_add_user trait is set by an implementation if it supports the ability to add a new user to the database.

Each of these capability traits has a corresponding method which has the same name except for the can_ prefix. The method only needs to be implemented if the corresponding traits is True. The method, for example add_user() is called by the user manager to implement the capability.

The interface has two other methods.

The bootstrapping() method is called by the user manager to determine if the database is bootstrapping. Typically this is when the database is empty and no users have yet been defined. The permissions framework treats this situation as a special case and is able to relax the enforcement of permissions to allow users and permissions to be initially defined.

The user_factory() method is called by the user manager to create a new user object, ie. an object that implements the IUser interface. This allows an implementation to extend the IUser interface and store additional user related data in the object if the blob trait proves insufficient.

PREFERENCES

The preferences package provides a simple API for managing application preferences. The classes in the package are implemented using a layered approach where the lowest layer provides access to the raw preferences mechanism and each layer on top providing more convenient ways to get and set preference values.

THE BASIC PREFERENCES MECHANISM

Lets start by taking a look at the lowest layer which consists of the IPreferences interface and its default implementation in the Preferences class. This layer implements the basic preferences system which is a hierarchical arrangement of preferences 'nodes' (where each node is simply an object that implements the IPreferences interface). Nodes in the hierarchy can contain preference settings and/or child nodes. This layer also provides a default way to read and write preferences from the filesystem using the excellent ConfigObj package.

This all sounds a bit complicated but, believe me, it isn't! To prove it (hopefully) lets look at an example. Say I have the following preferences in a file 'example.ini':

        [acme.ui]
        bgcolor = blue
        width = 50
        ratio = 1.0
        visible = True

        [acme.ui.splash_screen]
        image = splash
        fgcolor = red

I can create a preferences hierarchy from this file by:

        >>> from apptools.preferences.api import Preferences
        >>> preferences = Preferences(filename='example.ini')
        >>> preferences.dump()

         Node() {}
            Node(acme) {}
              Node(ui) {'bgcolor': 'blue', 'ratio': '1.0', 'width': '50', 'visible': 'True'}
                Node(splash_screen) {'image': 'splash', 'fgcolor': 'red'}

The 'dump' method (useful for debugging etc) simply 'pretty prints' a preferences hierarchy. The dictionary next to each node contains the node's actual preferences. In this case, the root node (the node with no name) is the preferences object that we created. This node now has one child node 'acme', which contains no preferences. The 'acme' node has one child, 'ui', which contains some preferences (e.g. 'bgcolor') and also a child node 'splash_screen' which also contains preferences (e.g. 'image').

To look up a preference we use:

        >>> preferences.get('acme.ui.bgcolor')
        'blue'

If no such preferences exists then, by default, None is returned:

        >>> preferences.get('acme.ui.bogus') is None
        True

You can also specify an explicit default value:

        >>> preferences.get('acme.ui.bogus', 'fred')
        'fred'

To set a preference we use:

        >>> preferences.set('acme.ui.bgcolor', 'red')
        >>> preferences.get('acme.ui.bgcolor')
        'red'

And to make sure the preferences are saved back to disk:

        >>> preferences.flush()

To add a new preference value we simply set it:

        >>> preferences.set('acme.ui.fgcolor', 'black')
        >>> preferences.get('acme.ui.fgcolor')
        'black'

Any missing nodes in a call to 'set' are created automatically, hence:

        >>> preferences.set('acme.ui.button.fgcolor', 'white')
        >>> preferences.get('acme.ui.button.fgcolor')
        'white'

Preferences can also be 'inherited'. e.g. Notice that the 'splash_screen' node does not contain a 'bgcolor' preference, and hence:

        >>> preferences.get('acme.ui.splash_screen.bgcolor') is None
        True

But if we allow the 'inheritance' of preference values then:

        >>> preferences.get('acme.ui.splash_screen.bgcolor', inherit=True)
        'red'

By using 'inheritance' here the preferences system will try the following preferences:

        'acme.ui.splash_screen.bgcolor'
        'acme.ui.bgcolor'
        'acme.bgcolor'
        'bgcolor'

  Strings, Glorious Strings

At this point it is worth mentioning that preferences are always stored and returned as strings. This is because of the limitations of the traditional '.ini' file format i.e. they don't contain any type information! Now before you start panicking, this doesn't mean that all of your preferences have to be strings! Currently the preferences system allows, strings(!), booleans, ints, longs, floats and complex numbers. When you store a non-string value it gets converted to a string for you, but you always get a string back:

        >>> preferences.get('acme.ui.width')
        '50'
        >>> preferences.set('acme.ui.width', 100)
        >>> preferences.get('acme.ui.width')
        '100'

        >>> preferences.get('acme.ui.visible')
        'True'
        >>> preferences.set('acme.ui.visible', False)
        >>> preferences.get('acme.ui.visible')
        'False'

This is obviously not terribly convenient, and so the following section discusses how we associate type information with our preferences to make getting and setting them more natural.

PREFERENCES AND TYPES

As mentioned previously, we would like to be able to get and set non-string preferences in a more convenient way. This is where the PreferencesHelper class comes in.

Let's take another look at 'example.ini':

        [acme.ui]
        bgcolor = blue
        width = 50
        ratio = 1.0
        visible = True

        [acme.ui.splash_screen]
        image = splash
        fgcolor = red

Say, I am interested in the preferences in the 'acme.ui' section. I can use a preferences helper as follows:

        from apptools.preferences.api import PreferencesHelper

        class SplashScreenPreferences(PreferencesHelper):
            """ A preferences helper for the splash screen. """

            PREFERENCES_PATH = 'acme.ui'

            bgcolor = Str
            width   = Int
            ratio   = Float
            visible = Bool

        >>> preferences = Preferences(filename='example.ini')
        >>> helper = SplashScreenPreferences(preferences=preferences)
        >>> helper.bgcolor
        'blue'
        >>> helper.width
        100
        >>> helper.ratio
        1.0
        >>> helper.visible
        True

And, obviously, I can set the value of the preferences via the helper too:

        >>> helper.ratio = 0.5

And if you want to prove to yourself it really did set the preference:

        >>> preferences.get('acme.ui.ratio')
        '0.5'

Using a preferences helper you also get notified via the usual trait mechanism when the preferences are changed (either via the helper or via the preferences node directly:

        def listener(obj, trait_name, old, new):
            print trait_name, old, new

        >>> helper.on_trait_change(listener)
        >>> helper.ratio = 0.75
        ratio 0.5 0.75
        >>> preferences.set('acme.ui.ratio', 0.33)
        ratio 0.75 0.33

If you always use the same preference node as the root of your preferences you can also set the class attribute 'PreferencesHelper.preferences' to be that node and from then on in, you don't have to pass a preferences collection in each time you create a helper:

        >>> PreferencesHelper.preferences = Preferences(filename='example.ini')
        >>> helper = SplashScreenPreferences()
        >>> helper.bgcolor
        'blue'
        >>> helper.width
        100
        >>> helper.ratio
        1.0
        >>> helper.visible
        True

SCOPED PREFERENCES

In many applications the idea of preferences scopes is useful. In a scoped system, an actual preference value can be stored in any scope and when a call is made to the 'get' method the scopes are searched in order of precedence.

The default implementation (in the ScopedPreferences class) provides two scopes by default:

    1.  The application scope

This scope stores itself in the 'ETSConfig.application_home' directory. This scope is generally used when setting any user preferences.

    2.  The default scope

This scope is transient (i.e. it does not store itself anywhere). This scope is generally used to load any predefined default values into the preferences system.

If you are happy with the default arrangement, then using the scoped preferences is just like using the plain old non-scoped version:

        >>> from apptools.preferences.api import ScopedPreferences
        >>> preferences = ScopedPreferences(filename='example.ini')
        >>> preferences.load('example.ini')
        >>> p.dump()

          Node() {}
            Node(application) {}
              Node(acme) {}
                Node(ui) {'bgcolor': 'blue', 'ratio': '1.0', 'width': '50', 'visible': 'True'}
                  Node(splash_screen) {'image': 'splash', 'fgcolor': 'red'}
            Node(default) {}

Here you can see that the root node now has a child node representing each scope.

When we are getting and setting preferences using scopes we generally want the following behaviour:

a) When we get a preference we want to look it up in each scope in order. The first scope that contains a value 'wins'.

b) When we set a preference, we want to set it in the first scope. By default this means that when we set a preference it will be set in the application scope. This is exactly what we want as the application scope is the scope that is persistent.

So usually, we just use the scoped preferences as before:

        >>> preferences.get('acme.ui.bgcolor')
        'blue'
        >>> preferences.set('acme.ui.bgcolor', 'red')
        >>> preferences.dump()

          Node() {}
            Node(application) {}
              Node(acme) {}
                Node(ui) {'bgcolor': 'red', 'ratio': '1.0', 'width': '50', 'visible': 'True'}
                  Node(splash_screen) {'image': 'splash', 'fgcolor': 'red'}
            Node(default) {}

And, conveniently, preference helpers work just the same with scoped preferences too:

        >>> PreferencesHelper.preferences = ScopedPreferences(filename='example.ini')
        >>> helper = SplashScreenPreferences()
        >>> helper.bgcolor
        'blue'
        >>> helper.width
        100
        >>> helper.ratio
        1.0
        >>> helper.visible
        True

  Accessing a particular scope

Should you care about getting or setting a preference in a particular scope then you use the following syntax:

        >>> preferences.set('default/acme.ui.bgcolor', 'red')
        >>> preferences.get('default/acme.ui.bgcolor')
        'red'
        >>> preferences.dump()

          Node() {}
            Node(application) {}
              Node(acme) {}
                Node(ui) {'bgcolor': 'red', 'ratio': '1.0', 'width': '50', 'visible': 'True'}
                  Node(splash_screen) {'image': 'splash', 'fgcolor': 'red'}
            Node(default) {}
              Node(acme) {}
                Node(ui) {'bgcolor': 'red'}

You can also get hold of a scope via:

        >>> default = preferences.get_scope('default')

And then perform any of the usual operations on it.

FURTHER READING

So that's a quick tour around the basic useage of the preferences API. For more imformation about what is provided take a look at the API documentation.

If you are using Envisage to build your applications then you might also be interested in the Preferences in Envisage section.

PREFERENCES IN ENVISAGE

This section discusses how an Envisage application uses the preferences mechanism. Envisage tries not to dictate too much, and so this describes the default behaviour, but you are free to override it as desired.

Envisage uses the default implementation of the ScopedPreferences class which is made available via the application's 'preferences' trait:

        >>> application = Application(id='myapplication')
        >>> application.preferences.set('acme.ui.bgcolor', 'yellow')
        >>> application.preferences.get('acme.ui.bgcolor')
        'yellow'

Hence, you use the Envisage preferences just like you would any other scoped preferences.

It also registers itself as the default preferences node used by the PreferencesHelper class. Hence you don't need to provide a preferences node explicitly to your helper:

        >>> helper = SplashScreenPreferences()
        >>> helper.bgcolor
        'blue'
        >>> helper.width
        100
        >>> helper.ratio
        1.0
        >>> helper.visible
        True

The only extra thing that Envisage does for you is to provide an extension point that allows you to contribute any number of '.ini' files that are loaded into the default scope when the application is started.

e.g. To contribute a preference file for my plugin I might use:

        class MyPlugin(Plugin):
            ...

            @contributes_to('envisage.preferences')
            def get_preferences(self, application):
                return ['pkgfile://mypackage:preferences.ini']

AUTOMATIC SCRIPT RECORDING

This package provides a very handy and powerful Python script recording facility. This can be used to:

            •   record all actions performed on a traits based UI into a human readable, Python script that should be able to recreate your UI actions.

            •   easily learn the scripting API of an application.

This package is not just a toy framework and is powerful enough to provide full script recording to the Mayavi application. Mayavi is a powerful 3D visualization tool that is part of ETS.

  The scripting API

The scripting API primarily allows you to record UI actions for objects that have Traits. Technically the framework listens to all trait changes so will work outside a UI. We do not document the full API here, the best place to look for that is the apptools.scripting.recorder module which is reasonably well documented. We provide a high level overview of the library.

The quickest way to get started is to look at a small example.

  A tour by example

The following example is taken from the test suite. Consider a set of simple objects organized in a hierarchy:

        from traits.api import (HasTraits, Float, Instance,
                Str, List, Bool, HasStrictTraits, Tuple, Range, TraitPrefixMap,
                Trait)
        from apptools.scripting.api import (Recorder, recordable,
            set_recorder)

        class Property(HasStrictTraits):
            color = Tuple(Range(0.0, 1.0), Range(0.0, 1.0), Range(0.0, 1.0))
            opacity = Range(0.0, 1.0, 1.0)
            representation = Trait('surface',
                                   TraitPrefixMap({'surface':2,
                                                   'wireframe': 1,
                                                   'points': 0}))
        class Toy(HasTraits):
            color = Str
            type = Str
            # Note the use of the trait metadata to ignore this trait.
            ignore = Bool(False, record=False)

        class Child(HasTraits):
            name = Str('child')
            age = Float(10.0)
            # The recorder walks through sub-instances if they are marked
            # with record=True
            property = Instance(Property, (), record=True)
            toy = Instance(Toy, record=True)
            friends = List(Str)

            # The decorator records the method.
            @recordable
            def grow(self, x):
                """Increase age by x years."""
                self.age += x

        class Parent(HasTraits):
            children = List(Child, record=True)
            recorder = Instance(Recorder, record=False)

Using these simple classes we first create a simple object hierarchy as follows:

        p = Parent()
        c = Child()
        t = Toy()
        c.toy = t
        p.children.append(c)

Given this hierarchy, we'd like to be able to record a script. To do this we setup the recording infrastructure:

        from mayavi.core.recorder import Recorder, set_recorder
        # Create a recorder.
        r = Recorder()
        # Set the global recorder so the decorator works.
        set_recorder(r)
        r.register(p)
        r.recording = True

The key method here is the r.register(p) call above. It looks at the traits of p and finds all traits and nested objects that specify a record=True in their trait metadata (all methods starting and ending with _ are ignored). All sub-objects are in turn registered with the recorder and so on. Callbacks are attached to traits changes and these are wired up to produce readable and executable code. The set_recorder(r) call is also very important and sets the global recorder so the framework listens to any functions that are decorated with the recordable decorator.

Now lets test this out like so:

        # The following will be recorded.
        c.name = 'Shiva'
        c.property.representation = 'w'
        c.property.opacity = 0.4
        c.grow(1)

To see what's been recorded do this:

        print r.script

This prints:

        child = parent.children[0]
        child.name = 'Shiva'
        child.property.representation = 'wireframe'
        child.property.opacity = 0.40000000000000002
        child.grow(1)

The recorder internally maintains a mapping between objects and unique names for each object. It also stores the information about the location of a particular object in the object hierarchy. For example, the path to the Toy instance in the hierarchy above is parent.children[0].toy. Since scripting with lists this way can be tedious, the recorder first instantiates the child:

        child = parent.children[0]

Subsequent lines use the child attribute. The recorder always tries to instantiate the object referred to using its path information in this manner.

To record a function or method call one must simply decorate the function/method with the recordable decorator. Nested recordable functions are not recorded and trait changes are also not recorded if done inside a recordable function.

NOTE:

            1.  It is very important to note that the global recorder must be set via the set_recorder method. The recordable decorator relies on this being set to work.

            2.  The recordable decorator will work with plain Python classes and with functions too.

To stop recording do this:

        r.unregister(p)
        r.recording = False

The r.unregister(p) reverses the r.register(p) call and unregisters all nested objects as well.

  Advanced use cases

Here are a few advanced use cases.

            •   The API also provides a RecorderWithUI class that provides a simple user interface that prints the recorded script and allows the user to save the script.

            •   Sometimes it is not enough to just record trait changes, one may want to pass an arbitrary string or command when recording is occurring. To allow for this, if one defines a recorder trait on the object, it is set to the current recorder. One can then use this recorder to do whatever one wants. This is very convenient.

            •   To ignore specific traits one must specify either a record=False metadata to the trait definition or specify a list of strings to the register method in the ignore keyword argument.

            •   If you want to use a specific name for an object on the script you can pass the script_id parameter to the register function.

For more details on the recorder itself we suggest reading the module source code. It is fairly well documented and with the above background should be enough to get you going.

UNDO FRAMEWORK

The Undo Framework is a component of the Enthought Tool Suite that provides developers with an API that implements the standard pattern for do/undo/redo commands.

The framework is completely configurable. Alternate implementations of all major components can be provided if necessary.

  Framework Concepts

The following are the concepts supported by the framework.

    •   Command

        A command is an application defined operation that can be done (i.e. executed), undone (i.e. reverted) and redone (i.e. repeated).

        A command operates on some data and maintains sufficient state to allow it to revert or repeat a change to the data.

        Commands may be merged so that potentially long sequences of similar commands (e.g. to add a character to some text) can be collapsed into a single command (e.g. to add a word to some text).

    •   Macro

        A macro is a sequence of commands that is treated as a single command when being undone or redone.

    •   Command Stack

        A command is done by pushing it onto a command stack. The last command can be undone and redone by calling appropriate command stack methods. It is also possible to move the stack's position to any point and the command stack will ensure that commands are undone or redone as required.

        A command stack maintains a clean state which is updated as commands are done and undone. It may be explicitly set, for example when the data being manipulated by the commands is saved to disk.

        Canned PyFace actions are provided as wrappers around command stack methods to implement common menu items.

    •   Undo Manager

        An undo manager is responsible for one or more command stacks and maintains a reference to the currently active stack. It provides convenience undo and redo methods that operate on the currently active stack.

        An undo manager ensures that each command execution is allocated a unique sequence number, irrespective of which command stack it is pushed to. Using this it is possible to synchronise multiple command stacks and restore them to a particular point in time.

        An undo manager will generate an event whenever the clean state of the active stack changes. This can be used to maintain some sort of GUI status indicator to tell the user that their data has been modified since it was last saved.

Typically an application will have one undo manager and one undo stack for each data type that can be edited. However this is not a requirement: how the command stack's in particular are organised and linked (with the user manager's sequence number) can need careful thought so as not to confuse the user - particularly in a plugin based application that may have many editors.

To support this typical usage the PyFace Workbench class has an undo_manager trait and the PyFace Editor class has a command_stack trait. Both are lazy loaded so can be completely ignored if they are not used.

  API Overview

This section gives a brief overview of the various classes implemented in the framework. The complete API documentation is available as endo generated HTML.

The example application demonstrates all the major features of the framework.

  UndoManager

The UndoManager class is the default implementation of the IUndoManager interface.

    active_stack
        This trait is a reference to the currently active command stack and may be None. Typically it is set when some sort of editor becomes active.

    active_stack_clean
        This boolean trait reflects the clean state of the currently active command stack. It is intended to support a "document modified" indicator in the GUI. It is maintained by the undo manager.

    stack_updated
        This event is fired when the index of a command stack is changed. A reference to the stack is passed as an argument to the event and may not be the currently active stack.

    undo_name
        This Unicode trait is the name of the command that can be undone, and will be empty if there is no such command. It is maintained by the undo manager.

    redo_name
        This Unicode trait is the name of the command that can be redone, and will be empty if there is no such command. It is maintained by the undo manager.

    sequence_nr
        This integer trait is the sequence number of the next command to be executed. It is incremented immediately before a command's do() method is called. A particular sequence number identifies the state of all command stacks handled by the undo manager and allows those stacks to be set to the point they were at at a particular point in time. In other words, the sequence number allows otherwise independent command stacks to be synchronised.

    undo()
        This method calls the undo() method of the last command on the active command stack.

    redo()
        This method calls the redo() method of the last undone command on the active command stack.

  CommandStack

The CommandStack class is the default implementation of the ICommandStack interface.

    clean
        This boolean traits reflects the clean state of the command stack. Its value changes as commands are executed, undone and redone. It may also be explicitly set to mark the current stack position as being clean (when data is saved to disk for example).

    undo_name
        This Unicode trait is the name of the command that can be undone, and will be empty if there is no such command. It is maintained by the command stack.

    redo_name
        This Unicode trait is the name of the command that can be redone, and will be empty if there is no such command. It is maintained by the command stack.

    undo_manager
        This trait is a reference to the undo manager that manages the command stack.

    push(command)
        This method executes the given command by calling its do() method. Any value returned by do() is returned by push(). If the command couldn't be merged with the previous one then it is saved on the command stack.

    undo(sequence_nr=0)
        This method undoes the last command. If a sequence number is given then all commands are undone up to an including the sequence number.

    redo(sequence_nr=0)
        This method redoes the last command and returns any result. If a sequence number is given then all commands are redone up to an including the sequence number and any result of the last of these is returned.

    clear()
        This method clears the command stack, without undoing or redoing any commands, and leaves the stack in a clean state. It is typically used when all changes to the data have been abandoned.

    begin_macro(name)
        This method begins a macro by creating an empty command with the given name. The commands passed to all subsequent calls to push() will be contained in the macro until the next call to end_macro(). Macros may be nested. The command stack is disabled (ie. nothing can be undone or redone) while a macro is being created (ie. while there is an outstanding end_macro() call).

    end_macro()
        This method ends the current macro.

  ICommand

The ICommand interface defines the interface that must be implemented by any undoable/redoable command.

    data
        This optional trait is a reference to the data object that the command operates on. It is not used by the framework itself.

    name
        This Unicode trait is the name of the command as it will appear in any GUI element (e.g. in the text of an undo and redo menu entry). It may include & to indicate a keyboard shortcut which will be automatically removed whenever it is inappropriate.

    __init__(*args)
        If the command takes arguments then the command must ensure that deep copies should be made if appropriate.

    do()
        This method is called by a command stack to execute the command and to return any result. The command must save any state necessary for the undo() and redo() methods to work. It is guaranteed that this will only ever be called once and that it will be called before any call to undo() or redo().

    undo()
        This method is called by a command stack to undo the command.

    redo()
        This method is called by a command stack to redo the command and to return any result.

    merge(other)
        This method is called by the command stack to try and merge the other command with this one. True should be returned if the commands were merged. If the commands are merged then other will not be placed on the command stack. A subsequent undo or redo of this modified command must have the same effect as the two original commands.

  AbstractCommand

AbstractCommand is an abstract base class that implements the ICommand interface. It provides a default implementation of the merge() method.

  CommandAction

The CommandAction class is a sub-class of the PyFace Action class that is used to wrap commands.

    command
        This callable trait must be set to a factory that will return an object that implements ICommand. It will be called when the action is invoked and the object created pushed onto the command stack.

    command_stack
        This instance trait must be set to the command stack that commands invoked by the action are pushed to.

    data
        This optional trait is a reference to the data object that will be passed to the command factory when it is called.

  UndoAction

The UndoAction class is a canned PyFace action that undoes the last command of the active command stack.

  RedoAction

The RedoAction class is a canned PyFace action that redoes the last command undone of the active command stack.

THE SELECTION SERVICE

It is quite common in GUI applications to have a UI element displaying a collection of items that a user can select ("selection providers"), while other parts of the application must react to changes in the selection ("selection listeners").

Ideally, the listeners would not have a direct dependency on the UI object. This is especially important in extensible envisage applications, where a plugin might need to react to a selection change, but we do not want to expose the internal organization of the application to external developers.

This package defines a selection service that manages the communication between providers and listener.

  The SelectionService object

The SelectionService object is the central manager that handles the communication between selection providers and listener.

Selection providers are components that wish to publish information about their current selection for public consumption. They register to a selection service instance when they first have a selection available (e.g., when the UI showing a list of selectable items is initialized), and un-register as soon as the selection is not available anymore (e.g., the UI is destroyed when the windows is closed).

Selection listeners can query the selection service to get the current selection published by a provider, using the provider unique ID.

The service acts as a broker between providers and listeners, making sure that they are notified when the selection event is fired.

  Selection providers

Any object can become a selection provider by implementing the ISelectionProvider interface, and registering to the selection service.

Selection providers must provide a unique ID provider_id, which is used by listeners to request its current selection.

Whenever its selection changes, providers fire a selection event. The content of the event is an instance implementing ISelection that contains information about the selected items. For example, a ListSelection object contains a list of selected items, and their indices.

Selection providers can also be queried directly about their current selection using the get_selection method, and can be requested to change their selection to a new one with the set_selection method.

  Registration

Selection providers publish their selection by registering to the selection service using the add_selection_provider method. When the selection is no longer available, selection providers should un-register through remove_selection_provider.

Typically, selection providers are UI objects showing a list or tree of items, they register as soon as the UI component is initialized, and un-register when the UI component disappears (e.g., because their window has been closed). In more complex applications, the registration could be done by a controller object instead.

  Selection listeners

Selection listeners request information regarding the current selection of a selection provider given their provider ID. The SelectionService supports two distinct use cases:

            1.  Passively listening to selection changes: listener connect to a specific provider and are notified when the provider's selection changes.

            2.  Actively querying a provider for its current selection: the selection service can be used to query a provider using its unique ID.

  Passive listening

Listeners connect to the selection events for a given provider using the connect_selection_listener method. They need to provide the unique ID of the provider, and a function (or callable) that is called to send the event. This callback function takes one argument, an implementation of the ISelection that represents the selection.

It is possible for a listener to connect to a provider ID before it is registered. As soon as the provider is registered, the listener will receive a notification containing the provider's initial selection.

To disconnect a listener use the methods disconnect_selection_listener.

  Active querying

In other instances, an element of the application only needs the current selection at a specific time. For example, a toolbar button could open dialog representing a user action based on what is currently selected in the active editor.

The get_selection method calls the corresponding method on the provider with the given ID and returns an ISelection instance.

  Setting a selection

Finally, it is possible to request a provider to set its selection to a given set of objects with set_selection. The main use case for this method is multiple views of the same list of objects, which need to keep their selection synchronized.

If the items specified in the arguments are not available in the provider, a ProviderNotRegisteredError is raised, unless the optional keyword argument ignore_missing is set to True.

  API Reference

  apptools.selection Package

Users of the apptools.selection package can access the objects that are part of the public API through the convenience apptools.selection.api.

  selection_service Module

    class apptools.selection.selection_service.SelectionService
        Bases: traits.has_traits.HasTraits

        The selection service connects selection providers and listeners.

        The selection service is a register of selection providers, i.e., objects that publish their current selection.

        Selections can be requested actively, by explicitly requesting the current selection in a provider (get_selection(id)()), or passively by connecting selection listeners.

        add_selection_provider(provider)
            Add a selection provider.

            The provider is identified by its ID. If a provider with the same ID has been already registered, an IDConflictError is raised.

            Arguments:

                provider -- ISelectionProvider
                    The selection provider added to the internal registry.

        connect_selection_listener(provider_id, func)
            Connect a listener to selection events from a specific provider.

            The signature if the listener callback is func(i_selection). The listener is called:

            1.  When a provider with the given ID is registered, with its initial selection as argument, or

            2.  whenever the provider fires a selection event.

        It is perfectly valid to connect a listener before a provider with the given ID is registered. The listener will remain connected even if the provider is repeatedly connected and disconnected.

            Arguments:

                provider_id -- str
                    The selection provider ID.

                func -- callable(i_selection)
                    A callable object that is notified when the selection changes.

        disconnect_selection_listener(provider_id, func)
            Disconnect a listener from a specific provider.

            Arguments:

                provider_id -- str
                    The selection provider ID.

                func -- callable(provider_id, i_selection)
                    A callable object that is notified when the selection changes.

        get_selection(provider_id)
            Return the current selection of the provider with the given ID.

            If a provider with that ID has not been registered, a ProviderNotRegisteredError is raised.

            Arguments:

                provider_id -- str
                    The selection provider ID.

            Returns:

                selection -- ISelection
                    The current selection of the provider.

        has_selection_provider(provider_id)
            Has a provider with the given ID been registered?

        remove_selection_provider(provider)
            Remove a selection provider.

            If the provider has not been registered, a ProviderNotRegisteredError is raised.

            Arguments:

                provider -- ISelectionProvider
                    The selection provider added to the internal registry.

        set_selection(provider_id, items, ignore_missing=False)
            Set the current selection in a provider to the given items.

            If a provider with the given ID has not been registered, a ProviderNotRegisteredError is raised.

            If ignore_missing is True, items that are not available in the selection provider are silently ignored. If it is False (default), a ValueError should be raised.

            Arguments:

                provider_id -- str
                    The selection provider ID.

                items -- list
                    List of items to be selected.

                ignore_missing -- bool
                    If False (default), the provider raises an exception if any of the items in items is not available to be selected. Otherwise, missing elements are silently ignored, and the rest is selected.

  i_selection_provider Module

    class apptools.selection.i_selection_provider.ISelectionProvider
        Bases: traits.has_traits.Interface

        Source of selections.

        get_selection()
            Return the current selection.

            Returns:

                selection -- ISelection
                    Object representing the current selection.

        provider_id = Str()
            Unique ID identifying the provider.

        selection = Event
            Event triggered when the selection changes. The content of the event is an ISelection instance.

        set_selection(items, ignore_missing=False)
            Set the current selection to the given items.

            If ignore_missing is True, items that are not available in the selection provider are silently ignored. If it is False (default), an ValueError should be raised.

            Arguments:

                items -- list
                    List of items to be selected.

                ignore_missing -- bool
                    If False (default), the provider raises an exception if any of the items in items is not available to be selected. Otherwise, missing elements are silently ignored, and the rest is selected.

  is_selection Module

    class apptools.selection.i_selection.IListSelection
        Bases: apptools.selection.i_selection.ISelection

        Selection for ordered sequences of items.

        indices = List
            Indices of the selected objects in the selection provider.

        items = List
            Selected objects.

    class apptools.selection.i_selection.ISelection
        Bases: traits.has_traits.Interface

        Collection of selected items.

        is_empty()
            Is the selection empty?

        provider_id = Str
            ID of the selection provider that created this selection object.

  list_selection Module

    class apptools.selection.list_selection.ListSelection
        Bases: traits.has_traits.HasTraits

        Selection for ordered sequences of items.

        This is the default implementation of the IListSelection interface.

        classmethod from_available_items(provider_id, selected, all_items)
            Create a list selection given a list of all available items.

            Fills in the required information (in particular, the indices) based on a list of selected items and a list of all available items.

            NOTE:

                    •   The list of available items must not contain any duplicate items.

                    •   It is expected that selected is populated by items in all_items.

        indices = List
            Indices of the selected objects in the selection provider.

        is_empty()
            Is the selection empty?

        items = List
            Selected objects.

        provider_id = Str
            ID of the selection provider that created this selection object.

  errors Module

    exception apptools.selection.errors.IDConflictError(provider_id)
        Bases: Exception

        Raised when a provider is added and its ID is already registered.

    exception apptools.selection.errors.ListenerNotConnectedError(provider_id, listener)
        Bases: Exception

        Raised when a listener that was never connected is disconnected.

    exception apptools.selection.errors.ProviderNotRegisteredError(provider_id)
        Bases: Exception

        Raised when a provider is requested by ID and not found.

    •   search

AUTHOR
Enthought

COPYRIGHT
2008-2020, Enthought