# kabaret.flow_extensions
Flow extensions is a Kabaret extension which allows to extend flow objects with standalone relations (i.e. defined outside of the flow object classes). These relations are named *dynamic relations*.
### Dependencies
This module relies on versions `2.3.0` and later of Kabaret, which provide the feature to override the object manager type needed to enable extensions on objects.
## Enabling extensions
The ability of an object to accept extensions relies on its manager, which must be of type `DynamicTreeManager`. We can then control extensions using the mecanism provided in Kabaret to override the object manager type (`Object._MANAGER_TYPE`) and specify whether it must be used in the object sub-tree (`Object._PROPAGATE_MANAGER_TYPE`).
To enable extensions on an object, simply set the manager type of its class to `DynamicTreeManager`.
```python
from kabaret.flow import Object
from kabaret.flow_extensions.flow import DynamicTreeManager
class MyExtendableObject(Object):
_MANAGER_TYPE = DynamicTreeManager
```
In the above example, only `MyExtendableObject` instances can be extended. To enable extensions for all children existing in the sub-trees of these instances as well, set the value of the class attribute `_PROPAGATE_MANAGER_TYPE` to `True`.
```python
class MyExtendableProject(Object):
_MANAGER_TYPE = DynamicTreeManager
_PROPAGATE_MANAGER_TYPE = True
```
In case the use of extensions must be disabled on an object which exists in a part of the flow where extensions are enabled by default, one can reset its `_MANAGER_TYPE` to the default type `_Manager`.
```python
class MyRegularObject(Object):
_MANAGER_TYPE = _Manager
class MyExtendableObject(Object):
_MANAGER_TYPE = DynamicTreeManager
_PROPAGATE_MANAGER_TYPE = True
regular_object = flow.Child(MyRegularObject)
...
```
In the above example, all but the `MyRegularObject` child under the `MyExtendableObject` will be able to register extensions.
## Definition
### Factories
Dynamic relations must be defined in *factories*. A factory is a function which takes an `Object` instance as an argument representing an extendable object, and returns either a single relation (`_Relation` instance), a list of relations, or `None` if no relation has to be registered by the target object.
```python
def relation_factory(parent -> Object) -> [None, _Relation, list]:
...
```
Declaring a dynamic relation is done similarly as for a regular relation, except that its name must be set explicitly.
```python
def create_dynamic_param(parent):
if parent.oid() == '/my/target/oid':
relation = flow.Param('').ui(label='My dynamic param')
relation.name = 'my_dynamic_param'
return relation
```
### Ordering
Optionally, one might set the relation's index to control its position among the existing ones. The default index of a dynamic relation is `0`, which makes it appear first in the UI. When set to `None`, the index is automatically computed so that the relation appears last.
```python
relation.index = 5
```
### Overrides
When multiple relation candidates have the same name, only the one with the highest priority weight is retained. This weight is optional and can be provided by the factory:
```python
def create_relation(parent):
...
return relation, 10
```
The default value of a relation weight is `0`. When not provided, only the last found relation candidate will be available.
It is also possible to provide a negative weight to prevent a dynamic relation from overriding an existing base relation.
### Installers
Installers are functions called by the manager of an object to retrieve its dynamic relations. An installer takes the current session as an argument, and returns a dictionary which maps a list of relation factories with the name of the extension.
Example:
```python
def install_extensions(session):
if session.session_uid().split(':', 1)[0] = 'my_user_name':
return {
"my_extension": [
create_dynamic_param,
...
],
...
}
```
In order for installers to be accessible to the manager, they must be provided in the environment variable `KABARET_FLOW_EXT_INSTALLERS` following the pattern: `<module_qualified_name>:<installer_name>[;<module_qualified_name>:<installer_name>...]`.
## Known issues
Instanciation of objects referenced by native relations obeys to the principle of *lazy evaluation*: objects are not instanciated until the relations are accessed.
The extension system currently drops this mecanism, in two points:
- child objects referenced by dynamic relations are instanciated as soon as their parent is instanciated.
- child objects themselves are set as attributes of their parent (instead of the generating relations).
The issues listed below stem from this change of behaviour. We propose a workaround for each whenever possible.
- **Infinite extension branch.** Given an extendable flow object, all objects in its sub-tree, including children generated by dynamic relations, will also accept extensions unless their manager type is set back to Kabaret's default manager type `_Manager`. It means that as long as factories return relations, an extension object can itself registers dynamic relations, leading to an infinite creation of dynamic relations.
Workaround: explicitly set the manager type of the dynamic relation's related types to `_Manager`.
```python
class MyExtensionObject(flow.Object):
_MANAGER_TYPE = _Manager # prevent extension from registering extensions itself
```
- **Invalid calls to `ProjectRoot.project()`.** When the `parent` object is the project instance, any call to `Object.root().project()` currently raises an `AttributeError`. This is because the project is not instanciated yet at the time the factory is called, hence it is not available as an attribute of the `ProjectRoot` instance.
Workarounds:
- get the project instance (from an object): use an explicit `Parent` relation pointing to the project
- check if the `parent` extension target is the project instance (in a factory): exploit the fact that the project *oid* equals to its name preceded by `/`.
```python
if parent.oid() == '/'+parent.name():
...
```
As a consequence of the second point, nothing prevents from overwriting a child object since it is a regular Python attribute.
# Changelog
## [1.0.0] - 2024-04-26
### Added
issue #1: Manage flow extensions
## [0.0.1] - 2024-04-26
Initial commit
Raw data
{
"_id": null,
"home_page": "https://gitlab.com/kabaretstudio/kabaret.flow_extensions",
"name": "kabaret.flow-extensions",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "kabaret pipeline dataflow workflow",
"author": "Baptiste Delos, Flavio Perez, Valentin Braem, Damien \"dee\" Coureau, Steeve Vincent",
"author_email": "baptiste@les-fees-speciales.coop, flavio@lfs.coop, valentinbraem1@gmail.com, kabaret-dev@googlegroups.com, steeve.v91@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/b1/a5/15f26f33ba5530a6188a90caba8493b86d4010affa4a3c8179574121368c/kabaret_flow_extensions-1.0.0.tar.gz",
"platform": null,
"description": "# kabaret.flow_extensions\n\nFlow extensions is a Kabaret extension which allows to extend flow objects with standalone relations (i.e. defined outside of the flow object classes). These relations are named *dynamic relations*.\n\n### Dependencies\n\nThis module relies on versions `2.3.0` and later of Kabaret, which provide the feature to override the object manager type needed to enable extensions on objects.\n\n## Enabling extensions\n\nThe ability of an object to accept extensions relies on its manager, which must be of type `DynamicTreeManager`. We can then control extensions using the mecanism provided in Kabaret to override the object manager type (`Object._MANAGER_TYPE`) and specify whether it must be used in the object sub-tree (`Object._PROPAGATE_MANAGER_TYPE`).\n\nTo enable extensions on an object, simply set the manager type of its class to `DynamicTreeManager`.\n\n```python\nfrom kabaret.flow import Object\nfrom kabaret.flow_extensions.flow import DynamicTreeManager\n\nclass MyExtendableObject(Object):\n _MANAGER_TYPE = DynamicTreeManager\n```\n\nIn the above example, only `MyExtendableObject` instances can be extended. To enable extensions for all children existing in the sub-trees of these instances as well, set the value of the class attribute `_PROPAGATE_MANAGER_TYPE` to `True`.\n\n```python\nclass MyExtendableProject(Object):\n _MANAGER_TYPE = DynamicTreeManager\n _PROPAGATE_MANAGER_TYPE = True\n```\n\nIn case the use of extensions must be disabled on an object which exists in a part of the flow where extensions are enabled by default, one can reset its `_MANAGER_TYPE` to the default type `_Manager`.\n\n```python\nclass MyRegularObject(Object):\n _MANAGER_TYPE = _Manager\n\nclass MyExtendableObject(Object):\n _MANAGER_TYPE = DynamicTreeManager\n _PROPAGATE_MANAGER_TYPE = True\n\n regular_object = flow.Child(MyRegularObject)\n ...\n```\nIn the above example, all but the `MyRegularObject` child under the `MyExtendableObject` will be able to register extensions.\n\n## Definition\n\n### Factories\n\nDynamic relations must be defined in *factories*. A factory is a function which takes an `Object` instance as an argument representing an extendable object, and returns either a single relation (`_Relation` instance), a list of relations, or `None` if no relation has to be registered by the target object.\n\n```python\ndef relation_factory(parent -> Object) -> [None, _Relation, list]: \n ...\n```\n\nDeclaring a dynamic relation is done similarly as for a regular relation, except that its name must be set explicitly.\n\n```python\ndef create_dynamic_param(parent): \n if parent.oid() == '/my/target/oid':\n relation = flow.Param('').ui(label='My dynamic param')\n relation.name = 'my_dynamic_param'\n return relation\n```\n\n### Ordering\n\nOptionally, one might set the relation's index to control its position among the existing ones. The default index of a dynamic relation is `0`, which makes it appear first in the UI. When set to `None`, the index is automatically computed so that the relation appears last.\n\n```python\nrelation.index = 5\n```\n\n### Overrides\n\nWhen multiple relation candidates have the same name, only the one with the highest priority weight is retained. This weight is optional and can be provided by the factory:\n\n```python\ndef create_relation(parent):\n ...\n return relation, 10\n```\n\nThe default value of a relation weight is `0`. When not provided, only the last found relation candidate will be available.\n\nIt is also possible to provide a negative weight to prevent a dynamic relation from overriding an existing base relation.\n\n### Installers\n\nInstallers are functions called by the manager of an object to retrieve its dynamic relations. An installer takes the current session as an argument, and returns a dictionary which maps a list of relation factories with the name of the extension.\n\nExample:\n\n```python\ndef install_extensions(session): \n if session.session_uid().split(':', 1)[0] = 'my_user_name':\n return {\n \"my_extension\": [\n create_dynamic_param,\n ...\n ],\n ...\n }\n```\n\nIn order for installers to be accessible to the manager, they must be provided in the environment variable `KABARET_FLOW_EXT_INSTALLERS` following the pattern: `<module_qualified_name>:<installer_name>[;<module_qualified_name>:<installer_name>...]`.\n\n## Known issues\n\nInstanciation of objects referenced by native relations obeys to the principle of *lazy evaluation*: objects are not instanciated until the relations are accessed.\nThe extension system currently drops this mecanism, in two points:\n- child objects referenced by dynamic relations are instanciated as soon as their parent is instanciated.\n- child objects themselves are set as attributes of their parent (instead of the generating relations).\n\nThe issues listed below stem from this change of behaviour. We propose a workaround for each whenever possible.\n\n- **Infinite extension branch.** Given an extendable flow object, all objects in its sub-tree, including children generated by dynamic relations, will also accept extensions unless their manager type is set back to Kabaret's default manager type `_Manager`. It means that as long as factories return relations, an extension object can itself registers dynamic relations, leading to an infinite creation of dynamic relations.\n \n Workaround: explicitly set the manager type of the dynamic relation's related types to `_Manager`.\n\n ```python\n class MyExtensionObject(flow.Object):\n _MANAGER_TYPE = _Manager # prevent extension from registering extensions itself\n ```\n\n- **Invalid calls to `ProjectRoot.project()`.** When the `parent` object is the project instance, any call to `Object.root().project()` currently raises an `AttributeError`. This is because the project is not instanciated yet at the time the factory is called, hence it is not available as an attribute of the `ProjectRoot` instance.\n\n Workarounds:\n - get the project instance (from an object): use an explicit `Parent` relation pointing to the project\n - check if the `parent` extension target is the project instance (in a factory): exploit the fact that the project *oid* equals to its name preceded by `/`.\n ```python\n if parent.oid() == '/'+parent.name():\n ...\n ```\n\nAs a consequence of the second point, nothing prevents from overwriting a child object since it is a regular Python attribute.\n\n# Changelog\n\n## [1.0.0] - 2024-04-26\n\n### Added\n\nissue #1: Manage flow extensions\n\n## [0.0.1] - 2024-04-26\n\nInitial commit\n",
"bugtrack_url": null,
"license": "LGPLv3+",
"summary": "Kabaret extension to create and activate relations at arbitrary locations in the flow",
"version": "1.0.0",
"project_urls": {
"Homepage": "https://gitlab.com/kabaretstudio/kabaret.flow_extensions"
},
"split_keywords": [
"kabaret",
"pipeline",
"dataflow",
"workflow"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "b1a515f26f33ba5530a6188a90caba8493b86d4010affa4a3c8179574121368c",
"md5": "3ca068519052fdd6806a521611dd9a66",
"sha256": "8c3de5e792a484f850fb02f15cceb7402f35ff0670f3de2b45c6092b4ff041b8"
},
"downloads": -1,
"filename": "kabaret_flow_extensions-1.0.0.tar.gz",
"has_sig": false,
"md5_digest": "3ca068519052fdd6806a521611dd9a66",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 34180,
"upload_time": "2024-04-26T11:06:13",
"upload_time_iso_8601": "2024-04-26T11:06:13.260794Z",
"url": "https://files.pythonhosted.org/packages/b1/a5/15f26f33ba5530a6188a90caba8493b86d4010affa4a3c8179574121368c/kabaret_flow_extensions-1.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-04-26 11:06:13",
"github": false,
"gitlab": true,
"bitbucket": false,
"codeberg": false,
"gitlab_user": "kabaretstudio",
"gitlab_project": "kabaret.flow_extensions",
"lcname": "kabaret.flow-extensions"
}