diff --git a/.gitignore b/.gitignore index fe8cb9f..4aac829 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,4 @@ build/ htmlcov/ .claude/ .DS_Store -tests/ .benchmarks/ diff --git a/src/sharpapi/_base.py b/src/sharpapi/_base.py index 61879c2..e2c83af 100644 --- a/src/sharpapi/_base.py +++ b/src/sharpapi/_base.py @@ -33,6 +33,15 @@ RETRY_MAX_DELAY = 4.0 +def normalize_base_url(base_url: str) -> str: + """Return the API origin URL, accepting values with a trailing /api/v1.""" + cleaned = base_url.rstrip("/") + suffix = "/api/v1" + if cleaned.endswith(suffix): + return cleaned[: -len(suffix)] + return cleaned + + def should_retry(response: httpx.Response | None, exc: Exception | None) -> bool: """True for transient upstream failures worth retrying.""" if exc is not None: diff --git a/src/sharpapi/async_client.py b/src/sharpapi/async_client.py index 2ba71cf..2ab8b1f 100644 --- a/src/sharpapi/async_client.py +++ b/src/sharpapi/async_client.py @@ -15,6 +15,7 @@ AuthMethod, handle_errors, make_headers, + normalize_base_url, parse_rate_limit, parse_response, retry_delay, @@ -89,7 +90,7 @@ def __init__( self._api_key = api_key self._auth_method: AuthMethod = auth_method - self._base_url = base_url.rstrip("/") + self._base_url = normalize_base_url(base_url) self._timeout = timeout self._http = httpx.AsyncClient( base_url=f"{self._base_url}/api/v1", diff --git a/src/sharpapi/client.py b/src/sharpapi/client.py index 9e7f9f5..4c99bf8 100644 --- a/src/sharpapi/client.py +++ b/src/sharpapi/client.py @@ -15,6 +15,7 @@ AuthMethod, handle_errors, make_headers, + normalize_base_url, parse_rate_limit, parse_response, retry_delay, @@ -97,7 +98,7 @@ def __init__( self._api_key = api_key self._auth_method: AuthMethod = auth_method - self._base_url = base_url.rstrip("/") + self._base_url = normalize_base_url(base_url) self._timeout = timeout self._http = httpx.Client( base_url=f"{self._base_url}/api/v1", diff --git a/tests/test_base_url.py b/tests/test_base_url.py new file mode 100644 index 0000000..47ba3ec --- /dev/null +++ b/tests/test_base_url.py @@ -0,0 +1,23 @@ +from sharpapi import AsyncSharpAPI, SharpAPI + + +def test_sync_client_accepts_api_v1_base_url_without_double_prefix(): + client = SharpAPI("sk_test", base_url="https://api.sharpapi.io/api/v1/") + + assert str(client._http.base_url) == "https://api.sharpapi.io/api/v1/" + assert client._base_url == "https://api.sharpapi.io" + + stream = client.stream.odds() + assert stream._url.startswith("https://api.sharpapi.io/api/v1/stream?") + assert "/api/v1/api/v1/" not in stream._url + + client.close() + + +async def test_async_client_accepts_api_v1_base_url_without_double_prefix(): + client = AsyncSharpAPI("sk_test", base_url="https://api.sharpapi.io/api/v1/") + + assert str(client._http.base_url) == "https://api.sharpapi.io/api/v1/" + assert client._base_url == "https://api.sharpapi.io" + + await client.close()