From 86aaa6810fc8d6631d80295367718785c6c0e402 Mon Sep 17 00:00:00 2001 From: abhishekmadan30 Date: Wed, 18 Feb 2026 10:17:27 -0500 Subject: [PATCH] fix: make nested struct fields optional to match voluptuous defaults --- src/taskgraph/transforms/task.py | 24 ++++++++++++------------ src/taskgraph/util/schema.py | 4 ++-- test/test_util_schema.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/taskgraph/transforms/task.py b/src/taskgraph/transforms/task.py index 2d4bcffd6..d23836352 100644 --- a/src/taskgraph/transforms/task.py +++ b/src/taskgraph/transforms/task.py @@ -232,22 +232,22 @@ def verify_index(config, index): class DockerWorkerCacheEntry(Schema): # only one type is supported by any of the workers right now - type: Literal["persistent"] + type: Literal["persistent"] = "persistent" # name of the cache, allowing reuse by subsequent tasks naming the same cache - name: str + name: Optional[str] = None # location in the task image where the cache will be mounted - mount_point: str + mount_point: Optional[str] = None # Whether the cache is not used in untrusted environments (like the Try repo). skip_untrusted: Optional[bool] = None class DockerWorkerArtifact(Schema): # type of artifact -- simple file, or recursive directory, or a volume mounted directory. - type: Literal["file", "directory", "volume"] + type: Optional[Literal["file", "directory", "volume"]] = None # task image path from which to read artifact - path: str + path: Optional[str] = None # name of the produced artifact (root of the names for type=directory) - name: str + name: Optional[str] = None class DockerWorkerPayloadSchema(Schema, forbid_unknown_fields=False, kw_only=True): @@ -675,12 +675,12 @@ def build_generic_worker_payload(config, task, task_def): class ReleaseProperties(Schema): - app_name: str - app_version: str - branch: str - build_id: str - hash_type: str - platform: str + app_name: Optional[str] = None + app_version: Optional[str] = None + branch: Optional[str] = None + build_id: Optional[str] = None + hash_type: Optional[str] = None + platform: Optional[str] = None class UpstreamArtifact(Schema, rename="camel"): diff --git a/src/taskgraph/util/schema.py b/src/taskgraph/util/schema.py index f6330c196..0afdb0018 100644 --- a/src/taskgraph/util/schema.py +++ b/src/taskgraph/util/schema.py @@ -316,9 +316,9 @@ def validate(cls, data): class IndexSchema(Schema): # the name of the product this build produces - product: str + product: Optional[str] = None # the names to use for this task in the TaskCluster index - job_name: str + job_name: Optional[str] = None # Type of gecko v2 index to use type: str = "generic" # The rank that the task will receive in the TaskCluster diff --git a/test/test_util_schema.py b/test/test_util_schema.py index 6a240e4da..4da2e58aa 100644 --- a/test/test_util_schema.py +++ b/test/test_util_schema.py @@ -9,6 +9,7 @@ import taskgraph from taskgraph.util.schema import ( + IndexSchema, Schema, optionally_keyed_by, resolve_keyed_by, @@ -241,6 +242,35 @@ def test_no_key(self): ) +class TestNestedStructFieldsOptional(unittest.TestCase): + """Regression test: bare keys in nested voluptuous dict schemas were optional + by default. The msgspec migration must preserve this — nested struct fields + without defaults should not be required.""" + + def test_index_schema_accepts_partial_fields(self): + """IndexSchema should accept data with only 'type', omitting product/job-name.""" + + validate_schema( + IndexSchema, + {"type": "custom-index"}, + "index schema", + ) + + def test_index_schema_accepts_all_fields(self): + """IndexSchema should still accept full data.""" + + validate_schema( + IndexSchema, + { + "type": "generic", + "product": "firefox", + "job-name": "build", + "rank": "by-tier", + }, + "index schema", + ) + + def test_optionally_keyed_by(): typ = optionally_keyed_by("foo", str, use_msgspec=True) assert msgspec.convert("baz", typ) == "baz"