Skip to content

Commit 1385ff7

Browse files
authored
Add DispatcherConfig model read from .ddev/config.toml (DataDog#24434)
* Add DispatcherConfig model read from .ddev/config.toml Holds max_jobs_per_batch, global_timeout_seconds, and the GitHub rate limits (RateLimiterFactoryConfig). Loaded via the /dispatcher JSON pointer of the repo config. * Add changelog entry for DataDog#24434 * Type-annotate test fixture and functions * Document the batch and timeout defaults * Drop -> None return annotations from tests * Make the changelog entry a patch-level fixed entry * Cap max_jobs_per_batch at the safe workflow maximum
1 parent b40aa0b commit 1385ff7

3 files changed

Lines changed: 115 additions & 0 deletions

File tree

ddev/changelog.d/24434.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add an internal DispatcherConfig model for reading Dispatcher settings from the repo config.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# (C) Datadog, Inc. 2026-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
"""Per-repository Dispatcher configuration read from `.ddev/config.toml`."""
5+
6+
from __future__ import annotations
7+
8+
from typing import TYPE_CHECKING
9+
10+
from pydantic import BaseModel, ConfigDict, Field
11+
12+
from ddev.cli.ci.tests.rate_limiting import RateLimiterFactoryConfig
13+
14+
if TYPE_CHECKING:
15+
from ddev.repo.config import RepositoryConfig
16+
17+
18+
class DispatcherConfig(BaseModel):
19+
"""Per-repository Dispatcher configuration."""
20+
21+
model_config = ConfigDict(frozen=True, extra="forbid")
22+
23+
max_jobs_per_batch: int = Field(default=240, gt=0, le=240) # 256 GitHub job cap - 16-job setup buffer; the safe max
24+
global_timeout_seconds: float = Field(default=10800.0, gt=0) # 3 hours
25+
github_rate_limits: RateLimiterFactoryConfig = RateLimiterFactoryConfig()
26+
27+
@classmethod
28+
def from_repo_config(cls, repo_config: RepositoryConfig) -> DispatcherConfig:
29+
"""Build a DispatcherConfig from the `/dispatcher` table of `.ddev/config.toml`."""
30+
return cls(**repo_config.get("/dispatcher", {}))
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# (C) Datadog, Inc. 2026-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
"""Tests for DispatcherConfig.from_repo_config."""
5+
6+
from __future__ import annotations
7+
8+
from collections.abc import Callable
9+
10+
import pytest
11+
12+
from ddev.cli.ci.tests.dispatcher_config import DispatcherConfig
13+
from ddev.cli.ci.tests.rate_limiting import RateLimiterFactoryConfig
14+
from ddev.repo.config import RepositoryConfig
15+
from ddev.utils.fs import Path
16+
17+
RepoConfigBuilder = Callable[[str], RepositoryConfig]
18+
19+
20+
@pytest.fixture
21+
def repo_config(tmp_path: Path) -> RepoConfigBuilder:
22+
def build(toml_content: str) -> RepositoryConfig:
23+
config_path = Path(tmp_path) / "config.toml"
24+
config_path.write_text(toml_content)
25+
return RepositoryConfig(config_path)
26+
27+
return build
28+
29+
30+
def test_from_repo_config_reads_full_dispatcher_table(repo_config: RepoConfigBuilder):
31+
config = repo_config(
32+
"""
33+
[dispatcher]
34+
max_jobs_per_batch = 120
35+
global_timeout_seconds = 3600.0
36+
37+
[dispatcher.github_rate_limits]
38+
total_hourly_max_rate = 1500
39+
slow_integrations = ["mongo", "mysql"]
40+
41+
[dispatcher.github_rate_limits.default]
42+
max_rate = 360
43+
44+
[dispatcher.github_rate_limits.slow]
45+
max_rate = 120
46+
"""
47+
)
48+
49+
result = DispatcherConfig.from_repo_config(config)
50+
51+
assert result.max_jobs_per_batch == 120
52+
assert result.global_timeout_seconds == 3600.0
53+
assert result.github_rate_limits.total_hourly_max_rate == 1500
54+
assert result.github_rate_limits.slow_integrations == frozenset({"mongo", "mysql"})
55+
assert result.github_rate_limits.default.max_rate == 360
56+
assert result.github_rate_limits.slow.max_rate == 120
57+
58+
59+
def test_from_repo_config_reads_scalars_without_rate_limits_subtable(repo_config: RepoConfigBuilder):
60+
config = repo_config(
61+
"""
62+
[dispatcher]
63+
max_jobs_per_batch = 120
64+
global_timeout_seconds = 3600.0
65+
"""
66+
)
67+
68+
result = DispatcherConfig.from_repo_config(config)
69+
70+
assert result.max_jobs_per_batch == 120
71+
assert result.global_timeout_seconds == 3600.0
72+
assert result.github_rate_limits == RateLimiterFactoryConfig()
73+
74+
75+
def test_from_repo_config_falls_back_to_defaults_when_dispatcher_table_missing(repo_config: RepoConfigBuilder):
76+
config = repo_config(
77+
"""
78+
validations = []
79+
"""
80+
)
81+
82+
result = DispatcherConfig.from_repo_config(config)
83+
84+
assert result == DispatcherConfig()

0 commit comments

Comments
 (0)