Write script extensions - DLR-SC/DataFinder GitHub Wiki
Motivation
The idea behind developing a script API for DataFinder is, that it should be easy for any user to extend the DataFinder and make it suitable for a desired situation. The API is built upon the core layer of the DataFinder and masks it, it is the interface to the core layer. Only the most needed functions and operations for items and their properties are supported. Also the DataFinder objects are encapsulated and represented by Python objects. Using the API makes it easy for a developer to implement new features, without having further knowledge about the details of the implementation. For minor releases the script API will remain stable.
Setup Requirements to Develop Scripts
You can find a description of how to setup your developing environment here: Script Developing Environment
General Information for the Script API
The API has the same structure as the core layer of the DataFinder. It is extremely simplified and is restricted to the main functions.
Furthermore, the DataFinder objects, such as Item
, Properties
and the Repository
, are described with standard Python data types. They are explained in the following section.
For detailed information see the EpyDoc documentation
Item
In the script API the item is as a simple string, which represents the path information to the real item, implemented. Furthermore, the script API provides an ItemDescription
class with with one can get additional information about the item.
Properties
Properties in the script API are dictionaries in the form of key-value pairs. The key corresponds to the PropertyIdentifier
and the value to the value of that specific key.
For further information, there is a PropertyDescription
class available.
Repository
The repository is described via a RepositoryDescription
class, which supplies the necessary information.
Script API Examples
In the following, there are code listings, which show how the script API is used. More examples can be found in the source code repository.
Connecting to a Repository and Canceling a Connection
import datetime
from decimal import Decimal
from datafinder.script_api.error import ItemSupportError, ScriptApiError
from datafinder.script_api import repository
from datafinder.script_api.item import item_support
from datafinder.script_api.properties import property_support
if __name__ ## "__main__":
try:
repo = repository.connectRepository("http://datafinder.dlr.de/repos/test/REL_2.0.0/data",
"http://datafinder.dlr.de/repos/test/REL_2.0.0/config",
"guest", "guest")
except SciptApiError:
print "Cannot connect repository!"
# ...
try:
repository.disconnectRepository(repo)
except ScriptApiError:
print "Cannot disconnect repository!"
Creating a Collection
collectionProperties = dict()
collectionProperties[u"____datatype____"] = u"Projekt"
try:
item_support.createCollection("/User1/Projekt_Test", collectionProperties)
except ItemSupportError, error:
print error.errorMessage
Creating a Leaf
leafProperties = dict()
leafProperties[u"____datastorename____"] = u"Data Store_COPY"
leafProperties[u"____content.size____"] = Decimal("0")
leafProperties[u"____dataformat____"] = u"TEXT"
leafProperties[u"____contentcreationdatetime____"] = datetime.datetime.today()
leafProperties[u"____contentmodificationdatetime____"] = datetime.datetime.today()
try:
item_support.createLeaf("/User1/Projekt_Test/test.txt", leafProperties)
except ItemSupportError, error:
print error.errorMessage
Creating a Link
try:
item_support.createLink("/User2/Projekt_Blub/test.txt", "/User1/Projekt_Test/test.txt")
except ItemSupportError, error:
print error.errorMessage
Deleting an Item
try:
item_support.delete("/User1/Projekt_Test/test.txt")
except ItemSupportError, error:
print error.errorMessage
Copying/Moving of an Item
try:
item_support.move("/User1/Projekt_Test/test.txt", "/User2/Projekt_Blub/test.txt")
item_support.copy("/User2/Projekt_Blub/test.txt", "/User1/Projekt_Test/test.txt")
except ItemSupportError, error:
print error.errorMessage
Reading the Properties of an Item
try:
itemProperties = property_support.retrieveProperties("/User1/Projekt_Test/test.txt")
for identifier, value in itemProperties.iteritems():
print "PropertyIdentifier: %s Value: %s" % (identifier, value)
except ItemSupportError, error:
print error.errorMessage
Adding Properties to an Item
properties = dict()
properties[u"____keyword____"] = u"testfile"
properties[u"____createdwith____"] = u"Notepad"
try:
property_support.storeProperties("/User1/Projekt_Test/test.txt", properties)
except ItemSupportError, error:
print error.errorMessage
Deleting Properties of an Item
propertiesForDeletion = list()
propertiesForDeletion.append(u"____keyword____")
propertiesForDeletion.append(u"____createdwith____")
try:
property_support.deleteProperties("/User1/Projekt_Test/test.txt", propertiesForDeletion)
except ItemSupportError, error:
print error.errorMessage
Defining and Adding Domain Properties
First, we define an Author domain object which describes the author by a name, a main address, and a list of additional addresses. The Author
class uses further domain objects to express this information. The domain properties are bound to a specific type. The type information is expressed using the existing type classes (e.g., !DateTimeType, !StringType, !NumberType, ...). This allows us to use the existing value restrictions (e.g., minimum values, regular expression, ...) when declaring a domain property.
from datafinder.script_api.properties import DomainObject, DomainProperty, DomainObjectType, ListType, NumberType, StringType
class _City(DomainObject):
name = DomainProperty(StringType(), "", "City Name", "Name of the city.")
zip = DomainProperty(StringType(), "", "ZIP", "The postal code of the city.")
class _Address(DomainObject):
street = DomainProperty(StringType(), "", "Street", "This is the street name.")
streetNumber = DomainProperty(NumberType(), 0, "Street Number", "This is the street number.")
city = DomainProperty(DomainObjectType(_City), _City(), "City", "This is the city.")
class Author(DomainObject):
name = DomainProperty(StringType(), None, "Name", "This is the author name")
mainAddress = DomainProperty(DomainObjectType(_Address), _Address(), "Addresses", "The main address.")
addresses = DomainProperty(ListType([DomainObjectType(_Address)]),
None, "Addresses", "These are additional addresses.")
def __init__(self, name="", mainAddress=None, addresses=None):
domain.DomainObject.__init__(self)
self.name = name
if not mainAddress is None:
self.mainAddress = mainAddress
if not addresses is None:
self.addresses = addresses
@name.setValidate
def _validateName(self):
if self.name is None or len(self.name) ## 0:
raise ValueError("Name should not be empty.")
Then, we bind the domain object to the property name author
. Afterwards, we can add an author instance to an item and retrieve it again.
property_support.registerPropertyDefinition("author", DomainObjectType(Author))
author = Author("Me")
property_support.storeProperties("/Project", {"author": author})
retrievedAuthor = property_support.properties("/Project")["author"]
assert retrievedAuthor ## author
Embeding Script Extensions into the User Client
One of the major use cases of the script API is to extend the functionality of the user client. For that reason a user client specific additional script API exists which allows you to access certain GUI state securely. The functionality provided is also intended to remain stable along minor releases.
For detailed information see the EpyDoc documentation.
User client specfic script example
The following example demonstrates how a collection of the File System
View is accessed and creates a single file below it.
import logging
from datafinder.gui.user import script_api # User client specific API
from datafinder.script_api.error import ItemSupportError
from datafinder.script_api.repository import setWorkingRepository
from datafinder.script_api.item.item_support import createLeaf
_log = logging.getLogger("script") # Ensures that logging output is sent to the Script Output Window
umr = script_api.unmanagedRepositoryDescription() # Represents local file system
if not umr is None:
setWorkingRepository(umr)
_log.info(script_api.currentSelection()) # Logs the cureent selected items of the table view
_log.info(script_api.currentCollection()) # Logs the currently active collection
_log.info("Creating test file test.txt..")
script_api.lock(["/"]) # Locks the collection /
try:
createLeaf("/test.txt", dict())
except ItemSupportError, error:
_log.error(error.message)
finally:
script_api.unlock(["/"]) # Unlocks the collection /
script_api.selectItem("/test.txt") # Selects the newly created file
else:
_log.error("Cannot access unmanaged repository.")
More examples can be found in the source code repository.
Script Extension Types
In context of the user client two different types of script extensions exist, simple scripts (.py files) and script collections (.tar files). Simple scripts extensions serve most purposes well, but if you want to add a library or configuration dialog to a script you have to use script collection.
Simple Scripts
Simple scripts are just plain python .py files with additional meta data. The meta data helps to display and identify the scripts. Meta data values have to be specified in the comment header of the script as follows:
# @title: Hello World!
# @description: Tries to create file.
# @datatypes: File,Directory
# @dataformats: PDF,TEXT
# @icon: The_Icon_Name
# @version: 0.0.1
The following comment tags are supported:
Tag Name | Description |
---|---|
@title | Displayed title of the script. |
@description | Displayed usage description of the script. |
@datatypes | List of comma-separated data type names the script is associated with. |
@dataformats | List of comma-separated data format names the script is associated with. |
@icon | Name of the displayed icon. |
@version | 3-digit version number. |
@automatic | Enables automatic execution of the script on start of the application |
If no data type or data format names are specified the script generally usable. If at least one data type or data format is specified the script can only be used when a collection with the specific data type or a file with the specific data format is highlighted.
The available data format names are listed below:
Data Format Descriptive Name | Associated File Suffixes | Data Format Name |
---|---|---|
MS Word | doc | WORD |
MS Excel | xls | EXCEL |
MS Powerpoint | ppt | POWERPOINT |
MS Visio | vsd | VISIO |
XML | xml | XML |
HTML | html | HTML |
Python | py, pyc, pyd | PYTHON |
Binary | bin | BINARY |
Text | log, java, cpp, js, php, csv, ini, rtf | TEXT |
Zip | zip, tar, 7z, bz2, rar | ARCHIVE |
Audio | mpeg, x-waf, midi, ogg, wma | AUDIO |
Video | mpeg, x-msvideo, quicktime, xvid | VIDEO |
Image | jpeg, tiff, gif, png,eps, bmp | IMAGE |
Script Collections
If you have more then one script which depends on the same functionalities the best option is to create a script collection instead of duplicating the code in every script. Furthermore, script collections allow you to create your own configuration dialogs that integrate seamlessly into the user client. Script collections are simple tar files containing simple scripts with an additional file called SCRIPTS
. This file contains a list of all available scripts in the tar file and could look as follows:
report1.py
report2.py
When loading such a tar archive only the simple scripts declared in file SCRIPTS
are parsed. The content of such a tar archive named my_extension.tar
could look as follows:
- report
|--- common.py
|--- report1.py
|--- report2.py
|--- SCRIPTS
|--- PreferencesPage.py
In conjunction with the content of the file SCRIPTS
listed above this means the following:
- The script collection adds the simple scripts
report1.py
andreport2.py
to the user client. - Both report scripts can import functionalities from
common.py
using import statementimport my_extension.common
. - A preference page is added to the user client by directly executing script
PreferencesPage.py
.
You can automatically package a script collection using the build target package_script_extension.