feat(config): TAPIR_FETCH_BACKOFF for rate-limit retry window
Adds FetchBackoff (Go duration, default 1h) controlling how long the run loop waits before re-fetching a transcript that returned HTTP 429. Zero means always retry. Not required by ValidateForRun — a zero/unset value is a valid policy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,3 +46,8 @@ TAPIR_SECRETS_FILE=
|
|||||||
# --- run loop -------------------------------------------------------------
|
# --- run loop -------------------------------------------------------------
|
||||||
# Empty/0 = single pass. Set (e.g. 15m) to poll on that cadence.
|
# Empty/0 = single pass. Set (e.g. 15m) to poll on that cadence.
|
||||||
TAPIR_POLL_INTERVAL=
|
TAPIR_POLL_INTERVAL=
|
||||||
|
# How long to wait before re-fetching a transcript that returned HTTP 429
|
||||||
|
# (rate_limited). Inside the window the video is skipped without hitting the
|
||||||
|
# caption endpoint; after it expires the video is retried. 0 = always retry.
|
||||||
|
# Go duration; default 1h.
|
||||||
|
TAPIR_FETCH_BACKOFF=
|
||||||
|
|||||||
@@ -59,6 +59,12 @@ type Config struct {
|
|||||||
// PollInterval, when > 0, makes `run` loop on that cadence; 0 means run once.
|
// PollInterval, when > 0, makes `run` loop on that cadence; 0 means run once.
|
||||||
PollInterval time.Duration
|
PollInterval time.Duration
|
||||||
|
|
||||||
|
// FetchBackoff is how long the run loop waits before re-fetching a transcript
|
||||||
|
// that previously returned HTTP 429 (rate_limited). Inside the window the video
|
||||||
|
// is skipped without hitting the caption endpoint, saving requests; after it
|
||||||
|
// expires the video is retried. Zero means "always retry" (no backoff).
|
||||||
|
FetchBackoff time.Duration
|
||||||
|
|
||||||
// HTTPAddr is the listen address for `tapir serve` (the Stage-0 web UI).
|
// HTTPAddr is the listen address for `tapir serve` (the Stage-0 web UI).
|
||||||
HTTPAddr string
|
HTTPAddr string
|
||||||
|
|
||||||
@@ -85,6 +91,7 @@ const (
|
|||||||
defaultYTConnectRedirectURL = "https://tapir.d-ma.be/oauth/youtube/callback"
|
defaultYTConnectRedirectURL = "https://tapir.d-ma.be/oauth/youtube/callback"
|
||||||
defaultOAuthRedirectAddr = "localhost:8080"
|
defaultOAuthRedirectAddr = "localhost:8080"
|
||||||
defaultHTTPAddr = ":8080"
|
defaultHTTPAddr = ":8080"
|
||||||
|
defaultFetchBackoff = time.Hour
|
||||||
)
|
)
|
||||||
|
|
||||||
// Load reads the environment into a Config, applying defaults. It does not
|
// Load reads the environment into a Config, applying defaults. It does not
|
||||||
@@ -124,6 +131,12 @@ func Load() (Config, error) {
|
|||||||
}
|
}
|
||||||
c.PollInterval = interval
|
c.PollInterval = interval
|
||||||
|
|
||||||
|
backoff, err := durationOr("TAPIR_FETCH_BACKOFF", defaultFetchBackoff)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, err
|
||||||
|
}
|
||||||
|
c.FetchBackoff = backoff
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ func TestLoad_AppliesDefaults(t *testing.T) {
|
|||||||
if c.PollInterval != 0 {
|
if c.PollInterval != 0 {
|
||||||
t.Errorf("PollInterval = %v, want 0 (run once)", c.PollInterval)
|
t.Errorf("PollInterval = %v, want 0 (run once)", c.PollInterval)
|
||||||
}
|
}
|
||||||
|
if c.FetchBackoff != defaultFetchBackoff {
|
||||||
|
t.Errorf("FetchBackoff = %v, want default %v", c.FetchBackoff, defaultFetchBackoff)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoad_ParsesValues(t *testing.T) {
|
func TestLoad_ParsesValues(t *testing.T) {
|
||||||
@@ -56,6 +59,7 @@ func TestLoad_ParsesValues(t *testing.T) {
|
|||||||
"TAPIR_SUMMARIZER_TIMEOUT": "90s",
|
"TAPIR_SUMMARIZER_TIMEOUT": "90s",
|
||||||
"TAPIR_DB_DSN": "postgres://x",
|
"TAPIR_DB_DSN": "postgres://x",
|
||||||
"TAPIR_POLL_INTERVAL": "10m",
|
"TAPIR_POLL_INTERVAL": "10m",
|
||||||
|
"TAPIR_FETCH_BACKOFF": "30m",
|
||||||
})
|
})
|
||||||
|
|
||||||
c, err := Load()
|
c, err := Load()
|
||||||
@@ -77,6 +81,9 @@ func TestLoad_ParsesValues(t *testing.T) {
|
|||||||
if c.PollInterval != 10*time.Minute {
|
if c.PollInterval != 10*time.Minute {
|
||||||
t.Errorf("PollInterval = %v, want 10m", c.PollInterval)
|
t.Errorf("PollInterval = %v, want 10m", c.PollInterval)
|
||||||
}
|
}
|
||||||
|
if c.FetchBackoff != 30*time.Minute {
|
||||||
|
t.Errorf("FetchBackoff = %v, want 30m", c.FetchBackoff)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoad_RejectsBadDuration(t *testing.T) {
|
func TestLoad_RejectsBadDuration(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user