Abstract Factory lends itself well to systems that
need to scale quickly and safely, allowing users to build and interact with code constructs through names.
Typically, additional functionality is required to ensure this design fits into a framework well, and even more work
to allow for speedy iteration and development.
abstract_factories
is a collection of classes to support this design with those additional conveniences built-in.
- automatic (dynamic) registration from paths or modules
- simple or conditional querying
- versioning
- type or instance items
and more.
Imagine you have a simple action you want to apply, already using the Abstract Factory design.
from .abstracts import ActionAbstract
class DemoAction(ActionAbstract):
Version = 1
def apply(self):
print('Applied Demo.')
You then need to create a new action that addresses a bug BUT its old behavior is still required in places.
You can use abstract_factories
to provide the correct version contextually.
from .abstracts import ActionAbstract
class MyAction(ActionAbstract):
Version = 2
def apply(self):
logging.info('Applied Demo.')
Now we just create a tool factory and tell it where to find these tools.
import abstract_factories
from .abstracts import ActionAbstract
from . import actions
tool_factory = abstract_factories.AbstractTypeFactory(ActionAbstract, version_key='Version')
tool_factory.register_module(actions)
demo_action = tool_factory.get('DemoAction') # Automatically retrieves latest.
old_demo_action = tool_factory.get('DemoAction', version=1)
See rig factory.
Rig components can often be updated to address bugs, improve performance or introduce features.
A common unintentional side effect is introducing behavioural regressions or different results.
abstract_factories
encourages the use of versioning to "soft-lock" components so when a change is necessary, it
can be done safely. The new rig component version is used by default, but the previous versions are still accessible at
the same time.
Better yet, the Abstract Factory design simplifies serialization and deserialization of the data, so older
rigs can still be built as they were whilst being aware of the potential to upgrade.
See simple_validation.
Asset validation is relatively simple to implement, but increasingly difficult to manage during a production.
Some validation frameworks, like Pyblish manages this well.
abstract_factories
provides the minimum required to build your own similar Validation framework. Its
item auto-registration provides a very flexible environment to quickly develop, improve, iterate and scale
as you see fit.
Clone this repo or access it from PyPI;
pip install abstract-factories
Initialize AbstractTypeFactory or AbstractInstanceFactory with an abstract type.
Optionally, provide the attribute/method name to identify items by name (and optionally version).
Registering items can be done directly.
from abstract_factories import AbstractTypeFactory
class AbstractVehicle(object):
def start(self):
raise NotImplementedError()
class Car(AbstractVehicle):
def start(self):
print('Vrooom...')
# Type Factory
type_factory = AbstractTypeFactory(AbstractVehicle)
type_factory.register_item(Car)
assert type_factory.get('Car') is Car
By default, items are referred to by class name, unless a name_key is provided.
Abstract factories can automatically register items found in given python modules or paths.
from abstract_factories import AbstractTypeFactory
from . import my_vehicle_package
# Type Factory
type_factory = AbstractTypeFactory(my_vehicle_package.AbstractVehicle)
# Find any AbstractVehicle subclasses in `my_vehicle_package` and register them.
type_factory.register_module(my_vehicle_package)
assert type_factory.get('Car') is my_vehicle_package.Car
# Can also find any AbstractVehicle subclasses in a directory and register those too.
type_factory.register_path('c:/Users/user/downloads/other_vehicles')
In some use-cases, instances are a much better fit for the type of data you want to use in your factory (a factory of factories?).
In that case, use AbstractInstanceFactory
.
from abstract_factories import AbstractInstanceFactory
class AbstractVehicle(object):
def __init__(self, make=None):
self.make = make
def start(self):
raise NotImplementedError()
class Car(AbstractVehicle):
def start(self):
print('Vrooom...')
# Instance Factory
honda = Car('Honda')
instance_factory = AbstractInstanceFactory(AbstractVehicle, name_key='make')
instance_factory.register_item(honda)
assert instance_factory.get('Honda') is honda
Register viable items directly.
type_factory.register_item(AbstractSubclass)
instance_factory.register_item(AbstractSubclass())
Find and register any viable items found in the module's locals.
type_factory/instance_factory.register_module(module)
Find and register any viable items found in any nested python file from a dynamic import. Some limitation using relative imports.
type_factory/instance_factory.register_path(r'c:/tools/tool_plugins')
type_factory/instance_factory.register_path(r'c:/tools/tool_plugins/plugin.py')
Instead of a str
type name_key
or version_key
value, you can instead provide a callable. This will be used to
determine each item's name and/or version.
This is especially useful when the context of an item's name or version lies outside the Factory's remit.
Abstract factories is influenced by https://github.com/mikemalinowski/factories.
This project is licensed under the MIT License.