2. Hello World

This tutorial is based on example code which can be found in the TRAC GitHub Repository under examples/models/python.

Writing a model

To write a model, start by importing the runtime’s API package and inheriting from the TracModel base class. This class is the entry point for running code with the runtime, both on the platform and when running locally.

src/tutorial/hello_world.py
16import typing as tp
17import tracdap.rt.api as trac
18
19
20class HelloWorldModel(trac.TracModel):

The model can define any parameters it is going to need. In this example there is only a single parameter so it can be declared in code (more complex models may wish to manage parameters in a parameters file). The runtime provides helper functions to ensure parameters are defined in the correct format.

22    def define_parameters(self) -> tp.Dict[str, trac.ModelParameter]:
23
24        return trac.define_parameters(
25            trac.P(
26                "input_number", trac.INTEGER,
27                label="An integer parameter supplied to the model"))

The model can also define inputs and outputs. In this case since all we are going to do is write a message in the log, no inputs and outputs are needed. Still, these methods are required in order for the model to be valid.

29    def define_inputs(self) -> tp.Dict[str, trac.ModelInputSchema]:
30        return {}
31
32    def define_outputs(self) -> tp.Dict[str, trac.ModelOutputSchema]:
33        return {}

To write the model logic, implement the run_model() method. When run_model() is called it receives a TracContext object which allows models to interact with the runtime.

35    def run_model(self, ctx: trac.TracContext):
36
37        ctx.log.info("Hello world model is running")
38
39        input_number = ctx.get_parameter("input_number")
40        ctx.log.info(f"The input number is {input_number}")

There are two useful features of TracContext that can be seen in this example:

  • The log() method returns a standard Python logger that can be used for writing model logs. When models run on the platform, it will capture any logs written to this logger and make them available with the job outputs as searchable datasets. Log outputs are available even if a job fails so they can be used for debugging.

  • get_parameter() allows models to access any parameters defined in the define_parameters() method. They are returned as native Python objects, so integers use the Python integer type, date and time values use the Python datetime classes and so on.

Supplying config

To run the model, we need to supply two configuration files:

  • Job config, which includes everything related to the models and the data and parameters that will be used to execute them.

  • System config, which includes everything related to storage locations, repositories, execution environment and other system settings.

When models are deployed to run on the platform, the runtime generates the job configuration according to scheduled instructions and/or user input. A full set of metadata is assembled for every object and setting that goes into a job, so that execution can be strictly controlled and validated.

Running locally uses dev mode, where the runtime infers and fills in this metadata itself instead of requiring it up front - object IDs, storage definitions and schemas are generated from whatever is available (parameters, file paths, the model class itself), rather than assembled from a governed catalog. This is why the config needed to run a model locally can be kept short and readable, at the cost of the strict validation a platform deployment enforces. launch_model() runs in dev mode by default; the command-line launcher used in later tutorials (see Chaining Models) needs the --dev-mode flag to enable it explicitly.

For our Hello World model, we only need to supply a single parameter in the job configuration:

config/hello_world.yaml
job:
  runModel:

    parameters:
      input_number: 42

Since this model is not using a Spark session or any storage, there is nothing that needs to be configured in the system config. We still need to supply a config file though:

config/sys_config.yaml
# The file can be empty, but you need to supply it!

Run the model

The easiest way to launch a model during development is to call launch_model() from the runtime’s launch package. Make sure to guard the launch by checking __name__ == “__main__”, to prevent launching a local config when the model is deployed to the platform (the runtime will not allow this, but the model will fail to deploy)!

src/tutorial/hello_world.py
43if __name__ == "__main__":
44    import tracdap.rt.launch as launch
45    launch.launch_model(HelloWorldModel, "config/hello_world.yaml", "config/sys_config.yaml")

Paths for the system and job config files are resolved in the following order:

  1. If an absolute path is supplied, this takes priority

  2. Resolve relative to the current working directory

  3. Search relative to parents of the current directory

  4. Resolve relative to the directory containing the model

  5. Search relative to parents of the directory containing the model

Now you should be able to run your model script and see the model output in the logs:

2022-05-31 12:19:36,104 [engine] INFO tracdap.rt.exec.engine.NodeProcessor - START RunModel [HelloWorldModel] / JOB-92df0bd5-50bd-4885-bc7a-3d4d95029360-v1
2022-05-31 12:19:36,104 [engine] INFO __main__.HelloWorldModel - Hello world model is running
2022-05-31 12:19:36,104 [engine] INFO __main__.HelloWorldModel - The input number is 42
2022-05-31 12:19:36,104 [engine] INFO tracdap.rt.exec.engine.NodeProcessor - DONE RunModel [HelloWorldModel] / JOB-92df0bd5-50bd-4885-bc7a-3d4d95029360-v1

See also

Full source code is available for the Hello World example on GitHub.