Bundle Example: Hello World

This example describes how to create a ChimeraX bundle that defines a new command, hello. The steps in implementing the bundle are:

  1. Create a bundle_info.xml containing information about the bundle,

  2. Create a Python package that interfaces with ChimeraX and implements the command functionality, and

  3. Install and test the bundle in ChimeraX.

The final step builds a Python wheel that ChimeraX uses to install the bundle. So if the bundle passes testing, it is immediately available for sharing with other users.

Source Code Organization

The source code for this example may be downloaded as a zip-format file containing a folder named hello_world. Alternatively, one can start with an empty folder and create source files based on the samples below. The source folder may be arbitrarily named, as it is only used during installation; however, avoiding whitespace characters in the folder name bypasses the need to type quote characters in some steps.

Sample Files

The files in the source code folder are:

  • hello_world - bundle folder
    • bundle_info.xml - bundle information read by ChimeraX

    • src - source code to Python package for bundle
      • __init__.py - package initializer and interface to ChimeraX

      • cmd.py - source code to implement hello command

The file contents are shown below.

bundle_info.xml

bundle_info.xml is an eXtensible Markup Language format file whose tags are listed in Bundle Information XML Tags. While there are many tags defined, only a few are needed for bundles written completely in Python.

 1<!--
 2ChimeraX bundle names must start with "ChimeraX-"
 3to avoid clashes with package names in pypi.python.org.
 4When uploaded to the ChimeraX toolshed, the bundle
 5will be displayed without the ChimeraX- prefix.
 6-->
 7
 8<BundleInfo name="ChimeraX-HelloWorld"
 9	    version="0.1" package="chimerax.hello_world"
10  	    minSessionVersion="1" maxSessionVersion="1">
11
12  <!-- Additional information about bundle source -->
13  <Author>UCSF RBVI</Author>
14  <Email>chimerax@cgl.ucsf.edu</Email>
15  <URL>https://www.rbvi.ucsf.edu/chimerax/</URL>
16
17  <!-- Synopsis is a one-line description
18       Description is a full multi-line description -->
19  <Synopsis>Basic example for adding a command</Synopsis>
20  <Description>Basic example code for implementing ChimeraX bundle.
21
22Implements command "hello" to print the string "hello world" in the log.
23  </Description>
24
25  <!-- Categories is a list where this bundle should appear -->
26  <Categories>
27    <Category name="General"/>
28  </Categories>
29
30  <!-- Dependencies on other ChimeraX/Python packages -->
31  <Dependencies>
32    <Dependency name="ChimeraX-Core" version="~=1.1"/>
33  </Dependencies>
34
35  <!-- Python and ChimeraX-specific classifiers
36       From https://pypi.python.org/pypi?%3Aaction=list_classifiers
37       Some Python classifiers are always inserted by the build process.
38       These include the Environment and Operating System classifiers
39       as well as:
40         Framework :: ChimeraX
41         Intended Audience :: Science/Research
42         Programming Language :: Python :: 3
43         Topic :: Scientific/Engineering :: Visualization
44         Topic :: Scientific/Engineering :: Chemistry
45         Topic :: Scientific/Engineering :: Bio-Informatics
46       The "ChimeraX :: Bundle" classifier is also supplied automatically.  -->
47  <Classifiers>
48    <!-- Development Status should be compatible with bundle version number -->
49    <PythonClassifier>Development Status :: 3 - Alpha</PythonClassifier>
50    <PythonClassifier>License :: Freeware</PythonClassifier>
51    <!-- ChimeraX classifiers describe supplied functionality -->
52    <ChimeraXClassifier>ChimeraX :: Command :: hello :: General ::
53	Print "hello world" in the log</ChimeraXClassifier>
54  </Classifiers>
55
56</BundleInfo>

The document tag (which contains all other tags) is named BundleInfo, whose required attributes are:

  • name: the name of the bundle,

  • version: version of the bundles, usually in the form of major.minor.patch,

  • package: the name of the Python package where ChimeraX can find the code for this bundle, and

  • minSessionVersion and maxSessionVersion: the minimum and maximum session file versions that the bundle supports.

The next few tags supply information about who wrote the bundle, where to find more information on the web, as well as short and long descriptions of what functionality the bundle provides.

The Category tags list the categories to which the bundle belong. These Category values are used by the ChimeraX Toolshed when the bundle is contributed to the repository. (Note that these values are completely distinct from the category values described below in ChimeraXClassifier.)

The Dependency tags list the bundles that must be installed for this bundle to work. The ChimeraX-Core bundle is a pre-installed bundle that provides much of ChimeraX functionality. For alpha and beta releases, the version number will start from “0.1” and slowly approach “1.0”. Because ChimeraX Python API follows semantic versioning rules (newer versions of ChimeraX are compatible with older ones with the same major version number), bundles written for earlier versions of ChimeraX will typically work in later versions as well. This is indicated by the ~= in the version attribute of the Dependency tag for ChimeraX-Core. A Dependency tag should be present for each additional bundle that must be installed. During installation for this bundle, if any of the bundles listed in Dependency tags are missing, they are automatically installed as well.

A Dependency tag can also list non-ChimeraX PyPi packages needed by the bundle, such as pandas or pytorch, along with required version numbers, and such packages will be installed with the bundle.

Finally, there are Classifier tags, of which there are two flavors: Python and ChimeraX. Values for Python classifiers are the same as those found in standard Python package setup scripts. Values for ChimeraXClassifier tags classifiers follow the same form as Python classifiers, using :: as separators among data fields. The first data field must be the string ChimeraX. The second field specifies the type of functionality supplied, in this case, a command. For command classifiers, the third field is the name of the command, in this case, hello. The fourth field for command classifiers is its category, in this case, General. (The category for a command is reserved for future use but does not currently affect ChimeraX behavior.) The final data field for command classifiers is a synopsis of what the command does, and is shown as help text in the ChimeraX interface.

Commands may be a single word or multiple words. The latter is useful for grouping multiple commands by sharing the same first word. ChimeraX also automatically support unambiguous prefixes as abbreviations. For example, the user can use hel as an abbreviation for hello if no other command begins with hel; however, h is not an abbreviation because the hide command also starts with h.

All bundle functionality must be listed in in ChimeraX classifiers in order for ChimeraX to integrate them into its user interface. In this example, the bundle only provides a single new command-line interface command. Reference documentation for bundle information tags, and specifically ChimeraX classifiers, is in Bundle Information XML Tags.

src

src is the folder containing the source code for the Python package that implements the bundle functionality. The ChimeraX devel command, used for building and installing bundles, automatically includes all .py files in src as part of the bundle. (Additional files may also be included using bundle information tags such as DataFiles as shown in Bundle Example: Add a Tool.) The only required file in src is __init__.py. Other .py files are typically arranged to implement different types of functionality. For example, cmd.py is used for command-line commands; tool.py or gui.py for graphical interfaces; io.py for reading and saving files, etc.

src/__init__.py

 1# vim: set expandtab shiftwidth=4 softtabstop=4:
 2
 3from chimerax.core.toolshed import BundleAPI
 4
 5
 6# Subclass from chimerax.core.toolshed.BundleAPI and
 7# override the method for registering commands,
 8# inheriting all other methods from the base class.
 9class _MyAPI(BundleAPI):
10
11    api_version = 1     # register_command called with BundleInfo and
12                        # CommandInfo instance instead of command name
13                        # (when api_version==0)
14
15    # Override method
16    @staticmethod
17    def register_command(bi, ci, logger):
18        # bi is an instance of chimerax.core.toolshed.BundleInfo
19        # ci is an instance of chimerax.core.toolshed.CommandInfo
20        # logger is an instance of chimerax.core.logger.Logger
21
22        # This method is called once for each command listed
23        # in bundle_info.xml.  Since we only listed one command,
24        # we expect only a single call to this method.
25
26        # We import the function to call and its argument
27        # description from the ``cmd`` module, adding a
28        # synopsis from bundle_info.xml if none is supplied
29        # by the code.
30        from . import cmd
31        desc = cmd.hello_world_desc
32        if desc.synopsis is None:
33            desc.synopsis = ci.synopsis
34
35        # We then register the function as the command callback
36        # with the chimerax.core.commands module.
37        # Note that the command name registered is not hardwired,
38        # but actually comes from bundle_info.xml.  In this example,
39        # the command name is "hello", not "hello world".
40        from chimerax.core.commands import register
41        register(ci.name, desc, cmd.hello_world)
42
43
44# Create the ``bundle_api`` object that ChimeraX expects.
45bundle_api = _MyAPI()

__init__.py contains the initialization code that defines the bundle_api object that ChimeraX needs in order to invoke bundle functionality. ChimeraX expects bundle_api class to be derived from chimerax.core.toolshed.BundleAPI, which has one public attribute, api_version, and these methods:

  • initialize - invoked when ChimeraX starts up and the bundle needs custom initialization

  • finish - invoked when ChimeraX exits and the bundle needs custom clean up

  • register_command - invoked the first time a bundle command is used

  • register_selector - invoked the first time a bundle chemical subgroup selector is used

  • start_tool - invoked to display a bundle graphical interface

  • open_file - invoked when a file of a bundle-supported format is opened

  • save_file - invoked when a file of a bundle-supported format is saved

  • fetch_from_database - invoked when an entry is fetched from a network database

  • get_class - invoked when a session is saved and a bundle object needs to be serialized

The api_version attribute should be set to 1. The default value for api_version is 0 and is supported for older bundles. New bundles should always use the latest supported API version.

This example only provides a single command, so the only method that needs to be overridden is register_command. The other methods should never be called because there are no ChimeraXClassifier tags in bundle_info.xml that mention other types of functionality.

register_command is called once for each command listed in a ChimeraXClassifier tag. When ChimeraX starts up, it registers a placeholder for each command in all bundles, but normally does import the bundles. When a command is used and ChimeraX detects that it is actually a placeholder, it asks the bundle to register the run-time information regarding what arguments are expected and which function should be called to process the command, after which the command line is parsed and the registered function is called. Once a command is registered, ChimeraX will not call register_command for it again.

In BundleAPI version 1, the register_command method is called with three arguments:

bi provides access to bundle information such as its name, version, and description. For this example, no bundle information is required and bi is unused. ci provides access to command information, and the two attributes used are synopsis (for setting help text if none is provided in code) and name (for notifying ChimeraX of what function to use to process the command). logger may be used to notify users of warnings and errors; in this example, errors will be handled by the normal Python exception machinery.

The most important line of code in register_command is the call to chimerax.core.commands.register(), whose arguments are:

  • name - a Python string for the command name,

  • cmd_desc - an instance of chimerax.core.commands.CmdDesc which describes what command line arguments are expected, and

  • function - a Python function to process the command.

In this example, the command name comes from the command information instance, ci.name. Both the argument description and the Python function are defined in another package module: cmd.py. The argument description comes from cmd.hello_world_desc, possibly augmented with help text from ci.synopsis. The command-processing function also comes from the same module, cmd.hello_world. The arguments that cmd.hello_world will be called with are determined by the attributes of cmd.hello_world_desc and is described below.

Note that register_command and other BundleAPI methods are static methods and are not associated with the bundle_api instance. The intent is that these methods remain simple and should not need other data. If necessary, data can be stored as attributes of bundle_api and the static methods can refer to the instance explicitly.

src/cmd.py

 1# vim: set expandtab shiftwidth=4 softtabstop=4:
 2
 3from chimerax.core.commands import CmdDesc
 4
 5
 6def hello_world(session):
 7    # All command functions are invoked with ``session`` as its
 8    # first argument.  Useful session attributes include:
 9    #   logger: chimerax.core.logger.Logger instance
10    #   models: chimerax.core.models.Models instance
11    session.logger.info("Hello world!")
12
13# CmdDesc contains the command description.  For the
14# "hello" command, we expect no arguments.
15hello_world_desc = CmdDesc()

To implement the hello command, two components are needed: a function that prints Hello world! to the ChimeraX log, and a description to register so that ChimeraX knows how to parse the typed command text and call the function with the appropriate arguments. In this simple example, hello_world is the name of the function and hello_world_desc is the description for the command. (Note that the function and description names need not match the command name.)

hello_world_desc, the command description, is an instance of chimerax.core.commands.CmdDesc. No arguments are passed to the constructor, meaning the user should not type anything after the command name. If additional text is entered after the command, ChimeraX will flag that as an error and display an error message without invoking the hello_world function.

If the command is entered correctly, ChimeraX calls the hello_world function with a single argument, session, which provides access to session data such as the open models and current selection. For this example, hello_world uses the session logger, an instance of chimerax.core.logger.Logger, to display the informational message “Hello world!” The message is displayed in the log window when the ChimeraX graphical interface is displayed; otherwise, it is printed to the console.

Later tutorials will discuss how to use the command description to inform ChimeraX how to convert input text to Python values and map them to arguments when calling the command-processing function.

Building and Testing Bundles

To build a bundle, start ChimeraX and execute the command:

devel build PATH_TO_SOURCE_CODE_FOLDER

Python source code and other resource files are copied into a build sub-folder below the source code folder. C/C++ source files, if any, are compiled and also copied into the build folder. The files in build are then assembled into a Python wheel in the dist sub-folder. The file with the .whl extension in the dist folder is the ChimeraX bundle.

To test the bundle, execute the ChimeraX command:

devel install PATH_TO_SOURCE_CODE_FOLDER

This will build the bundle, if necessary, and install the bundle in ChimeraX. Bundle functionality should be available immediately.

To remove temporary files created while building the bundle, execute the ChimeraX command:

devel clean PATH_TO_SOURCE_CODE_FOLDER

Some files, such as the bundle itself, may still remain and need to be removed manually.

Building bundles as part of a batch process is straightforward, as these ChimeraX commands may be invoked directly by using commands such as:

ChimeraX --nogui --exit --cmd 'devel install PATH_TO_SOURCE_CODE_FOLDER exit true'

This example executes the devel install command without displaying a graphics window (--nogui) and exits immediately after installation (exit true). The initial --exit flag guarantees that ChimeraX will exit even if installation fails for some reason.

Distributing Bundles

With ChimeraX bundles being packaged as standard Python wheel-format files, they can be distributed as plain files and installed using the ChimeraX toolshed install command. Thus, electronic mail, web sites and file sharing services can all be used to distribute ChimeraX bundles.

Private distributions are most useful during bundle development, when circulation may be limited to testers. When bundles are ready for public release, they can be published on the ChimeraX Toolshed, which is designed to help developers by eliminating the need for custom distribution channels, and to aid users by providing a central repository where bundles with a variety of different functionality may be found.

Customizable information for each bundle on the toolshed includes its description, screen captures, authors, citation instructions and license terms. Automatically maintained information includes release history and download statistics.

To submit a bundle for publication on the toolshed, you must first sign in. Currently, only Google sign in is supported. Once signed in, use the Submit a Bundle link at the top of the page to initiate submission, and follow the instructions. The first time a bundle is submitted to the toolshed, it is held for inspection by the ChimeraX team, which may contact the authors for more information. Once approved, all subsequent submissions of new versions of the bundle are posted immediately on the site.

What’s Next