Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
192efb8
feat: convert graph config schema to msgspec
abhishekmadan30 Aug 19, 2025
d34493f
feat: convert more schemas to msgspec
abhishekmadan30 Aug 22, 2025
8971de1
feat: updated fetch schema
abhishekmadan30 Aug 22, 2025
08dae5f
feat: update 2 more schemas
abhishekmadan30 Aug 22, 2025
656e375
feat: added more schemas
abhishekmadan30 Aug 22, 2025
4804bb8
feat: update schema
abhishekmadan30 Aug 25, 2025
7e7206a
feat: update schema from voluptuous to msgspec
abhishekmadan30 Aug 26, 2025
de18fdf
Merge branch 'schema-conversion' of github.com:abhishekmadan30/taskgr…
abhishekmadan30 Aug 26, 2025
d6f3589
feat: updated some bugs
abhishekmadan30 Aug 27, 2025
60318b4
feat: used schema wrapper for msgspec
abhishekmadan30 Aug 29, 2025
7fddbd9
feat: cleaned up schema wrapper
abhishekmadan30 Aug 29, 2025
1d23c98
feat: added support for optional keying
abhishekmadan30 Aug 29, 2025
1a1e79c
Merge branch 'main' into schema-conversion
abhishekmadan30 Aug 29, 2025
565a827
fix: updated errors
abhishekmadan30 Sep 2, 2025
a679015
Merge branch 'main' into schema-conversion
abhishekmadan30 Sep 2, 2025
9b76e78
DO NOT LAND: Use by-level in priority key
ahal Sep 2, 2025
7d203c6
Experimental refactor
ahal Sep 2, 2025
3baf4eb
fix: changed how extra fields work
abhishekmadan30 Sep 2, 2025
ab07a7e
fix: updated optionally keyed by fucntionality
abhishekmadan30 Sep 2, 2025
6849b11
fix: updated tests for optionally keyed by
abhishekmadan30 Sep 2, 2025
006a6e3
fix: remove extra code
abhishekmadan30 Sep 3, 2025
1b37f95
fix: updated based on review
abhishekmadan30 Sep 4, 2025
750511c
fix: compatibility with gecko
abhishekmadan30 Sep 16, 2025
2d95095
feat: fixed reviews
abhishekmadan30 Sep 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions docs/concepts/transforms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,16 @@ about the state of the tasks at given points. Here is an example:

.. code-block:: python

from voluptuous import Optional, Required

from typing import Optional
from taskgraph.transforms.base import TransformSequence
from taskgraph.util.schema import Schema

my_schema = Schema({
Required("foo"): str,
Optional("bar"): bool,
})
class MySchema(Schema):
foo: str # Required field
bar: Optional[bool] = None # Optional field

transforms.add_validate(my_schema)
transforms = TransformSequence()
transforms.add_validate(MySchema)

In the above example, we can be sure that every task dict has a string field
called ``foo``, and may or may not have a boolean field called ``bar``.
Expand Down
16 changes: 8 additions & 8 deletions docs/tutorials/creating-a-task-graph.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,16 @@ comments for explanations):

.. code-block:: python

from voluptuous import Optional, Required

from taskgraph.transforms.base import TransformSequence
from typing import Optional
from taskgraph.util.schema import Schema
from taskgraph.transforms.base import TransformSequence

# Define the schema using Schema base class.
class HelloDescriptionSchema(Schema):
text: str # Required field
description: Optional[str] = None # Optional field

# Define the schema. We use the `voluptuous` package to handle validation.
hello_description_schema = Schema({
Required("text"): str,
Optional("description"): str,
})
hello_description_schema = HelloDescriptionSchema

# Create a 'TransformSequence' instance. This class collects transform
# functions to run later.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ dependencies = [
"cookiecutter~=2.1",
"json-e>=2.7",
"mozilla-repo-urls",
"msgspec>=0.18.6",
"PyYAML>=5.3.1",
"redo>=2.0",
"requests>=2.25",
"slugid>=2.0",
"taskcluster-urls>=11.0",
"voluptuous>=0.12.1",
]

[project.optional-dependencies]
Expand Down
188 changes: 94 additions & 94 deletions src/taskgraph/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,113 +2,112 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.


import logging
import os
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Dict

from voluptuous import ALLOW_EXTRA, All, Any, Extra, Length, Optional, Required
from typing import Dict, List, Literal, Optional, Union

from .util.caches import CACHES
from .util.python_path import find_object
from .util.schema import Schema, optionally_keyed_by, validate_schema
from .util.schema import Schema, TaskPriority, optionally_keyed_by, validate_schema
from .util.vcs import get_repository
from .util.yaml import load_yaml

logger = logging.getLogger(__name__)

# CacheName type for valid cache names
CacheName = Literal[tuple(CACHES.keys())]


class WorkerAliasSchema(Schema):
"""Worker alias configuration."""

provisioner: optionally_keyed_by("level", str) # type: ignore
implementation: str
os: str
worker_type: optionally_keyed_by("level", str) # type: ignore


class WorkersSchema(Schema, rename=None):
"""Workers configuration."""

aliases: Dict[str, WorkerAliasSchema]


class Repository(Schema, forbid_unknown_fields=False):
"""Repository configuration.

This schema allows extra fields for repository-specific configuration.
"""

# Required fields first
name: str

# Optional fields
project_regex: Optional[str] = None # Maps from "project-regex"
ssh_secret_name: Optional[str] = None # Maps from "ssh-secret-name"


class RunConfig(Schema):
"""Run transforms configuration."""

# List of caches to enable, or a boolean to enable/disable all of them.
use_caches: Optional[Union[bool, List[str]]] = None # Maps from "use-caches"

def __post_init__(self):
"""Validate that cache names are valid."""
if isinstance(self.use_caches, list):
invalid = set(self.use_caches) - set(CACHES.keys())
if invalid:
raise ValueError(
f"Invalid cache names: {invalid}. "
f"Valid names are: {list(CACHES.keys())}"
)


class TaskGraphSchema(Schema):
"""Taskgraph specific configuration."""

# Required fields first
repositories: Dict[str, Repository]

# Optional fields
# Python function to call to register extensions.
register: Optional[str] = None
decision_parameters: Optional[str] = None # Maps from "decision-parameters"
# The taskcluster index prefix to use for caching tasks. Defaults to `trust-domain`.
cached_task_prefix: Optional[str] = None # Maps from "cached-task-prefix"
# Should tasks from pull requests populate the cache
cache_pull_requests: Optional[bool] = None # Maps from "cache-pull-requests"
# Regular expressions matching index paths to be summarized.
index_path_regexes: Optional[List[str]] = None # Maps from "index-path-regexes"
# Configuration related to the 'run' transforms.
run: Optional[RunConfig] = None


class GraphConfigSchema(Schema, forbid_unknown_fields=False):
"""Main graph configuration schema.

This schema allows extra fields for flexibility in graph configuration.
"""

# Required fields first
# The trust-domain for this graph.
# (See https://firefox-source-docs.mozilla.org/taskcluster/taskcluster/taskgraph.html#taskgraph-trust-domain)
trust_domain: str # Maps from "trust-domain"
task_priority: optionally_keyed_by("project", "level", TaskPriority) # type: ignore
workers: WorkersSchema
taskgraph: TaskGraphSchema

#: Schema for the graph config
graph_config_schema = Schema(
{
# The trust-domain for this graph.
# (See https://firefox-source-docs.mozilla.org/taskcluster/taskcluster/taskgraph.html#taskgraph-trust-domain) # noqa
Required("trust-domain"): str,
Optional(
"docker-image-kind",
description="Name of the docker image kind (default: docker-image)",
): str,
Required("task-priority"): optionally_keyed_by(
"project",
"level",
Any(
"highest",
"very-high",
"high",
"medium",
"low",
"very-low",
"lowest",
),
),
Optional(
"task-deadline-after",
description="Default 'deadline' for tasks, in relative date format. "
"Eg: '1 week'",
): optionally_keyed_by("project", str),
Optional(
"task-expires-after",
description="Default 'expires-after' for level 1 tasks, in relative date format. "
"Eg: '90 days'",
): str,
Required("workers"): {
Required("aliases"): {
str: {
Required("provisioner"): optionally_keyed_by("level", str),
Required("implementation"): str,
Required("os"): str,
Required("worker-type"): optionally_keyed_by("level", str),
}
},
},
Required("taskgraph"): {
Optional(
"register",
description="Python function to call to register extensions.",
): str,
Optional("decision-parameters"): str,
Optional(
"cached-task-prefix",
description="The taskcluster index prefix to use for caching tasks. "
"Defaults to `trust-domain`.",
): str,
Optional(
"cache-pull-requests",
description="Should tasks from pull requests populate the cache",
): bool,
Optional(
"index-path-regexes",
description="Regular expressions matching index paths to be summarized.",
): [str],
Optional(
"run",
description="Configuration related to the 'run' transforms.",
): {
Optional(
"use-caches",
description="List of caches to enable, or a boolean to "
"enable/disable all of them.",
): Any(bool, list(CACHES.keys())),
},
Required("repositories"): All(
{
str: {
Required("name"): str,
Optional("project-regex"): str,
Optional("ssh-secret-name"): str,
# FIXME
Extra: str,
}
},
Length(min=1),
),
},
},
extra=ALLOW_EXTRA,
)
# Optional fields
# Name of the docker image kind (default: docker-image)
docker_image_kind: Optional[str] = None # Maps from "docker-image-kind"
# Default 'deadline' for tasks, in relative date format. Eg: '1 week'
task_deadline_after: Optional[optionally_keyed_by("project", str)] = None # type: ignore
# Default 'expires-after' for level 1 tasks, in relative date format. Eg: '90 days'
task_expires_after: Optional[str] = None # Maps from "task-expires-after"


@dataclass(frozen=True, eq=False)
Expand Down Expand Up @@ -177,7 +176,8 @@ def kinds_dir(self):


def validate_graph_config(config):
validate_schema(graph_config_schema, config, "Invalid graph configuration:")
"""Validate graph configuration using msgspec."""
validate_schema(GraphConfigSchema, config, "Invalid graph configuration:")


def load_graph_config(root_dir):
Expand Down
12 changes: 5 additions & 7 deletions src/taskgraph/decision.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
import shutil
import time
from pathlib import Path
from typing import Any, Dict, Optional

import yaml
from voluptuous import Optional

from taskgraph.actions import render_actions_json
from taskgraph.create import create_tasks
Expand Down Expand Up @@ -40,11 +40,9 @@


#: Schema for try_task_config.json version 2
try_task_config_schema_v2 = Schema(
{
Optional("parameters"): {str: object},
}
)
class TryTaskConfigSchemaV2(Schema):
# All fields are optional
parameters: Optional[Dict[str, Any]] = None


def full_task_graph_to_runnable_tasks(full_task_json):
Expand Down Expand Up @@ -354,7 +352,7 @@ def set_try_config(parameters, task_config_file):
task_config_version = task_config.pop("version")
if task_config_version == 2:
validate_schema(
try_task_config_schema_v2,
TryTaskConfigSchemaV2,
task_config,
"Invalid v2 `try_task_config.json`.",
)
Expand Down
Loading