From 7e0f04265f5be8d8335bb2f588dfbb26acbabba5 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Tue, 5 May 2026 20:10:10 -0500 Subject: [PATCH] feat: Implement cancel subscription request and delete graph operation models --- .../api/billing/cancel_org_subscription.py | 64 +++- .../api/graph_operations/op_delete_graph.py | 289 ++++++++++++++++++ robosystems_client/models/__init__.py | 4 + .../models/cancel_subscription_request.py | 90 ++++++ robosystems_client/models/delete_graph_op.py | 67 ++++ 5 files changed, 505 insertions(+), 9 deletions(-) create mode 100644 robosystems_client/api/graph_operations/op_delete_graph.py create mode 100644 robosystems_client/models/cancel_subscription_request.py create mode 100644 robosystems_client/models/delete_graph_op.py diff --git a/robosystems_client/api/billing/cancel_org_subscription.py b/robosystems_client/api/billing/cancel_org_subscription.py index ec3df65..2a34605 100644 --- a/robosystems_client/api/billing/cancel_org_subscription.py +++ b/robosystems_client/api/billing/cancel_org_subscription.py @@ -6,16 +6,20 @@ from ... import errors from ...client import AuthenticatedClient, Client +from ...models.cancel_subscription_request import CancelSubscriptionRequest from ...models.error_response import ErrorResponse from ...models.graph_subscription_response import GraphSubscriptionResponse from ...models.http_validation_error import HTTPValidationError -from ...types import Response +from ...types import UNSET, Response, Unset def _get_kwargs( org_id: str, subscription_id: str, + *, + body: CancelSubscriptionRequest | Unset = UNSET, ) -> dict[str, Any]: + headers: dict[str, Any] = {} _kwargs: dict[str, Any] = { "method": "post", @@ -25,6 +29,12 @@ def _get_kwargs( ), } + if not isinstance(body, Unset): + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers return _kwargs @@ -93,15 +103,23 @@ def sync_detailed( subscription_id: str, *, client: AuthenticatedClient, + body: CancelSubscriptionRequest | Unset = UNSET, ) -> Response[ErrorResponse | GraphSubscriptionResponse | HTTPValidationError]: """Cancel Organization Subscription - Cancels at period end — subscription remains active through the current billing cycle. Requires org - owner role. + Cancels a subscription. Default behavior cancels at period end (subscription stays active through + the current cycle). Pass `immediate=true` with `confirm=` to terminate now and trigger + fast-path deprovisioning of the underlying resource. Requires org owner role. Args: org_id (str): subscription_id (str): + body (CancelSubscriptionRequest | Unset): Request to cancel a subscription. + + Default behavior cancels at period end (soft cancel). Pass `immediate=True` + to terminate the subscription right away — this requires `confirm` to equal + the subscription's `resource_id` (e.g. the graph_id) as a guard against + accidental destructive calls. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -114,6 +132,7 @@ def sync_detailed( kwargs = _get_kwargs( org_id=org_id, subscription_id=subscription_id, + body=body, ) response = client.get_httpx_client().request( @@ -128,15 +147,23 @@ def sync( subscription_id: str, *, client: AuthenticatedClient, + body: CancelSubscriptionRequest | Unset = UNSET, ) -> ErrorResponse | GraphSubscriptionResponse | HTTPValidationError | None: """Cancel Organization Subscription - Cancels at period end — subscription remains active through the current billing cycle. Requires org - owner role. + Cancels a subscription. Default behavior cancels at period end (subscription stays active through + the current cycle). Pass `immediate=true` with `confirm=` to terminate now and trigger + fast-path deprovisioning of the underlying resource. Requires org owner role. Args: org_id (str): subscription_id (str): + body (CancelSubscriptionRequest | Unset): Request to cancel a subscription. + + Default behavior cancels at period end (soft cancel). Pass `immediate=True` + to terminate the subscription right away — this requires `confirm` to equal + the subscription's `resource_id` (e.g. the graph_id) as a guard against + accidental destructive calls. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -150,6 +177,7 @@ def sync( org_id=org_id, subscription_id=subscription_id, client=client, + body=body, ).parsed @@ -158,15 +186,23 @@ async def asyncio_detailed( subscription_id: str, *, client: AuthenticatedClient, + body: CancelSubscriptionRequest | Unset = UNSET, ) -> Response[ErrorResponse | GraphSubscriptionResponse | HTTPValidationError]: """Cancel Organization Subscription - Cancels at period end — subscription remains active through the current billing cycle. Requires org - owner role. + Cancels a subscription. Default behavior cancels at period end (subscription stays active through + the current cycle). Pass `immediate=true` with `confirm=` to terminate now and trigger + fast-path deprovisioning of the underlying resource. Requires org owner role. Args: org_id (str): subscription_id (str): + body (CancelSubscriptionRequest | Unset): Request to cancel a subscription. + + Default behavior cancels at period end (soft cancel). Pass `immediate=True` + to terminate the subscription right away — this requires `confirm` to equal + the subscription's `resource_id` (e.g. the graph_id) as a guard against + accidental destructive calls. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -179,6 +215,7 @@ async def asyncio_detailed( kwargs = _get_kwargs( org_id=org_id, subscription_id=subscription_id, + body=body, ) response = await client.get_async_httpx_client().request(**kwargs) @@ -191,15 +228,23 @@ async def asyncio( subscription_id: str, *, client: AuthenticatedClient, + body: CancelSubscriptionRequest | Unset = UNSET, ) -> ErrorResponse | GraphSubscriptionResponse | HTTPValidationError | None: """Cancel Organization Subscription - Cancels at period end — subscription remains active through the current billing cycle. Requires org - owner role. + Cancels a subscription. Default behavior cancels at period end (subscription stays active through + the current cycle). Pass `immediate=true` with `confirm=` to terminate now and trigger + fast-path deprovisioning of the underlying resource. Requires org owner role. Args: org_id (str): subscription_id (str): + body (CancelSubscriptionRequest | Unset): Request to cancel a subscription. + + Default behavior cancels at period end (soft cancel). Pass `immediate=True` + to terminate the subscription right away — this requires `confirm` to equal + the subscription's `resource_id` (e.g. the graph_id) as a guard against + accidental destructive calls. Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. @@ -214,5 +259,6 @@ async def asyncio( org_id=org_id, subscription_id=subscription_id, client=client, + body=body, ) ).parsed diff --git a/robosystems_client/api/graph_operations/op_delete_graph.py b/robosystems_client/api/graph_operations/op_delete_graph.py new file mode 100644 index 0000000..de586c1 --- /dev/null +++ b/robosystems_client/api/graph_operations/op_delete_graph.py @@ -0,0 +1,289 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.delete_graph_op import DeleteGraphOp +from ...models.error_response import ErrorResponse +from ...models.http_validation_error import HTTPValidationError +from ...models.operation_envelope import OperationEnvelope +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + graph_id: str, + *, + body: DeleteGraphOp, + idempotency_key: None | str | Unset = UNSET, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + if not isinstance(idempotency_key, Unset): + headers["Idempotency-Key"] = idempotency_key + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/v1/graphs/{graph_id}/operations/delete-graph".format( + graph_id=quote(str(graph_id), safe=""), + ), + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> ErrorResponse | HTTPValidationError | OperationEnvelope | None: + if response.status_code == 202: + response_202 = OperationEnvelope.from_dict(response.json()) + + return response_202 + + if response.status_code == 400: + response_400 = ErrorResponse.from_dict(response.json()) + + return response_400 + + if response.status_code == 401: + response_401 = ErrorResponse.from_dict(response.json()) + + return response_401 + + if response.status_code == 403: + response_403 = ErrorResponse.from_dict(response.json()) + + return response_403 + + if response.status_code == 404: + response_404 = ErrorResponse.from_dict(response.json()) + + return response_404 + + if response.status_code == 409: + response_409 = ErrorResponse.from_dict(response.json()) + + return response_409 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if response.status_code == 429: + response_429 = ErrorResponse.from_dict(response.json()) + + return response_429 + + if response.status_code == 500: + response_500 = ErrorResponse.from_dict(response.json()) + + return response_500 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[ErrorResponse | HTTPValidationError | OperationEnvelope]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + graph_id: str, + *, + client: AuthenticatedClient, + body: DeleteGraphOp, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | HTTPValidationError | OperationEnvelope]: + """Delete Graph + + Permanently destroys a user graph: cancels its subscription immediately and queues fast-path + deprovisioning (LadybugDB database removed, DynamoDB slot freed, PG records cleaned). Requires + `confirm` to equal the URL `graph_id`. Caller must be admin on the graph. Not allowed on shared + repositories. Deprovisioning typically completes within ~10 minutes; poll the graph status to + verify. + + **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours + return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. + + Args: + graph_id (str): + idempotency_key (None | str | Unset): + body (DeleteGraphOp): Body for the delete-graph operation. + + Permanently destroys the graph: cancels its subscription immediately, then + triggers fast-path deprovisioning (LadybugDB database removed, DynamoDB slot + freed, PG records cleaned). Requires `confirm` to equal the URL `graph_id` + as a guard against accidental destructive calls. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ErrorResponse | HTTPValidationError | OperationEnvelope] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + body=body, + idempotency_key=idempotency_key, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + graph_id: str, + *, + client: AuthenticatedClient, + body: DeleteGraphOp, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | HTTPValidationError | OperationEnvelope | None: + """Delete Graph + + Permanently destroys a user graph: cancels its subscription immediately and queues fast-path + deprovisioning (LadybugDB database removed, DynamoDB slot freed, PG records cleaned). Requires + `confirm` to equal the URL `graph_id`. Caller must be admin on the graph. Not allowed on shared + repositories. Deprovisioning typically completes within ~10 minutes; poll the graph status to + verify. + + **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours + return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. + + Args: + graph_id (str): + idempotency_key (None | str | Unset): + body (DeleteGraphOp): Body for the delete-graph operation. + + Permanently destroys the graph: cancels its subscription immediately, then + triggers fast-path deprovisioning (LadybugDB database removed, DynamoDB slot + freed, PG records cleaned). Requires `confirm` to equal the URL `graph_id` + as a guard against accidental destructive calls. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ErrorResponse | HTTPValidationError | OperationEnvelope + """ + + return sync_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ).parsed + + +async def asyncio_detailed( + graph_id: str, + *, + client: AuthenticatedClient, + body: DeleteGraphOp, + idempotency_key: None | str | Unset = UNSET, +) -> Response[ErrorResponse | HTTPValidationError | OperationEnvelope]: + """Delete Graph + + Permanently destroys a user graph: cancels its subscription immediately and queues fast-path + deprovisioning (LadybugDB database removed, DynamoDB slot freed, PG records cleaned). Requires + `confirm` to equal the URL `graph_id`. Caller must be admin on the graph. Not allowed on shared + repositories. Deprovisioning typically completes within ~10 minutes; poll the graph status to + verify. + + **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours + return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. + + Args: + graph_id (str): + idempotency_key (None | str | Unset): + body (DeleteGraphOp): Body for the delete-graph operation. + + Permanently destroys the graph: cancels its subscription immediately, then + triggers fast-path deprovisioning (LadybugDB database removed, DynamoDB slot + freed, PG records cleaned). Requires `confirm` to equal the URL `graph_id` + as a guard against accidental destructive calls. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ErrorResponse | HTTPValidationError | OperationEnvelope] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + body=body, + idempotency_key=idempotency_key, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + graph_id: str, + *, + client: AuthenticatedClient, + body: DeleteGraphOp, + idempotency_key: None | str | Unset = UNSET, +) -> ErrorResponse | HTTPValidationError | OperationEnvelope | None: + """Delete Graph + + Permanently destroys a user graph: cancels its subscription immediately and queues fast-path + deprovisioning (LadybugDB database removed, DynamoDB slot freed, PG records cleaned). Requires + `confirm` to equal the URL `graph_id`. Caller must be admin on the graph. Not allowed on shared + repositories. Deprovisioning typically completes within ~10 minutes; poll the graph status to + verify. + + **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours + return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. + + Args: + graph_id (str): + idempotency_key (None | str | Unset): + body (DeleteGraphOp): Body for the delete-graph operation. + + Permanently destroys the graph: cancels its subscription immediately, then + triggers fast-path deprovisioning (LadybugDB database removed, DynamoDB slot + freed, PG records cleaned). Requires `confirm` to equal the URL `graph_id` + as a guard against accidental destructive calls. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ErrorResponse | HTTPValidationError | OperationEnvelope + """ + + return ( + await asyncio_detailed( + graph_id=graph_id, + client=client, + body=body, + idempotency_key=idempotency_key, + ) + ).parsed diff --git a/robosystems_client/models/__init__.py b/robosystems_client/models/__init__.py index adc1672..5ba4060 100644 --- a/robosystems_client/models/__init__.py +++ b/robosystems_client/models/__init__.py @@ -44,6 +44,7 @@ from .cancel_operation_response_canceloperation import ( CancelOperationResponseCanceloperation, ) +from .cancel_subscription_request import CancelSubscriptionRequest from .change_tier_op import ChangeTierOp from .change_tier_op_new_tier import ChangeTierOpNewTier from .checkout_response import CheckoutResponse @@ -120,6 +121,7 @@ from .database_info_response import DatabaseInfoResponse from .database_storage_entry import DatabaseStorageEntry from .delete_file_response import DeleteFileResponse +from .delete_graph_op import DeleteGraphOp from .delete_information_block_request import DeleteInformationBlockRequest from .delete_information_block_request_payload import ( DeleteInformationBlockRequestPayload, @@ -455,6 +457,7 @@ "BatchAgentResponse", "BillingCustomer", "CancelOperationResponseCanceloperation", + "CancelSubscriptionRequest", "ChangeTierOp", "ChangeTierOpNewTier", "CheckoutResponse", @@ -517,6 +520,7 @@ "DatabaseInfoResponse", "DatabaseStorageEntry", "DeleteFileResponse", + "DeleteGraphOp", "DeleteInformationBlockRequest", "DeleteInformationBlockRequestPayload", "DeleteJournalEntryRequest", diff --git a/robosystems_client/models/cancel_subscription_request.py b/robosystems_client/models/cancel_subscription_request.py new file mode 100644 index 0000000..76bf05e --- /dev/null +++ b/robosystems_client/models/cancel_subscription_request.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CancelSubscriptionRequest") + + +@_attrs_define +class CancelSubscriptionRequest: + """Request to cancel a subscription. + + Default behavior cancels at period end (soft cancel). Pass `immediate=True` + to terminate the subscription right away — this requires `confirm` to equal + the subscription's `resource_id` (e.g. the graph_id) as a guard against + accidental destructive calls. + + Attributes: + immediate (bool | Unset): If true, cancel immediately and trigger fast-path deprovisioning of the underlying + resource (within ~10 minutes). If false (default), cancel at the end of the current billing period. Default: + False. + confirm (None | str | Unset): Required when immediate=True. Must equal the subscription's resource_id (e.g. + graph_id) to confirm intent. + """ + + immediate: bool | Unset = False + confirm: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + immediate = self.immediate + + confirm: None | str | Unset + if isinstance(self.confirm, Unset): + confirm = UNSET + else: + confirm = self.confirm + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if immediate is not UNSET: + field_dict["immediate"] = immediate + if confirm is not UNSET: + field_dict["confirm"] = confirm + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + immediate = d.pop("immediate", UNSET) + + def _parse_confirm(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + confirm = _parse_confirm(d.pop("confirm", UNSET)) + + cancel_subscription_request = cls( + immediate=immediate, + confirm=confirm, + ) + + cancel_subscription_request.additional_properties = d + return cancel_subscription_request + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/robosystems_client/models/delete_graph_op.py b/robosystems_client/models/delete_graph_op.py new file mode 100644 index 0000000..7958d26 --- /dev/null +++ b/robosystems_client/models/delete_graph_op.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="DeleteGraphOp") + + +@_attrs_define +class DeleteGraphOp: + """Body for the delete-graph operation. + + Permanently destroys the graph: cancels its subscription immediately, then + triggers fast-path deprovisioning (LadybugDB database removed, DynamoDB slot + freed, PG records cleaned). Requires `confirm` to equal the URL `graph_id` + as a guard against accidental destructive calls. + + Attributes: + confirm (str): Must equal the graph_id in the URL — confirms the caller intends to destroy this specific graph. + """ + + confirm: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + confirm = self.confirm + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "confirm": confirm, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + confirm = d.pop("confirm") + + delete_graph_op = cls( + confirm=confirm, + ) + + delete_graph_op.additional_properties = d + return delete_graph_op + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties