kabaret.flow-extensions


Namekabaret.flow-extensions JSON
Version 1.0.0 PyPI version JSON
download
home_pagehttps://gitlab.com/kabaretstudio/kabaret.flow_extensions
SummaryKabaret extension to create and activate relations at arbitrary locations in the flow
upload_time2024-04-26 11:06:13
maintainerNone
docs_urlNone
authorBaptiste Delos, Flavio Perez, Valentin Braem, Damien "dee" Coureau, Steeve Vincent
requires_python>=3.8
licenseLGPLv3+
keywords kabaret pipeline dataflow workflow
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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"
}
        
Elapsed time: 2.95949s