From c78a467f21f5b8f0a9d21b9b4cba859a337f5449 Mon Sep 17 00:00:00 2001 From: Adir Amsalem Date: Sun, 30 Nov 2025 15:05:39 +0200 Subject: [PATCH] feat: add automatic API key detection from env var --- decart/client.py | 15 ++++++--- decart/errors.py | 4 ++- tests/test_client.py | 76 ++++++++++++++++++++++++++++++++------------ 3 files changed, 70 insertions(+), 25 deletions(-) diff --git a/decart/client.py b/decart/client.py index e12b392..c2fb5aa 100644 --- a/decart/client.py +++ b/decart/client.py @@ -1,3 +1,4 @@ +import os from typing import Any, Optional import aiohttp from pydantic import ValidationError @@ -20,14 +21,18 @@ class DecartClient: Decart API client for video and image generation/transformation. Args: - api_key: Your Decart API key + api_key: Your Decart API key. Defaults to the DECART_API_KEY environment variable. base_url: API base URL (defaults to production) integration: Optional integration identifier (e.g., "langchain/0.1.0") Example: ```python + # Option 1: Explicit API key client = DecartClient(api_key="your-key") + # Option 2: Using DECART_API_KEY environment variable + client = DecartClient() + # Image generation (sync) - use process() image = await client.process({ "model": models.image("lucy-pro-t2i"), @@ -44,17 +49,19 @@ class DecartClient: def __init__( self, - api_key: str, + api_key: Optional[str] = None, base_url: str = "https://api.decart.ai", integration: Optional[str] = None, ) -> None: - if not api_key or not api_key.strip(): + resolved_api_key = api_key or os.environ.get("DECART_API_KEY", "").strip() or None + + if not resolved_api_key: raise InvalidAPIKeyError() if not base_url.startswith(("http://", "https://")): raise InvalidBaseURLError(base_url) - self.api_key = api_key + self.api_key = resolved_api_key self.base_url = base_url self.integration = integration self._session: Optional[aiohttp.ClientSession] = None diff --git a/decart/errors.py b/decart/errors.py index 2454a3b..cb47456 100644 --- a/decart/errors.py +++ b/decart/errors.py @@ -26,7 +26,9 @@ class InvalidAPIKeyError(DecartSDKError): """Raised when API key is invalid or missing.""" def __init__(self) -> None: - super().__init__("API key is required and must be a non-empty string") + super().__init__( + "Missing API key. Pass `api_key` to DecartClient() or set the DECART_API_KEY environment variable." + ) class InvalidBaseURLError(DecartSDKError): diff --git a/tests/test_client.py b/tests/test_client.py index 5da5da8..3f75868 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -2,23 +2,59 @@ from decart import DecartClient, InvalidAPIKeyError, InvalidBaseURLError -def test_create_client_success() -> None: - client = DecartClient(api_key="test-key") - assert client is not None - assert client.process is not None - - -def test_create_client_invalid_api_key() -> None: - with pytest.raises(InvalidAPIKeyError): - DecartClient(api_key="") - - -def test_create_client_invalid_base_url() -> None: - with pytest.raises(InvalidBaseURLError): - DecartClient(api_key="test-key", base_url="invalid-url") - - -def test_create_client_custom_base_url() -> None: - client = DecartClient(api_key="test-key", base_url="https://custom.decart.ai") - assert client is not None - assert client.base_url == "https://custom.decart.ai" +class TestDecartClient: + """Tests for DecartClient initialization.""" + + def test_create_client_with_explicit_api_key(self) -> None: + """Creates a client with explicit api_key.""" + client = DecartClient(api_key="test-key") + assert client is not None + assert client.process is not None + assert client.api_key == "test-key" + + def test_create_client_from_env_var(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Creates a client using DECART_API_KEY env var.""" + monkeypatch.setenv("DECART_API_KEY", "env-api-key") + client = DecartClient() + assert client is not None + assert client.api_key == "env-api-key" + + def test_explicit_api_key_takes_precedence(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Explicit api_key takes precedence over env var.""" + monkeypatch.setenv("DECART_API_KEY", "env-api-key") + client = DecartClient(api_key="explicit-key") + assert client.api_key == "explicit-key" + + def test_create_client_no_api_key_no_env(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Throws an error if api key is not provided and env var is not set.""" + monkeypatch.delenv("DECART_API_KEY", raising=False) + with pytest.raises(InvalidAPIKeyError, match="Missing API key"): + DecartClient() + + def test_create_client_empty_api_key(self) -> None: + """Throws an error if api key is empty string.""" + with pytest.raises(InvalidAPIKeyError, match="Missing API key"): + DecartClient(api_key="") + + def test_create_client_empty_env_var(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Throws an error if env var is empty string.""" + monkeypatch.setenv("DECART_API_KEY", "") + with pytest.raises(InvalidAPIKeyError, match="Missing API key"): + DecartClient() + + def test_create_client_whitespace_env_var(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Throws an error if env var is only whitespace.""" + monkeypatch.setenv("DECART_API_KEY", " ") + with pytest.raises(InvalidAPIKeyError, match="Missing API key"): + DecartClient() + + def test_create_client_invalid_base_url(self) -> None: + """Throws an error if invalid base url is provided.""" + with pytest.raises(InvalidBaseURLError): + DecartClient(api_key="test-key", base_url="invalid-url") + + def test_create_client_custom_base_url(self) -> None: + """Creates a client with custom base url.""" + client = DecartClient(api_key="test-key", base_url="https://custom.decart.ai") + assert client is not None + assert client.base_url == "https://custom.decart.ai"