Rattail Extensions

Rattail “extensions” exist primarily to allow for custom data to be stored and accessed. An extension may extend the Rattail framework in one or more of the following ways:

  • adding new tables
  • adding new enumerations
  • adding new mapped classes
  • adding new properties to existing mapped classes
  • adding new permission definitions

Some extensions will add significantly to Rattail (e.g. the orders extension, which adds enumerations, classes and permissions), while others may add as little as a single property to a core class (e.g. for the sake of object tracking with ETL).

Available Extensions

Here is a list of the extensions which ship with Rattail:

Creating an Extension

It is very possible that you will need to create an extension of your own. For example, many ETL configurations will need a custom key field (per ETLed class) to track objects between jobs. There are basically two things you need to do in order to create your extension:

  1. Subclass rattail.FrameworkExtension.
  2. Advertise your extension with a setuptools entry point.

See the rattail.FrameworkExtension API documentation as well as the rest of the current document for more information regarding the first point. As for the second:

Rattail extensions are advertised via the setuptools entry point group of rattail.extensions (see setuptools’ documentation for more info). For example, an extension might be advertised with the following entry point definition:

setup(
     # bunch of stuff...

     entry_points = """
        [rattail.extensions]
        orders = rattail.ext.orders:OrdersExtension
        """
     )

In addition to creating your extension, there must be some application logic somewhere which takes advantage of the functionality it provides. In the typical ETL case for example, there will be at least one script (also yours) which references say, a custom key field provided by your extension.

The extension must be activated within your Rattail database before it may be used in an application, however. This can be done with the command:

rattail ext activate <your_extension>

Some applications (e.g. Rattail BONE) are able to activate any extensions which they require as part of their own setup process, so it is not always necessary to activate extensions “manually.”

Adding Tables

An extension may (and usually will) need to track extra data in order to add some additional functionality. Extensions are only allowed to add new tables to the schema though; they are not allowed to alter existing tables in any way. This means for instance that even if you just need to add a single attribute to the Product class (e.g. Product.smell), your custom extension still must create a table (e.g. product_smells) to store the values, instead of adding a column to the products table.

An extension is not required to define any new tables, but if it wishes to then it must return a sqlalchemy.MetaData instance containing the table definitions whenever its rattail.FrameworkExtension.get_metadata() method is called.

Note

Due to the way Rattail uses this metadata, it must be returned “freshly-created” any time the method is called; i.e. the extension must never return a previously-existing MetaData instance.

When an extension is initially “activated”, Rattail will create any of the tables defined by the extension before calling on it to extend the running framework. Similarly, when an extension is “deactivated”, Rattail will first call on it to restore the running framework, and then will drop any tables the extension provides.

Any extension which provides metadata must also provide a SQLAlchemy-Migrate repository for the sake of schema migrations specific to the extension. The Python module representing this repository must be referenced by the extension’s rattail.FrameworkExtension.schema attribute.

Note

Rattail itself can apply any migrations necessary for the extension; see the rattail.db.upgrade_schema() function.

Adding Enumerations

Sometimes class attributes will need to map to enumeration values specific to the domain (and possibly to the organization). This usually (always?) will have to do with workflows; for use in defining things such Order.status and OrderEvent.type.

Currently such attributes are represented as Integer fields in the underlying schema. Each field should only be allowed to contain one of the predefined enumeration values relevant to it. These values are not defined in the database however, and must be defined directly in the Python code.

The rattail.enum module is where these enumeration values are held after rattail.init() has been called. For an extension to get its definitions to show up there, all it needs to do is provide a rattail.FrameworkExtension.enum attribute which references its own module containing the definitions. Rattail will handle importing each variable found in that module to its own rattail.enum namespace, and after all active extensions have been applied, everything in rattail.enum will be imported to the rattail root namespace.

Adding Classes

An extension may also provide additional (SQLAlchemy-mapped) classes to be imported to the root rattail namespace. Such classes will then be available in the same way as those provided by the core. (See rattail.classes Module for more info on the core classes.)

Classes should be added whenever the extension’s rattail.FrameworkExtension.extend_classes() method is called. There is a convenience method (rattail.FrameworkExtension.add_class()) which accepts as its argument a class that you wish to add to Rattail, so a typical example might be:

def extend_classes(self):
    self.add_class(MyNewClass)

Note

It is important for such classes to not yet be mapped when they’re added here; the mappings should happen in rattail.FrameworkExtension.extend_mappers().

Altering Class Mappings

Extensions have the option to alter any class mappings whenever their rattail.FrameworkExtension.extend_mappers() method is called. It is here that the extension should apply mappers to any extra classes which it provides during rattail.FrameworkExtension.extend_classes().

However, any of Rattail’s core classes may have their mappers extended as well. A common pattern for an extension needing to do this might be:

  1. Define a table to provide extra fields for a core class.
  2. Define a class to join the core class to the custom fields.
  3. Alter the core class mapper to add the custom fields as properties.

Adding Permission Definitions

Extensions may also supply additional permission definitions to Rattail.

Todo

Finish the permission extension docs.

Restoring the Framework

Please note that while extending the framework is the primary function of extensions, it is just as important that they reverse their own changes when asked. Rattail is able to do most of this on its own, but when the extension’s rattail.FrameworkExtension.restore_classes() method is called it must remove any extra classes it previously had added. Again, there is a convenience method available to help out (rattail.FrameworkExtension.remove_class()). A typical example would be:

def restore_classes(self):
    self.remove_class("MyNewClass")

Note

While rattail.FrameworkExtension.add_class() expects a real class, rattail.FrameworkExtension.remove_class() expects a class name.