diff --git a/src/gradient_labs/_secret_revoke.py b/src/gradient_labs/_secret_revoke.py new file mode 100644 index 0000000..a2bb30c --- /dev/null +++ b/src/gradient_labs/_secret_revoke.py @@ -0,0 +1,8 @@ +from ._http_client import HttpClient + + +def revoke_secret(*, client: HttpClient, name: str) -> None: + """revoke_secret permanently deletes a secret. This action cannot be undone. + + Note: requires a `Management` API key.""" + _ = client.delete(path=f"secrets/{name}", body={}) diff --git a/src/gradient_labs/_secret_write.py b/src/gradient_labs/_secret_write.py new file mode 100644 index 0000000..220235d --- /dev/null +++ b/src/gradient_labs/_secret_write.py @@ -0,0 +1,44 @@ +from typing import Optional +from datetime import datetime +from dataclasses import dataclass +from dataclasses_json import dataclass_json + +from ._http_client import HttpClient +from .secret import Secret, RefreshMechanismHTTP + + +@dataclass_json +@dataclass(frozen=True) +class WriteSecretParams: + """WriteSecretParams contains the parameters for writing (creating or updating) a secret.""" + + # name is the unique identifier for the secret. + name: str + + # value is the secret value to store. + value: str + + # expiry is the optional expiration time for the secret. + expiry: Optional[datetime] = None + + # refresh_mechanism_http is the optional configuration for automatically refreshing + # the secret value using an HTTP request (e.g., OAuth token refresh). + refresh_mechanism_http: Optional[RefreshMechanismHTTP] = None + + +def write_secret(*, client: HttpClient, params: WriteSecretParams) -> Secret: + """write_secret creates or updates a secret. If a secret with the given name already exists, + it will be updated with the new value and configuration. + + Note: requires a `Management` API key.""" + # Build body manually to handle datetime serialization and exclude name from body + body = { + "value": params.value, + } + if params.expiry is not None: + body["expiry"] = HttpClient.localize(params.expiry) + if params.refresh_mechanism_http is not None: + body["refresh_mechanism_http"] = params.refresh_mechanism_http.to_dict() + + rsp = client.put(path=f"secrets/{params.name}", body=body) + return Secret.from_dict(rsp) diff --git a/src/gradient_labs/_secrets_list.py b/src/gradient_labs/_secrets_list.py new file mode 100644 index 0000000..a05895c --- /dev/null +++ b/src/gradient_labs/_secrets_list.py @@ -0,0 +1,25 @@ +from typing import List +from dataclasses import dataclass +from dataclasses_json import dataclass_json + +from .secret import Secret +from ._http_client import HttpClient + + +@dataclass_json +@dataclass(frozen=True) +class SecretsList: + """SecretsList contains the list of all secrets.""" + + secrets: List[Secret] + + +def list_secrets(*, client: HttpClient) -> SecretsList: + """list_secrets returns all of your secrets. + + Note: requires a `Management` API key.""" + rsp = client.get( + path="secrets", + body={}, + ) + return SecretsList.from_dict(rsp) diff --git a/src/gradient_labs/client.py b/src/gradient_labs/client.py index a330715..779d8a0 100644 --- a/src/gradient_labs/client.py +++ b/src/gradient_labs/client.py @@ -73,9 +73,14 @@ from ._note_update import update_note, UpdateNoteParams from ._note_set_status import set_note_status, SetNoteStatusParams +from ._secret_write import write_secret, WriteSecretParams +from ._secrets_list import list_secrets, SecretsList +from ._secret_revoke import revoke_secret + from ._http_client import HttpClient, API_BASE_URL from .tool import * from .note import Note +from .secret import Secret from .webhook import Webhook, WebhookEvent @@ -551,3 +556,30 @@ def set_note_status(self, *, note_id: str, params: SetNoteStatusParams) -> None: note_id=note_id, params=params, ) + + # Secret Operations + + def write_secret(self, *, params: WriteSecretParams) -> Secret: + """write_secret creates or updates a secret. If a secret with the given name already exists, + it will be updated with the new value and configuration. + + Note: requires a `Management` API key.""" + return write_secret( + client=self.http_client, + params=params, + ) + + def list_secrets(self) -> SecretsList: + """list_secrets returns all of your secrets. + + Note: requires a `Management` API key.""" + return list_secrets(client=self.http_client) + + def revoke_secret(self, *, name: str) -> None: + """revoke_secret permanently deletes a secret. This action cannot be undone. + + Note: requires a `Management` API key.""" + revoke_secret( + client=self.http_client, + name=name, + ) diff --git a/src/gradient_labs/secret.py b/src/gradient_labs/secret.py new file mode 100644 index 0000000..3e589c5 --- /dev/null +++ b/src/gradient_labs/secret.py @@ -0,0 +1,43 @@ +from typing import Optional +from dataclasses import dataclass +from dataclasses_json import dataclass_json +from datetime import datetime + +from .tool import HTTPDefinition + + +@dataclass_json +@dataclass(frozen=True) +class RefreshMechanismHTTP: + """RefreshMechanismHTTP defines how to automatically refresh a secret's value + using an HTTP request. This is commonly used for OAuth access tokens that + need periodic renewal.""" + + # request_definition specifies the HTTP request to make to refresh the secret. + request_definition: HTTPDefinition + + # response_param_name is the JSON field name in the response that contains + # the new secret value. + response_param_name: str + + +@dataclass_json +@dataclass +class Secret: + """Secret represents a stored secret with optional expiration and refresh configuration.""" + + # name is the unique identifier for the secret. + name: str + + # created is when the secret was first created. + created: datetime + + # updated is when the secret was last updated. + updated: datetime + + # expiry is the optional expiration time for the secret. + expiry: Optional[datetime] = None + + # refresh_mechanism_http is the optional configuration for automatically refreshing + # the secret value using an HTTP request (e.g., OAuth token refresh). + refresh_mechanism_http: Optional[RefreshMechanismHTTP] = None