Prerequisites¶
Python >= 3.7.7 <3.8
Docker >= 19.03
pip >= 20.1.1
Note
We have a much more in depth getting started guide for anyone wants it
Quick-start¶
Install from PyPi¶
pip install nomnomdata-tools-engine nomnomdata-engine
Create a new app template directory structure, similar to create-react-app
nnd engine-tools create-new waiter-app
Template Layout¶
These files and folders will get created locally on your machine to form the structure of your NND App
engine/pkg/__init__.py
engine/pkg/tests/__init__.py
engine/pkg/tests/test_executable.py
engine/requirements.txt¶
Pip requirements file . Initially blank.
engine/Dockerfile¶
Template Dockerfile, we’ve done a little work here for you to utilize multi-stage builds and install your requirements.txt. The important thing is to set CMD to run your engine entry point.
Note
If you are unfamilar with Docker/dockerfiles it would be advantageous to familarize yourself over at the docker documentation
# Starting from an image of our SDK as the initial build layer
FROM registry.gitlab.com/nomnomdata/tools/nomnomdata-engine:latest as builder
# Copy your requirements.txt file into the build layer environmens
# use its contents to create pip wheels for installing
# the packages and versions specified
COPY requirements.txt /nomnom/requirements.txt
RUN pip wheel --wheel-dir /python_packages -r /nomnom/requirements.txt
# Use our 'slim' image as the base for the next layer
# which will be the layer that is deployed
FROM registry.gitlab.com/nomnomdata/tools/nomnomdata-engine:latest-slim
# Copy the python wheels from our previous builder layer
COPY --from=builder /python_packages /python_packages
# Install the packages and remove the
# temporary /python_packages directory
RUN pip install --no-index --find-links /python_packages /python_packages/* \
&& rm -rf /python_packages
# Set up /nomnom as the working directory
# and copy every file in the pkg directory (your app code) into it
WORKDIR /nomnom/
COPY pkg/*.* /nomnom/pkg/
# sets the command that will run
# your code inside the container when
# it is triggered from a Nominode Task:
CMD python pkg/executable.py run
engine/pkg/executable.py¶
Main entry module of the example app.
Understanding executable.py¶
Our executable.py looks like this
import logging
from nomnomdata.engine import Engine, Parameter, ParameterGroup
from nomnomdata.engine.parameters import Int, String
logger = logging.getLogger("nnd.some-cool-engine")
engine = Engine(
uuid="CHANGE-ME-PLEASE",
alias="Some Cool Engine",
description="Description of your engine",
categories=["general"],
)
@engine.action(display_name="An Action", description="Action description")
@engine.parameter_group(
ParameterGroup(
Parameter(
type=Int(),
name="integer_parameter",
display_name="An Integer Parameter",
description="Description of what the parameter is used for",
),
Parameter(
type=String(),
name="string_parameter",
display_name="A String Parameter",
description="Description of what the parameter is used for",
),
name="general_parameters",
display_name="General Parameters",
description="Description of the parameter group",
)
)
def an_action(parameters):
engine.update_progress(progress=50)
print(parameters)
if __name__ == "__main__":
engine.main()
Lets break it down
import logging
We’re using the standard library logging module
Note
When your app runs via python pkg.py run
, a special log handler is
attached to the root logger. This handler sends all log messages to the nominode for storage and display
in the UI
Next import the building blocks of an engine from nomnomdata.engine
, information here API Reference
from nomnomdata.engine import Engine, Parameter, ParameterGroup
from nomnomdata.engine.parameters import Int, String
Instantiate our logger to be used later, the name nnd.some-cool-engine
here is an example, rename it whatever suits your App best:
logger = logging.getLogger("nnd.some-cool-engine")
These lines define the unique identifier, the display name, the description and the categories that apply to the NND App: You’ll want to change these to be more appropriate. Think of the UUID as a url slug.
You can consult the reference docs for Engine
for more details
engine = Engine(
uuid="CHANGE-ME-PLEASE",
alias="Some Cool Engine",
description="Description of your engine",
categories=["general"],
)
Warning
The UUID must be globally unique, and cannot collide with any existing app UUID owned by anyone on the platforms
If we want to have some icons display for the App on the nominode, it’s a simple change.
engine = Engine(
uuid="TUTOR-WAITR",
alias="Waiter Tutorial",
description="Example of an NND App that waits until a time specified.",
categories=["tutorial","wait"],
icons={
"1x": "../../icons/icon-1x.png",
"2x": "../../icons/icon-2x.png",
"3x": "../../icons/icon-3x.png",
},
)
Note
Icon paths are relative to where you run python executable.py dump-yaml
from.
Next we come to the definition of the action this engine can perform.
@engine.action(display_name="An Action", description="Action description")
@engine.parameter_group(
ParameterGroup(
Parameter(
type=Int(),
name="integer_parameter",
display_name="An Integer Parameter",
description="Description of what the parameter is used for",
),
Parameter(
type=String(),
name="string_parameter",
display_name="A String Parameter",
description="Description of what the parameter is used for",
),
name="general_parameters",
display_name="General Parameters",
description="Description of the parameter group",
)
)
def an_action(parameters):
engine.update_progress(progress=50)
print(parameters)
There’s a lot going on here so let’s break it down further
@engine.action(display_name="An Action", description="Action description")
The top level decorator here is action()
. It defines our function as an action,
gives it a name and also a description. Lets add a reference to a help file.
@engine.action(
display_name="An Action",
description="Action Description",
help_md_path="../../help/waiter-tutorial-wait_until.md"
)
Note
help_md_path
is relative to where you run python executable.py dump-yaml
from.
Next up we’re defining what parameters we want our action to accept,
using Parameter
, ParameterGroup
and decorating our action with engine. parameter_group()
@engine.parameter_group(
ParameterGroup(
Parameter(
type=Int(),
name="integer_parameter",
display_name="An Integer Parameter",
description="Description of what the parameter is used for",
),
Parameter(
type=String(),
name="string_parameter",
display_name="A String Parameter",
description="Description of what the parameter is used for",
),
name="general_parameters",
display_name="General Parameters",
description="Description of the parameter group",
)
)
Note
parameter_group()
is not limited to just accepting one Parameter,
you can pass as many parameters as you want as positional arguments
Next we have our actual function. We use our engine instance to communicate our progress back to the nominode.
def an_action(parameters):
engine.update_progress(progress=50)
print(parameters)
What does parameters look like in this case?
{
"until" : "<value the user sets in the interface>"
}
Next we ensure we call main()
as this is the entry point of every engine
if __name__ == "__main__":
engine.main()
Running tests locally¶
Looking at engine/pkg/tests/test_executable.py we can see just how easy it is to test things locally, when you call an action in any code (outside of the run cli command), we mock up a nominode for your code to “talk” to!
from ..executable import an_action
def test_an_action():
an_action(integer_parameters=1, string_parameters="some_string")
Warning
If you use any nominode communication functions outside the context of an action entry point, you will have have to use
ExecutionContextMock
to wrap your code like so.
with ExecutionContextMock():
# code that uses NominodeClient
pass
Deploying your App¶
If you have not yet run nnd login
now is the time. Use your credentials for my.nomnomdata.com .
Note
Engines will be deployed to the organization you select and only be available to nominodes under that organization
All commands must be run in the engine folder of your template app
Generate a model.yaml file for your NND App.
python pkg/executable.py dump-yaml
Publish the engine for all the Nominodes assigned to your organization.
nnd engine-tools model-update -n nomitall-prod
Build the local docker image for your NND App and upload it to our secure image repository.
nnd engine-tools deploy -n nomitall-prod
Full CLI Reference