locustio/locust · error · ValueError

The 'tasks' attribute can only be set to list or dict

Error message

The 'tasks' attribute can only be set to list or dict

What it means

SequentialTaskSet.__new__ expands the 'tasks' attribute into an ordered list. Only lists (and methods decorated with @task) are allowed for sequential execution; other types raise ValueError. Notably, unlike TaskSet, a plain dict of task->weight is rejected here even though the expansion code path handles nested dicts inside lists.

Source

Thrown at locust/user/sequential_taskset.py:34

    def __new__(mcs, classname, bases, class_dict):
        new_tasks = []
        for base in bases:
            # first get tasks from base classes
            if hasattr(base, "tasks") and base.tasks:
                new_tasks += base.tasks
        for key, value in class_dict.items():
            if key == "tasks":
                # we want to insert tasks from the tasks attribute at the point of it's declaration
                # compared to methods declared with @task
                if isinstance(value, list):
                    new_tasks.extend(value)
                elif isinstance(value, dict):
                    for task, weight in value.items():
                        for _ in range(weight):
                            new_tasks.append(task)
                else:
                    raise ValueError("The 'tasks' attribute can only be set to list or dict")

            if "locust_task_weight" in dir(value):
                # method decorated with @task
                for _ in range(value.locust_task_weight):
                    new_tasks.append(value)

        class_dict["tasks"] = new_tasks
        return type.__new__(mcs, classname, bases, class_dict)


class SequentialTaskSet(TaskSet, metaclass=SequentialTaskSetMeta):
    """
    Class defining a sequence of tasks that a User will execute.

    Works like TaskSet, but task weight is ignored, and all tasks are executed in order. Tasks can
    either be specified by setting the *tasks* attribute to a list of tasks, or by declaring tasks
    as methods using the @task decorator. The order of declaration decides the order of execution.

View on GitHub (pinned to f391a716e1)

Solutions

  1. Set 'tasks' to a list; express repetition by duplicating entries, e.g. [TaskA, TaskA, TaskA] instead of {TaskA: 3}
  2. Wrap weighted dicts inside a plain TaskSet instead, or build the list programmatically
  3. Ensure dynamically built tasks are converted with list(...) before assignment

Example fix

// before
class MyFlow(SequentialTaskSet):
    tasks = {TaskA: 3, TaskB: 1}
// after
class MyFlow(SequentialTaskSet):
    tasks = [TaskA, TaskA, TaskA, TaskB]
Defensive patterns

Strategy: type-guard

Validate before calling

tasks = getattr(MyFlow, "tasks", [])
if not isinstance(tasks, list):
    raise TypeError("SequentialTaskSet.tasks must be a list")

Type guard

def has_valid_tasks(cls) -> bool:
    tasks = getattr(cls, "tasks", None)
    return isinstance(tasks, list) and len(tasks) > 0

Try / catch

try:
    flow = MyFlow(user)
except ValueError as e:
    if "tasks" in str(e):
        logging.error("Convert tasks to a list")
    raise

Prevention

When it happens

Trigger: Setting tasks = {TaskA: 3} (a dict) on a SequentialTaskSet subclass, or assigning tasks to a non-list value like a single callable or tuple of unsupported items.

Common situations: Copying the dict-based weighted tasks pattern from TaskSet docs into SequentialTaskSet; passing a tuple instead of a list; dynamic task assembly that produces a set or generator.

Related errors


AI-assisted analysis of locustio/locust@f391a716e1 (2026-08-29). Data as JSON: /api/errors/83bf561b86dafca1. Report an issue: GitHub.