This documentation provides a short introduction to the MoveApps Python SDK. Note that it can also be found in the developer_README.md of the Python GitHub template.
As a first step, and before your read this, you should have forked the above GitHub template to your personal space and named the repository as your App will be named in MoveApps.
This template is designed according to a file structure that is necessary for your App to run in your local development environment similar to the way it will run in the MoveApps environment later. Please contain the structure and only change/add files as necessary for your App's functionality. See below which files can be changed and which should remain as is for simulation of the behaviour on MoveApps on your local system. A stepwise explanation below indicates the function and some background of each file and folder.
.
├── app
│ └── app.py
├── appspec.json
├── environment.yml
├── resources
│ ├── local_app_files
│ ├── output
│ └── samples
│ └── input1.pickle
├── sdk
│ ├── moveapps_execution.py
│ ├── moveapps_io.py
│ └── moveapps_spec.py
├── sdk.py
├── tests
│ ├── app
│ │ └── test_app.py
│ └── resources
│ ├── app
│ │ └── input2.pickle
│ └── output
./app/app.py: This is the entrypoint for your App logic. MoveApps will call this class during a workflow execution which includes your App. The class must be namedAppand the file must be named./app/app.py, do not alter it!./appspec.json: This file defines the settings and metadata of your App, for details refer to the MoveApps User Manual./environment.yml: Definition of the dependencies of your App. We usecondaas library manager../resources/**: Resources of the SDKlocal_app_files/**: Simulates the usage of auxiliary app files. You can put files into this folder to simulate an App run with provided/user-uploaded files.output/**: If your App produces artefacts they will be stored here.samples/**: Collection of sample App input data. You can use these samples to simulate an App run with real input.
./sdk/**: The (internal) MoveApps Python SDK logic (not to be changed).moveapps_execution.py: The logic for simulating an App run.moveapps_io.py: Helper functions to use IO features of MoveApps.moveapps_spec.py: The python App specification each MoveApps Python App must implement
./sdk.py: The main entry point of the SDK. Use it to execute your App in your IDE../tests/**: Location for Unit Tests
Critical parts of the SDK can be adjusted by environment variables.
Keep in mind that these variables are only changeable during App development and not during an App run on MoveApps.
They are predefined with sensible defaults - they should work for you as they are.
SOURCE_FILE: path to input file for your AppCONFIGURATION_FILE: configuration of your App (json - must correspondent with thesettingsof yourappspec.json)PRINT_CONFIGURATION: prints the configuration your App receivesLOCAL_APP_FILES_DIR: base directory of your local App files (auxiliary)OUTPUT_FILE: path to output file of your AppAPP_ARTIFACTS_DIR: base directory for writing App artifacts
You can adjust these environment variables by adjusting the file ./.env.
Which files will be bundled into the final App running on MoveApps?
- everything in
./app/** - your conda environment definition
./environment.yml - all directories defined in your
appspec.jsonatprovidedAppFiles
Nothing else.
- Create the conda environment by
conda env create -n APP_NAME --file environment.yml - Execute
python sdk.py - Ensure the sdk executes the vanilla template App code. Everything is set up correctly if no error occurs and you see something like Welcome to the MoveApps Python SDK.
- Begin with your App development in
./app/app.py
As mentioned, MoveApps will call your custom App business logic in ./app/app.py. It will instantiate the class App. So do not alter the class name or the file name.
The SDK calls so called hooks. These hooks must be implemented by the App. Currently, there is only the following hook specified in ./sdk/moveapps_spec.py:
@hook_spec def execute(self, data: TrajectoryCollection, config: dict) -> TrajectoryCollection:
A proper implementation of this hook specification looks like this:
@hook_impl def execute(self, data: TrajectoryCollection, config: dict) -> TrajectoryCollection:
"""Your app code goes here"""
return data
./appspec.json: define the settings UI on MoveApps. Users of your App can enter their configuration values.
"settings": [
{
"id": "line_width",
"name": "Line width",
"description": "The width of the lines in the plot.",
"defaultValue": 2,
"type": "INTEGER"
},
{
"id": "legend",
"name": "Include legend?",
"description": "Should the plot contain a legend?",
"defaultValue": false,
"type": "CHECKBOX"
}
],
./app-configuration.json: this is only needed during the app development to simulate an App run
{
"line_width": 2,
"legend": true
}
./app/app.py: your App will be called with the user's App configuration
@dataclass
class AppConfig:
line_width: int
with_legend: bool
class App(object):
@staticmethod
def map_config(config: dict):
return AppConfig(
line_width=config['line_width'] if 'line_width' in config else 5,
with_legend=config['legend'] if 'legend' in config else False
)
@hook_impl
def execute(self, data: TrajectoryCollection, config: dict) -> TrajectoryCollection:
app_config = self.map_config(config=config)
# [..]
./tests/app/test_app.py: do not forget to test your App
def test_app_config_mapping_defaults(self):
# prepare
config = {}
# execute
actual = self.sut.map_config(config=config)
# verify
self.assertEqual(5, actual.line_width)
self.assertFalse(actual.with_legend)
Your App can write files which the user can download after it has run.
./appspec.json
"createsArtifacts": true,
./app/app.py
plot = data.plot(
column="speed",
linewidth=app_config.line_width,
capstyle='round',
legend=app_config.with_legend
)
plot.figure.savefig(self.moveapps_io.create_artifacts_file('plot.png'))
You can include files to your final App, e.g. a directory containing files of a Shapefile.
./resources/local_app_files/provided-app-files/{file-set-identifier}
We use my-app-files as {file-set-identifier} for this example. Also, we want to provide the simple file the-file.txt.
./appspec.json
"providedAppFiles": [
{
"settingId": "my-app-files",
"from": "resources/local_app_files/provided-app-files/my-app-files"
}
],
Next, store your necessary file(s) in the defined folder. For our example add the file ./resources/local_app_files/provided-app-files/my-app-files/the-file.txt
Somewhere in you App code (like ./app/app.py) you can access this file with the help of the SDK utility method moveapps_io.get_app_file_path() like:
def _consume_app_file(self):
app_file_base_dir = self.moveapps_io.get_app_file_path('my-app-files')
if app_file_base_dir:
expected_file = os.path.join(app_file_root_dir, 'the-file.txt')
# do something with this file
Sometimes it is useful that a MoveApps user working with your App can upload his/her own files during runtime. The SDK provides a way to access these uploaded files.
With this mechanism it is also possible to let the user overwrite your provided app files. Therefor we will extend the previous example in the following.
./appspec.json
"providedAppFiles": [
{
"settingId": "my-app-files",
"from": "resources/local_app_files/provided-app-files/my-app-files"
}
],
"settings": [
{
"id": "my-app-files",
"name": "Upload your own file",
"description": "You can provide your own file. Expected file name is `the-file.txt`",
"defaultValue": null,
"type": "LOCAL_FILE"
}
]
By using the same helper method for loading the app-file we can use the uploaded file:
def _consume_app_file(self):
app_file_base_dir = self.moveapps_io.get_app_file_path('my-app-files')
if app_file_base_dir:
expected_file = os.path.join(app_file_root_dir, 'the-file.txt')
# do something with this file
The rules are:
- If the user uploaded the expected file, then the App uses it
- If the user did not upload the expected file and you as the App developer provided the file, then the App uses the provided file
In short: files from the users win over files provided by the app developer
Note that, if neither the user uploaded the expected file, nor you as the App developer provided the file, the App might run into an error - depending on the code desing handling the return value of moveapps_io.get_app_file_path() in ./app/app.py.

