Robot Chef version 2: Use lml to write a shared library

In previous chapter, lml was used to split all in one Robot Chef into one core package and several plugins module and packages. In this chapter, we are going to go one step further to split the core package into two so as to showcase how to use lml to write a shared api library.

_images/robotchef_api_crd.svg

Demo

Please checkout the following examples:

$ virtualenv --no-site-packages robotchefv2
$ source robotchefv2/bin/activate
$ git clone https://github.com/python-lml/robotchef_v2
$ cd robotchef_v2
$ python setup.py install
$ cd ..
$ git clone https://github.com/python-lml/robotchef_api
$ cd robotchef_api
$ python setup.py install

And then you can type in and test the second version of Robot Chef:

$ robotchef_v2 "Portable Battery"
I can cook Portable Battery for robots
$ robotchef_v2 "Jacket Potato"
I do not know how to cook Jacket Potato

In order to add “Jacket Potato” in the know-how, you would need to install robotchef_britishcuisine in this folder:

$ git clone https://github.com/python-lml/robotchef_britishcuisine_v2
$ cd robotchef_britishcuisine_v2
$ python setup.py install
$ robotchef_v2 "Jacket Potato"
I can bake Jacket Potato

Robot Chef v2 code

Let us look at main code robotchef_v2:

--- /home/docs/checkouts/readthedocs.org/user_builds/lml/checkouts/latest/examples/robotchef/robotchef/main.py
+++ /home/docs/checkouts/readthedocs.org/user_builds/lml/checkouts/latest/examples/v2/robotchef_v2/robotchef_v2/main.py
@@ -1,28 +1,11 @@
 import sys
-import logging
-import logging.config
 
-from lml.loader import scan_plugins_regex
-from robotchef.plugin import CuisineManager, NoChefException
-
-logging.basicConfig(
-    format="%(name)s:%(lineno)d - %(levelname)s - %(message)s",
-    level=logging.DEBUG,
-)
-
-BUILTINS = ["robotchef.robot_cuisine"]
+from robotchef_api import NoChefException, cuisine_manager
 
 
 def main():
     if len(sys.argv) < 2:
         sys.exit(-1)
-
-    cuisine_manager = CuisineManager()
-    scan_plugins_regex(
-        plugin_name_patterns="robotchef_",
-        pyinstaller_path="robotchef",
-        white_list=BUILTINS,
-    )
 
     food_name = sys.argv[1]
     try:

The code highlighted in red are removed from main.py and are placed into robotchef_api package. And robotchef_v2 becomes the consumer of the robotchef api.

And plugin.py and robot_cuisine has been moved to robotchef_api package.

Robot Chef API

Now let us look at robotchef_api. In the following directory listing, the plugin.py And robot_cuisine is exactly the same as the plugin.py and robot_cuisine in robotchef:

__init__.py    plugin.py       robot_cuisine

Notably, the plugin loader is put in the __init__.py:

from lml.loader import scan_plugins_regex
from robotchef_api.plugin import CuisineManager, NoChefException  # noqa: F401

BUILTINS = ["robotchef_api.robot_cuisine"]


scan_plugins_regex(
    plugin_name_patterns="^robotchef_.*$",
    pyinstaller_path=__path__,  # noqa: F821
    white_list=BUILTINS,
)
cuisine_manager = CuisineManager()

scan_plugins_regex here loads all modules that start with “robotchef_” and as well as the module robotchef_api.robot_cuisine in the white_list.

This is how you will write the main component as a library.

Built-in plugin and Standalone plugin

You may have noticed that a copy of robotchef_britishcuisine is placed in v2 directory. Why not using the same one above v2 directory? although they are almost identical, there is a minor difference. robotchef_britishcuisine in v2 directory depends on robotchef_api but the other British cuisine package depends on robotchef. Hence, if you look at the fry.py in v2 directory, you will notice a slight difference:

--- /home/docs/checkouts/readthedocs.org/user_builds/lml/checkouts/latest/examples/robotchef_britishcuisine/robotchef_britishcuisine/fry.py
+++ /home/docs/checkouts/readthedocs.org/user_builds/lml/checkouts/latest/examples/v2/robotchef_britishcuisine/robotchef_britishcuisine/fry.py
@@ -1,4 +1,4 @@
-from robotchef.plugin import Chef
+from robotchef_api.plugin import Chef
 
 
 class Fry(Chef):