Skip to content

Commit 569fdad

Browse files
feat: make CVAT host configurable
Add a `host` field to `Client` (default https://app.cvat.ai), configurable via the `CVAT_HOST` env var / `.env.cvat.secrets` or the `host=` argument. A validator normalizes bare hosts to `https://` and strips trailing slashes. Passing an explicit scheme lets the CVAT SDK skip its HTTPS/HTTP auto-detection, which intermittently times out and raises InvalidHostException when the server is slow to answer /api/server/about. Supports the migration to the self-hosted server at cvat.nextml.com. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 43d1223 commit 569fdad

3 files changed

Lines changed: 34 additions & 3 deletions

File tree

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ CVAT_USERNAME=username
1515
CVAT_PASSWORD=password
1616
```
1717

18+
By default the client connects to `https://app.cvat.ai`. To use a self-hosted
19+
server, set `CVAT_HOST` (a bare host is fine — `https://` is assumed):
20+
21+
```bash
22+
CVAT_HOST=https://cvat.nextml.com
23+
```
24+
25+
You can also pass it directly: `next_cvat.Client(host="https://cvat.nextml.com", ...)`.
26+
1827
If you don't want to use your username and password, you can create a token:
1928

2029
```bash

next_cvat/client/client.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from cvat_sdk import Client as CVATClient
77
from cvat_sdk import make_client
8-
from pydantic import BaseModel
8+
from pydantic import BaseModel, field_validator
99

1010
from next_cvat.access_token import AccessToken
1111
from next_cvat.settings import settings
@@ -16,11 +16,32 @@
1616
from .project import Project
1717
from .task import Task
1818

19+
#: Default CVAT server. Override via the ``CVAT_HOST`` environment variable
20+
#: (e.g. ``CVAT_HOST=https://cvat.nextml.com``) or the ``host=`` argument.
21+
DEFAULT_CVAT_HOST = "https://app.cvat.ai"
22+
1923

2024
class Client(BaseModel):
2125
username: str | None = None
2226
password: str | None = None
2327
token: str | None = None
28+
host: str = DEFAULT_CVAT_HOST
29+
30+
@field_validator("host", mode="before")
31+
@classmethod
32+
def _normalize_host(cls, value: str | None) -> str:
33+
"""Default empty hosts and ensure an explicit scheme is present.
34+
35+
Passing a scheme (``https://`` / ``http://``) lets the CVAT SDK skip its
36+
flaky HTTPS/HTTP auto-detection, which otherwise times out and raises
37+
``InvalidHostException`` when the server is slow to answer.
38+
"""
39+
if value is None or value == "":
40+
return DEFAULT_CVAT_HOST
41+
value = value.rstrip("/")
42+
if not value.startswith(("http://", "https://")):
43+
value = f"https://{value}"
44+
return value
2445

2546
@classmethod
2647
def from_env(cls, env_prefix: str | None = None) -> Client:
@@ -52,14 +73,14 @@ def cvat_client(self) -> Generator[CVATClient, Any, Any]:
5273
@contextmanager
5374
def basic_cvat_client(self) -> Generator[CVATClient, None, None]:
5475
with make_client(
55-
host="app.cvat.ai", credentials=(self.username, self.password)
76+
host=self.host, credentials=(self.username, self.password)
5677
) as client:
5778
client.login((self.username, self.password))
5879
yield client
5980

6081
@contextmanager
6182
def token_cvat_client(self) -> Generator[CVATClient, None, None]:
62-
with make_client(host="app.cvat.ai") as client:
83+
with make_client(host=self.host) as client:
6384
token = AccessToken.deserialize(self.token)
6485

6586
# Only set Authorization header if we have a real API key (not session-based)

next_cvat/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ class Settings(
1515
username: str | None = None
1616
password: str | None = None
1717
token: str | None = None
18+
host: str | None = None
1819

1920
return Settings()

0 commit comments

Comments
 (0)