Customization Using Python Packages and Modules¶
You can extend with additional user interface and other complex capabilities that aren’t covered by other Python scripting techniques.
Some examples of things you must do with a Python plug-in package or module.
Create a menu item.
Create a panel providing a new user interface.
Create an IO handler for importing and exporting new file types.
Create a data acquisition device.
Create more complex acquisition procedures using a hardware source for control and data acquisition.
Create high performance algorithms that run on threads.
At launch time, Nion Swift searches its Python environment for packages, searches some predefined locations for modules, and starts them using a startup procedure which can include dependency and order checking.
Finding Your Python Package¶
Nion Swift first searches its Python environment for packages.
It looks for installed packages which have a nionswift_plugin
namespace and treats packages within that
namespace as packages to be loaded.
When you create a Python package, you should NOT put a __init__.py
in the nionswift_plugin
directory, since it
is a Python namespace. See PEP 420 for more details. Similarly, you will
NOT need the namespace_packages directive in your setup.py file.
Loading Your Python Package¶
After Nion Swift finds your packages, it loads them.
When the package is imported, Swift will look for a class that ends with Extension
and defines a class property
extension_id
. If it finds such a class, it will instantiate it, passing the api_broker
object that allows you to
get the versioned API your extension requires. Your extension should do initialization in the __init__
method of
that class.
When Swift exits, the close
method of that class will also be called. You can do any de-initialization in the close
method.
You should avoid executing code that depends on other extensions or on Swift during extension module loading.
When the package is loaded, the class will be instantiated and an api_broker
object will be passed to
__init__
. The api_broker
can be used to get an api
object.
class MyExtension:
# required for Swift to recognize this as an extension class.
extension_id = "my.extension.identifier"
def __init__(self, api_broker):
# grab the api object.
api = api_broker.get_api(version='~1.0', ui_version='~1.0')
# api can be used to access Nion Swift...
def close(self):
pass # perform any shutdown activities
Organizing Your Python Package¶
If you are planning on making your plug-in available publicly, we recommend using Python packages to distribute your
extension and publishing it to PyPI. Other users will be able to install your plug-in using pip
and other standard
Python installation tools.
$ pip install my-great-nion-swift-extension
If you are planning on using your plug-in privately, we still recommend using a standard Python package, but installing it into your Python environment using one of two techniques.
$ python setup.py /path/to/your/extension # for end users
$ pip install -e /path/to/your/extension # for development
If parts of your package may be reusable in other packages (e.g. a library of processing functions), then you will want to split your package into different namespaces so that other packages can import your library independently of the user interface or other Nion Swift specific code.
Warning
You should never import anything from the nionswift_plugin namespace directly. Instead, split your code into two namespaces.
For instance, you might have the following directory structure.
mycompany/__init__.py
mycompany/processing/feature_finder.py
nionswift_plugin/mycompany_featurefinder/feature_finder_ui.py
This allows your UI code and other packages to access your feature_finder
code.
>>> from mycompany.processing import feature_finder
And it allows Nion Swift to load your UI code during startup.
PlugIns¶
Warning
Installing Python packages using the nionswift_plugin
namespace is preferable to using PlugIns
.
For backwards compatility, you can also organize your extension as a PlugIn
. If you’re organizing your code into
a plug-in, you simply put your code into a sub-directory of one of the PlugIn
directories listed above.
Using this technique is not recommended since you cannot cleanly provide code to other plug-ins nor can you nicely specify dependencies using imports.
Nion Swift looks for packages within various PlugIns
directories. The PlugIns
directories can be located in the
following locations:
- Windows
C:\Users\<username>\AppData\Local\Nion\Swift\PlugIns
C:\Users\<username>\Documents\Nion\Swift\PlugIns
PlugIns
in the same location as theSwift.exe
file
- Mac OS
/Users/<username>/Library/Application Support/Nion/Swift/PlugIns
/Users/<username>/Documents/Nion/Swift/PlugIns
PlugIns
in the same location as theNionSwift
executable file
- Linux
TODO: Describe locations for Linux extensions
Debugging¶
When using the API for external scripting, the Python instance used for scripting is separate from the Python instance used internally in Swift, so debugging is easy. You can set breakpoints and otherwise step through your code as necessary.
However, debugging packages is more tricky. There are two main options for debugging packages:
Using print and logging facilities.
Launch your Python package outside of Nion Swift.
You can use the Python logging module to output to the Output window. logging.info and above are sent. logging.debug is only sent to developer console.
import logging
logging.info("Here is your result: 42")
logging.debug("Debugging: 21 + 21 = 42")
print("Forty-two")
When launching your module outside of Nion Swift, you may be able to debug parts of your software using the scripting mode of development. You will not be able to directly debug the part of your plug-in that implements the extension.
For instance, if you have a menu item in an extension, the menu item might call a function perform_action
. While you
can’t step through the code that creates the menu item (since it is part of the extension architecture), you could at
least load the library that implements perform_action
and run code that directly invokes that function for
debugging.
Designing Python Packages to Extend Swift¶
Questions to help define a plug-in package.
What actions can the user perform?
What is presented to user?
Custom UI?
Custom workspace layout?
How saved?
How restored?
How initialized?
What happens dynamically?
How are deletes handled?
Copy/paste/clone/snapshot?
Any required configuration?
How tested?
Plug-in Checklist¶
A list of items that make a well defined plug-in package.
Available on PyPI
Has setup script
Available on conda, plus feedstock.
Automated testing
Settings stored using API paths
JSON settings
Settings location logging.info
Settings are isolated; don’t read other settings
Clean install/uninstall with pip
Avoid committing large files to GitHub projects (use Git LFS instead).
Load local resources using
pkgutil.get_data
or usingimportlib.resources
.