Define Task class inputs#

There are two ways of defining task inputs in Ewoks when using the Task class:

These two methods are incompatible and only one should be picked (see Incompatibility between methods).

Input names#

Required input names are given as a list of strings via the input_names subclass argument of Task.

They can then be retrieved in the task via self.inputs or self.get_input_value:

from ewoks import Task

class ExampleTask(Task, input_names=["a", "b"]):
    def run(self):
        a = self.inputs.a
        b = self.get_input_value("b")

        print(f"a={a} b={b}")

For demonstration purposes, we can execute a task from Python.

>>> task = ExampleTask(inputs={"a": 1, "b": 2})
>>> task.run()
a=1 b=2

Since these are required input names, Ewoks will throw a TaskInputError if one is missing

>>> task = ExampleTask(inputs={"a": 1})
ewokscore.task.TaskInputError: Missing inputs for <class '__main__.ExampleTask'>: {'b'}

We can allow optional inputs by giving a list of strings to a optional_input_names subclass argument of Task

class ExampleTaskWithOptional(Task, input_names=["a", "b"], optional_input_names=["c"]):
    def run(self):
        a = self.inputs.a
        b = self.get_input_value("b")
        c = self.inputs.c

        print(f"a={a} b={b} c={c}")

These optional inputs can be specified

>>> task = ExampleTaskWithOptional(inputs={"a": 1, "b": 2, "c": 3})
>>> task.run()
a=1 b=2 c=3

or omitted. In this case, they will be set to the special object MISSING_DATA:

>>> task = ExampleTaskWithOptional(inputs={"a": 1, "b": 2})
>>> task.run()
a=1 b=2 c=<MISSING_DATA>

Default values can be given thanks to get_input_value

class ExampleTaskWithDefault(Task, input_names=["a", "b"], optional_input_names=["c"]):
    def run(self):
        a = self.inputs.a
        b = self.get_input_value("b")
        c = self.get_input_value("c", 0) # <-- Default value

        print(f"a={a} b={b} c={c}")
>>> task = ExampleTaskWithDefault(inputs={"a": 1, "b": 2})
>>> task.run()
a=1 b=2 c=0

Subclassing#

Subclasses of a task will inherit the input names from the base class and any additional input name will be added to those:

class ChildExampleTask(ExampleTaskWithOptional, input_names=["d"]):
    def run(self):
        print(self.get_input_values())
>>> task = ChildExampleTask(inputs={"a": 1, "b": 2, "c": 3, "d": 4})
>>> # Accepts `a`, `b` and `c` in addition to `d` ^
>>> task.run()
{'a': 1, 'b': 2, 'c': 3, 'd': 4}

Input model#

Added in version 1.1.0.

Instead of input names, it is possible to provide a Pydantic model for inputs.

The model needs to derive from ewoks.BaseInputModel and must be provided via input_model to the task.

Inputs can then be retrieved in the task via self.inputs or self.get_input_value:

from ewoks import Task
from ewoks import BaseInputModel

class Inputs(BaseInputModel):
    a: int
    b: int


class ExampleTaskWithModel(Task, input_model=Inputs):
    def run(self):
        a = self.inputs.a
        b = self.get_input_value("b")

        print(f"a={a} b={b}")
>>> task = ExampleTaskWithModel(inputs={"a": 1, "b": 2})
>>> task.run()
a=1 b=2

The advantage of using a model is that inputs are validated by Pydantic

>>> task = ExampleTaskWithModel(inputs={"a": 1})
ewokscore.task.TaskInputError: 1 validation error for Inputs
b
    Field required [type=missing, input_value={'a': 1}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.10/v/missing

>>> task = ExampleTaskWithModel(inputs={"a": 1, "b": "not_a_number"})
ewokscore.task.TaskInputError: 1 validation error for Inputs
b
    Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='not_a_number', input_type=str]
    For further information visit https://errors.pydantic.dev/2.10/v/int_parsing

All Pydantic features such has Default values, Constraints or Custom validation are available.

For example, we can add an optional input to our model by giving it a default value

from ewoks import Task
from ewoks import BaseInputModel

class Inputs(BaseInputModel):
    a: int
    b: int
    c: int = 0 # <-- `c` is optional

class ExampleTaskWithModel(Task, input_model=Inputs):
    def run(self):
        a = self.inputs.a
        b = self.get_input_value("b")
        c = self.inputs.c

        print(f"a={a} b={b} c={c}")
>>> task = ExampleTaskWithModel(inputs={"a": 1, "b": 2})
>>> task.run()
a=1 b=2 c=0

Note

It is possible to reproduce the default behaviour of optional_input_names that are set to MISSING_DATA if not set.

For this, add MissingData as a possible type for the input in the model and use MISSING_DATA as the default value

from ewoks import Task
from ewoks import BaseInputModel
from ewokscore.missing_data import MissingData, MISSING_DATA

class Inputs(BaseInputModel):
    a: int
    b: int
    c: int | MissingData = MISSING_DATA # <-- `c` is optional

class ExampleTaskWithModel(Task, input_model=Inputs):
    def run(self):
        a = self.inputs.a
        b = self.get_input_value("b")
        c = self.inputs.c

        print(f"a={a} b={b} c={c}")
>>> task = ExampleTaskWithModel(inputs={"a": 1, "b": 2})
>>> task.run()
a=1 b=2 c=<MISSING_DATA>

Subclassing#

Subclasses of tasks with input models will inherit the model if they do not implement a model themselves

class ChildTask(ExampleTaskWithModel):
    def run(self):
        print(self.get_input_values())
>>> task = ChildTask(inputs={"a": 1, "b": 2, "c": 3})
>>> task.run()
{'a': 1, 'b': 2, 'c': 3}

If the subclass does have an input model, it must be inherit from the model of the base class to have compliant inputs

class NewInputs(Inputs):
    d: int


class ChildTaskWithModel(ExampleTaskWithModel, input_model=NewInputs):
    def run(self):
        print(self.get_input_values())
>>> task = ChildTaskWithModel(inputs={"a": 1, "b": 2, "c": 3, "d": 4})
>>> task.run()
{'a': 1, 'b': 2, 'c': 3, 'd': 4}

Incompatibility between methods#

Danger

It is not possible to mix input_names/optional_input_names and input_model!

This means a Task cannot have both input_names/optional_input_names and input_model defined.

But also, a subclass must use the same input definition method as its base class:

  • If the base class task uses input_names/optional_input_names, the subclass must use input_names/optional_input_names as well.

  • If the base class task uses input_model, the subclass must use an input_model that subclasses the model of the base task (see above).