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:
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).
Here is a list of the extensions which ship with Rattail:
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:
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.”
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.
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.
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().
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:
Extensions may also supply additional permission definitions to Rattail.
Todo
Finish the permission extension docs.
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.