Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ $ export TASKBADGER_API_KEY=***
$ taskbadger run "nightly-backup" -- ./backup.sh
```

### Request timeout

API requests time out after 5 seconds by default. Override it with the `timeout` argument
(seconds), or with the `TASKBADGER_HTTP_TIMEOUT` environment variable, which the CLI also
honours:

```python
taskbadger.init(token="***", timeout=30)
```

Pass an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/) for finer control,
or `httpx.Timeout(None)` to disable timeouts entirely.

### Procrastinate Integration

The SDK includes optional support for the [Procrastinate](https://procrastinate.readthedocs.io/) task queue.
Expand Down
8 changes: 7 additions & 1 deletion taskbadger/mug.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@
from contextvars import ContextVar
from copy import deepcopy

import httpx

from taskbadger.context_providers import ContextProvider
from taskbadger.internal import AuthenticatedClient
from taskbadger.systems import System

_local = ContextVar("taskbadger_client")

#: Default timeout (seconds) applied to all API requests.
DEFAULT_HTTP_TIMEOUT = 5.0


Callback = str | Callable[[dict], dict | None]

Expand All @@ -23,9 +28,10 @@ class Settings:
systems: dict[str, System] = dataclasses.field(default_factory=dict)
before_create: Callback = None
context_providers: list[ContextProvider] = dataclasses.field(default_factory=list)
timeout: float | httpx.Timeout | None = DEFAULT_HTTP_TIMEOUT

def get_client(self):
return AuthenticatedClient(self.base_url, self.token)
return AuthenticatedClient(self.base_url, self.token, timeout=httpx.Timeout(self.timeout))

def as_kwargs(self):
return {
Expand Down
25 changes: 23 additions & 2 deletions taskbadger/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import warnings
from typing import Any

import httpx

from taskbadger._error_context import capture_error_data
from taskbadger.context_providers import ContextProvider
from taskbadger.exceptions import (
Expand All @@ -29,7 +31,7 @@
TaskRequest,
)
from taskbadger.internal.types import UNSET
from taskbadger.mug import Badger, Callback, Session, Settings
from taskbadger.mug import DEFAULT_HTTP_TIMEOUT, Badger, Callback, Session, Settings
from taskbadger.systems import System
from taskbadger.utils import import_string

Expand Down Expand Up @@ -58,6 +60,17 @@ def _parse_token(token):
return None


def _timeout_from_env():
"""Read the request timeout from ``TASKBADGER_HTTP_TIMEOUT``, falling back to the default."""
raw = os.environ.get("TASKBADGER_HTTP_TIMEOUT")
if not raw:
return DEFAULT_HTTP_TIMEOUT
try:
return float(raw)
except ValueError as e:
raise ConfigurationError(f"TASKBADGER_HTTP_TIMEOUT must be a number, got {raw!r}") from e


def init(
organization_slug: str = None,
project_slug: str = None,
Expand All @@ -66,6 +79,7 @@ def init(
tags: dict[str, str] = None,
before_create: Callback = None,
context_providers: list[ContextProvider] = None,
timeout: float | httpx.Timeout = None,
):
"""Initialize Task Badger client.

Expand All @@ -79,10 +93,13 @@ def init(
Arguments:
context_providers: Providers consulted when a tracked task errors, to attach extra
context (e.g. a Sentry issue link) to the task's `data`. See `taskbadger.context_providers`.
timeout: Timeout (seconds) for API requests. Defaults to the ``TASKBADGER_HTTP_TIMEOUT``
environment variable if set, otherwise 5 seconds. Pass an `httpx.Timeout` for finer
control, or ``httpx.Timeout(None)`` to disable timeouts.

Call this function once per thread.
"""
_init(_TB_HOST, organization_slug, project_slug, token, systems, tags, before_create, context_providers)
_init(_TB_HOST, organization_slug, project_slug, token, systems, tags, before_create, context_providers, timeout)


def _init(
Expand All @@ -94,11 +111,14 @@ def _init(
tags: dict[str, str] = None,
before_create: Callback = None,
context_providers: list[ContextProvider] = None,
timeout: float | httpx.Timeout = None,
):
host = host or os.environ.get("TASKBADGER_HOST", "https://taskbadger.net")
organization_slug = organization_slug or os.environ.get("TASKBADGER_ORG")
project_slug = project_slug or os.environ.get("TASKBADGER_PROJECT")
token = token or os.environ.get("TASKBADGER_API_KEY")
if timeout is None:
timeout = _timeout_from_env()

if token:
parsed = _parse_token(token)
Expand Down Expand Up @@ -127,6 +147,7 @@ def _init(
systems={system.identifier: system for system in systems},
before_create=before_create,
context_providers=context_providers or [],
timeout=timeout,
)
Badger.current.bind(settings, tags)
else:
Expand Down
38 changes: 37 additions & 1 deletion tests/test_init.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import warnings

import httpx
import pytest

from taskbadger import Badger, init
from taskbadger.exceptions import ConfigurationError
from taskbadger.mug import _local
from taskbadger.mug import DEFAULT_HTTP_TIMEOUT, _local


@pytest.fixture(autouse=True)
Expand Down Expand Up @@ -34,5 +35,40 @@ def test_init_import_before_create_fail():
init("org", "project", "token", before_create="missing")


def test_init_default_timeout():
_init_token()
assert Badger.current.settings.timeout == DEFAULT_HTTP_TIMEOUT
assert Badger.current.client().get_httpx_client().timeout == httpx.Timeout(DEFAULT_HTTP_TIMEOUT)


def test_init_timeout_override():
_init_token(timeout=30)
assert Badger.current.client().get_httpx_client().timeout == httpx.Timeout(30)


def test_init_timeout_from_env(monkeypatch):
monkeypatch.setenv("TASKBADGER_HTTP_TIMEOUT", "12.5")
_init_token()
assert Badger.current.settings.timeout == 12.5


def test_init_timeout_arg_beats_env(monkeypatch):
monkeypatch.setenv("TASKBADGER_HTTP_TIMEOUT", "12.5")
_init_token(timeout=httpx.Timeout(1, connect=2))
assert Badger.current.client().get_httpx_client().timeout == httpx.Timeout(1, connect=2)


def test_init_timeout_from_env_invalid(monkeypatch):
monkeypatch.setenv("TASKBADGER_HTTP_TIMEOUT", "soon")
with pytest.raises(ConfigurationError):
_init_token()


def _init_token(**kwargs):
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
init("org", "project", "token", **kwargs)


def _before_create(_):
pass