Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.0.1-alpha.1"
".": "0.1.0-alpha.1"
}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.1.0-alpha.1 (2024-11-29)

Full Changelog: [v0.0.1-alpha.1...v0.1.0-alpha.1](https://github.com/makegov/tango-python/compare/v0.0.1-alpha.1...v0.1.0-alpha.1)

### Features

* **api:** update via SDK Studio ([#4](https://github.com/makegov/tango-python/issues/4)) ([53ec9bd](https://github.com/makegov/tango-python/commit/53ec9bd1e1938224f9fd6ff0036fd61f3bbbcc67))

## 0.0.1-alpha.1 (2024-11-29)

Full Changelog: [v0.0.1-alpha.0...v0.0.1-alpha.1](https://github.com/makegov/tango-python/compare/v0.0.1-alpha.0...v0.0.1-alpha.1)
Expand Down
31 changes: 11 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,32 @@ pip install --pre tango-python
The full API of this library can be found in [api.md](api.md).

```python
import os
from tango import Tango

client = Tango(
client_id="My Client ID",
client_secret="My Client Secret",
access_token=os.environ.get("TANGO_API_ACCESS_TOKEN"), # This is the default and can be omitted
)

schema = client.schemas.retrieve()
```

While you can provide a `access_token` keyword argument,
we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/)
to add `TANGO_API_ACCESS_TOKEN="My Access Token"` to your `.env` file
so that your Access Token is not stored in source control.

## Async usage

Simply import `AsyncTango` instead of `Tango` and use `await` with each API call:

```python
import os
import asyncio
from tango import AsyncTango

client = AsyncTango(
client_id="My Client ID",
client_secret="My Client Secret",
access_token=os.environ.get("TANGO_API_ACCESS_TOKEN"), # This is the default and can be omitted
)


Expand Down Expand Up @@ -79,10 +84,7 @@ All errors inherit from `tango.APIError`.
import tango
from tango import Tango

client = Tango(
client_id="My Client ID",
client_secret="My Client Secret",
)
client = Tango()

try:
client.schemas.retrieve()
Expand Down Expand Up @@ -125,8 +127,6 @@ from tango import Tango
client = Tango(
# default is 2
max_retries=0,
client_id="My Client ID",
client_secret="My Client Secret",
)

# Or, configure per-request:
Expand All @@ -145,15 +145,11 @@ from tango import Tango
client = Tango(
# 20 seconds (default is 1 minute)
timeout=20.0,
client_id="My Client ID",
client_secret="My Client Secret",
)

# More granular control:
client = Tango(
timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
client_id="My Client ID",
client_secret="My Client Secret",
)

# Override per-request:
Expand Down Expand Up @@ -197,10 +193,7 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to
```py
from tango import Tango

client = Tango(
client_id="My Client ID",
client_secret="My Client Secret",
)
client = Tango()
response = client.schemas.with_raw_response.retrieve()
print(response.headers.get('X-My-Header'))

Expand Down Expand Up @@ -281,8 +274,6 @@ client = Tango(
proxies="http://my.test.proxy.example.com",
transport=httpx.HTTPTransport(local_address="0.0.0.0"),
),
client_id="My Client ID",
client_secret="My Client Secret",
)
```

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "tango-python"
version = "0.0.1-alpha.1"
version = "0.1.0-alpha.1"
description = "The official Python library for the tango API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down
80 changes: 32 additions & 48 deletions src/tango/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,12 @@ class Tango(SyncAPIClient):
with_streaming_response: TangoWithStreamedResponse

# client options
client_id: str
client_secret: str
access_token: str

def __init__(
self,
*,
client_id: str | None = None,
client_secret: str | None = None,
access_token: str | None = None,
base_url: str | httpx.URL | None = None,
timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
max_retries: int = DEFAULT_MAX_RETRIES,
Expand All @@ -95,25 +93,15 @@ def __init__(
) -> None:
"""Construct a new synchronous tango client instance.

This automatically infers the following arguments from their corresponding environment variables if they are not provided:
- `client_id` from `CLIENT_ID`
- `client_secret` from `CLIENT_SECRET`
This automatically infers the `access_token` argument from the `TANGO_API_ACCESS_TOKEN` environment variable if it is not provided.
"""
if client_id is None:
client_id = os.environ.get("CLIENT_ID")
if client_id is None:
if access_token is None:
access_token = os.environ.get("TANGO_API_ACCESS_TOKEN")
if access_token is None:
raise TangoError(
"The client_id client option must be set either by passing client_id to the client or by setting the CLIENT_ID environment variable"
"The access_token client option must be set either by passing access_token to the client or by setting the TANGO_API_ACCESS_TOKEN environment variable"
)
self.client_id = client_id

if client_secret is None:
client_secret = os.environ.get("CLIENT_SECRET")
if client_secret is None:
raise TangoError(
"The client_secret client option must be set either by passing client_secret to the client or by setting the CLIENT_SECRET environment variable"
)
self.client_secret = client_secret
self.access_token = access_token

if base_url is None:
base_url = os.environ.get("TANGO_BASE_URL")
Expand Down Expand Up @@ -155,6 +143,12 @@ def __init__(
def qs(self) -> Querystring:
return Querystring(array_format="comma")

@property
@override
def auth_headers(self) -> dict[str, str]:
access_token = self.access_token
return {"Authorization": f"Bearer {access_token}"}

@property
@override
def default_headers(self) -> dict[str, str | Omit]:
Expand All @@ -167,8 +161,7 @@ def default_headers(self) -> dict[str, str | Omit]:
def copy(
self,
*,
client_id: str | None = None,
client_secret: str | None = None,
access_token: str | None = None,
base_url: str | httpx.URL | None = None,
timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
http_client: httpx.Client | None = None,
Expand Down Expand Up @@ -202,8 +195,7 @@ def copy(

http_client = http_client or self._client
return self.__class__(
client_id=client_id or self.client_id,
client_secret=client_secret or self.client_secret,
access_token=access_token or self.access_token,
base_url=base_url or self.base_url,
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
http_client=http_client,
Expand Down Expand Up @@ -272,14 +264,12 @@ class AsyncTango(AsyncAPIClient):
with_streaming_response: AsyncTangoWithStreamedResponse

# client options
client_id: str
client_secret: str
access_token: str

def __init__(
self,
*,
client_id: str | None = None,
client_secret: str | None = None,
access_token: str | None = None,
base_url: str | httpx.URL | None = None,
timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
max_retries: int = DEFAULT_MAX_RETRIES,
Expand All @@ -301,25 +291,15 @@ def __init__(
) -> None:
"""Construct a new async tango client instance.

This automatically infers the following arguments from their corresponding environment variables if they are not provided:
- `client_id` from `CLIENT_ID`
- `client_secret` from `CLIENT_SECRET`
This automatically infers the `access_token` argument from the `TANGO_API_ACCESS_TOKEN` environment variable if it is not provided.
"""
if client_id is None:
client_id = os.environ.get("CLIENT_ID")
if client_id is None:
if access_token is None:
access_token = os.environ.get("TANGO_API_ACCESS_TOKEN")
if access_token is None:
raise TangoError(
"The client_id client option must be set either by passing client_id to the client or by setting the CLIENT_ID environment variable"
"The access_token client option must be set either by passing access_token to the client or by setting the TANGO_API_ACCESS_TOKEN environment variable"
)
self.client_id = client_id

if client_secret is None:
client_secret = os.environ.get("CLIENT_SECRET")
if client_secret is None:
raise TangoError(
"The client_secret client option must be set either by passing client_secret to the client or by setting the CLIENT_SECRET environment variable"
)
self.client_secret = client_secret
self.access_token = access_token

if base_url is None:
base_url = os.environ.get("TANGO_BASE_URL")
Expand Down Expand Up @@ -361,6 +341,12 @@ def __init__(
def qs(self) -> Querystring:
return Querystring(array_format="comma")

@property
@override
def auth_headers(self) -> dict[str, str]:
access_token = self.access_token
return {"Authorization": f"Bearer {access_token}"}

@property
@override
def default_headers(self) -> dict[str, str | Omit]:
Expand All @@ -373,8 +359,7 @@ def default_headers(self) -> dict[str, str | Omit]:
def copy(
self,
*,
client_id: str | None = None,
client_secret: str | None = None,
access_token: str | None = None,
base_url: str | httpx.URL | None = None,
timeout: float | Timeout | None | NotGiven = NOT_GIVEN,
http_client: httpx.AsyncClient | None = None,
Expand Down Expand Up @@ -408,8 +393,7 @@ def copy(

http_client = http_client or self._client
return self.__class__(
client_id=client_id or self.client_id,
client_secret=client_secret or self.client_secret,
access_token=access_token or self.access_token,
base_url=base_url or self.base_url,
timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
http_client=http_client,
Expand Down
2 changes: 1 addition & 1 deletion src/tango/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "tango"
__version__ = "0.0.1-alpha.1" # x-release-please-version
__version__ = "0.1.0-alpha.1" # x-release-please-version
11 changes: 3 additions & 8 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None:

base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")

client_id = "My Client ID"
client_secret = "My Client Secret"
access_token = "My Access Token"


@pytest.fixture(scope="session")
Expand All @@ -38,9 +37,7 @@ def client(request: FixtureRequest) -> Iterator[Tango]:
if not isinstance(strict, bool):
raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")

with Tango(
base_url=base_url, client_id=client_id, client_secret=client_secret, _strict_response_validation=strict
) as client:
with Tango(base_url=base_url, access_token=access_token, _strict_response_validation=strict) as client:
yield client


Expand All @@ -50,7 +47,5 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncTango]:
if not isinstance(strict, bool):
raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}")

async with AsyncTango(
base_url=base_url, client_id=client_id, client_secret=client_secret, _strict_response_validation=strict
) as client:
async with AsyncTango(base_url=base_url, access_token=access_token, _strict_response_validation=strict) as client:
yield client
Loading
Loading