Codebase list python-lml / b1ea9e5 docs / source / lml_tutorial.rst
b1ea9e5

Tree @b1ea9e5 (Download .tar.gz)

lml_tutorial.rst @b1ea9e5view markup · raw · history · blame

Robot Chef distributed in multiple packages

In previous chapter, Robot Chef was written using lml but in a single package and its plugins are loaded immediately. In this chapter, we will decouple the plugin and the main package using lml. And we will demonstrates the changes needed to plugin them back with the main package.

Demo

Do the following:

$ git clone https://github.com/python-lml/robotchef
$ cd robotchef
$ python setup.py install

The main command line interface module does simply this:

$ robotchef "Portable Battery"
I can cook Portable Battery for robots

Although it does not understand all the cuisines in the world as you see as below:

$ robotchef "Jacket Potato"
I do not know how to cook Jacket Potato

it starts to understand it once you install Chinese cuisine package to complement its knowledge:

$ git clone https://github.com/python-lml/robotchef_britishcuisine
$ cd robotchef_britishcuisine
$ python setup.py install

And then type in the following:

$ robotchef "Fish and Chips"
I can fry Fish and Chips

The more cuisine packages you install, the more dishes it understands. Here is the loading sequence:

_static/images/loading_sequence.svg

Decoupling the plugins with the main package

_static/images/robotchef_crd.svg

In order to demonstrate the capabilities of lml, Boost class is singled out and placed into an internal module robotchef.robot_cuisine. Fry and Bake are relocated to robotchef_britishcuisine package, which is separately installable. :ref:`built-in` and :ref:`standalone-plugin` will explain how to glue them up.

System Message: INFO/1 (<string>, line 53)

No role entry for "ref" in module "docutils.parsers.rst.languages.en". Trying "ref" as canonical role name.

System Message: ERROR/3 (<string>, line 53); backlink

Unknown interpreted text role "ref".

System Message: INFO/1 (<string>, line 53)

No role entry for "ref" in module "docutils.parsers.rst.languages.en". Trying "ref" as canonical role name.

System Message: ERROR/3 (<string>, line 53); backlink

Unknown interpreted text role "ref".

After the separation, in order to piece all together, a special function :meth:`lml.loader.scan_plugins` needs to be called before the plugins are used.

System Message: INFO/1 (<string>, line 58)

No role entry for "meth" in module "docutils.parsers.rst.languages.en". Trying "meth" as canonical role name.

System Message: ERROR/3 (<string>, line 58); backlink

Unknown interpreted text role "meth".

System Message: INFO/1 (<string>, line 61)

No directive entry for "literalinclude" in module "docutils.parsers.rst.languages.en". Trying "literalinclude" as canonical directive name.

System Message: ERROR/3 (<string>, line 61)

Unknown directive type "literalinclude".

.. literalinclude:: ../../examples/robotchef/robotchef/main.py
   :diff: ../../examples/robotchef_allinone_lml/robotchef_allinone_lml/main.py

What's more, :meth:`lml.loader.scan_plugins` search through all installed python modules and register plugin modules that has prefix "robotchef_".

System Message: INFO/1 (<string>, line 64)

No role entry for "meth" in module "docutils.parsers.rst.languages.en". Trying "meth" as canonical role name.

System Message: ERROR/3 (<string>, line 64); backlink

Unknown interpreted text role "meth".

The second parameter of scan_plugins is to inform pyinstaller about the package path if your package is to be packaged up using pyinstaller. white_list lists the built-ins packages.

Once scan_plugins is executed, all 'cuisine' plugins in your python path, including the built-in ones will be discovered and will be collected by :class:`~lml.plugin.PluginInfoChain` in a dictionary for :meth:`~lml.PluginManager.get_a_plugin` to look up.

System Message: INFO/1 (<string>, line 71)

No role entry for "class" in module "docutils.parsers.rst.languages.en". Trying "class" as canonical role name.

System Message: ERROR/3 (<string>, line 71); backlink

Unknown interpreted text role "class".

System Message: INFO/1 (<string>, line 71)

No role entry for "meth" in module "docutils.parsers.rst.languages.en". Trying "meth" as canonical role name.

System Message: ERROR/3 (<string>, line 71); backlink

Unknown interpreted text role "meth".

Plugin management

As you see in the class relationship diagram, There has not been any changes for CuisineManager which inherits from :class:lml.PluginManager and manages cuisine plugins. Please read the discussion in :ref:`previous chapter <cuisine_manager>`. Let us look at the plugins.

System Message: INFO/1 (<string>, line 80)

No role entry for "ref" in module "docutils.parsers.rst.languages.en". Trying "ref" as canonical role name.

System Message: ERROR/3 (<string>, line 80); backlink

Unknown interpreted text role "ref".

Built-in plugin

Boost plugin has been placed in a submodule, robotchef.robot_cuisine. Let us see how it was done. The magic lies in robot_cuisine module's __init__.py

System Message: INFO/1 (<string>, line 94)

No directive entry for "literalinclude" in module "docutils.parsers.rst.languages.en". Trying "literalinclude" as canonical directive name.

System Message: ERROR/3 (<string>, line 94)

Unknown directive type "literalinclude".

.. literalinclude:: ../../examples/robotchef/robotchef/robot_cuisine/__init__.py
  :language: python

A unnamed instance of :class:`lml.plugin.PluginInfoChain` registers the meta data internally with CuisineManager. __name__ variable refers to the module name, and in this case it equals 'robotchef.robot_cuisine'. It is used to form the absolute import path for Boost class.

System Message: INFO/1 (<string>, line 97)

No role entry for "class" in module "docutils.parsers.rst.languages.en". Trying "class" as canonical role name.

System Message: ERROR/3 (<string>, line 97); backlink

Unknown interpreted text role "class".

First parameter cuisine indicates that electrify.Boost is a cuisine plugin. lml will associate it with CuisineManager. It is why CuisineMananger has initialized as 'cuisine'. The second parameter is used the absolute import path 'robotchef.robot_cuisine.electricity.Boost'. The third parameter tags are the dictionary keys to look up class Boost.

Here is a warning: to achieve lazy loading as promised by lml, you shall avoid heavy duty loading in __init__.py. this design principle: not to import any un-necessary modules in your plugin module's __init__.py.

That's all you need to write a built-in plugin.

Standalone plugin

Before we go to examine the source code of robotchef_britishcuisine, please let me dictate that the standalone plugins shall respect the package prefix, which is set by the main package. In this case, the plugin packages shall start with 'robotchef_'. Hence for British Cuisine, it is named as 'robotchef_britishcuisine'.

Now let us have look at the module's __init__.py, you would find similar the plugin declaration code as in the following. But nothing else.

System Message: INFO/1 (<string>, line 129)

No directive entry for "literalinclude" in module "docutils.parsers.rst.languages.en". Trying "literalinclude" as canonical directive name.

System Message: ERROR/3 (<string>, line 129)

Unknown directive type "literalinclude".

.. literalinclude:: ../../examples/robotchef_britishcuisine/robotchef_britishcuisine/__init__.py
  :language: python
  :linenos:

Because we have relocated Fry and Bake in this package, the instance of :class:`~lml.plugin.PluginInfoChain` issues two chained call :meth:`~lml.plugin.PluginInfoChain.add_a_plugin` but with corresponding parameters.

System Message: INFO/1 (<string>, line 133)

No role entry for "class" in module "docutils.parsers.rst.languages.en". Trying "class" as canonical role name.

System Message: ERROR/3 (<string>, line 133); backlink

Unknown interpreted text role "class".

System Message: INFO/1 (<string>, line 133)

No role entry for "meth" in module "docutils.parsers.rst.languages.en". Trying "meth" as canonical role name.

System Message: ERROR/3 (<string>, line 133); backlink

Unknown interpreted text role "meth".

Note

In your plugin package, you can add as many plugin class as you need. And the tags can be as long as you deem necessary.

Let me wrap up this section. All you will need to do, in order to make a standalone plugin, is to provide a package installer(setup.py and other related package files) for a built-in plugin.

The end

That is all you need to make your main component to start using component based approach to expand its functionalities. Here is the takeaway for you:

  1. :class:`lml.plugin.PluginManager` is just another factory pattern that hides the complexity away.

    System Message: INFO/1 (<string>, line 154)

    No role entry for "class" in module "docutils.parsers.rst.languages.en". Trying "class" as canonical role name.

    System Message: ERROR/3 (<string>, line 154); backlink

    Unknown interpreted text role "class".

  2. You will need to call :meth:`lml.loader.scan_plugins` in your __init__.py or where appropriate before your factory class is called.

    System Message: INFO/1 (<string>, line 156)

    No role entry for "meth" in module "docutils.parsers.rst.languages.en". Trying "meth" as canonical role name.

    System Message: ERROR/3 (<string>, line 156); backlink

    Unknown interpreted text role "meth".

More standalone plugins

You are left to install robotchef_chinesecuisine and robotchef_cook yourself and explore their functionalities.

How to ask robotchef to forget British cuisine?

The management of standalone plugins are left in the hands of the user. To prevent robotchef from finding British cuisine, you can use pip to uninstall it, like this:

$ pip uninstall robotchef_britishcuisine

Docutils System Messages

System Message: ERROR/3 (<string>, line 64); backlink

Unknown target name: "robotchef".

System Message: ERROR/3 (<string>, line 120); backlink

Unknown target name: "robotchef".

System Message: INFO/1 (<string>, line 86)

Hyperlink target "builtin-plugin" is not referenced.

System Message: INFO/1 (<string>, line 115)

Hyperlink target "standaline-plugin" is not referenced.