diff --git a/api.md b/api.md index 3e58d8f..082696a 100644 --- a/api.md +++ b/api.md @@ -10,7 +10,7 @@ A list method returns its first page, such as a `AddOnsListPage`: a subclass of model (`AddOnListResponse`) with the paging members `items`, `has_next_page()`, `get_next_page()`, `iter_pages()` and `body`, which iterates every item. The page classes are in `meteroid.api`. -[Add ons](#add-ons) · [Add ons entitlements](#add-ons-entitlements) · [Batch jobs](#batch-jobs) · [Budgets](#budgets) · [Checkout sessions](#checkout-sessions) · [Checks](#checks) · [Connect](#connect) · [Coupons](#coupons) · [Credit notes](#credit-notes) · [Credit units](#credit-units) · [Credit units prices](#credit-units-prices) · [Credits](#credits) · [Credits grants](#credits-grants) · [Credits wallets](#credits-wallets) · [Custom properties](#custom-properties) · [Customers](#customers) · [Entitlements](#entitlements) · [Events](#events) · [Features](#features) · [Invoices](#invoices) · [Metrics](#metrics) · [Oauth](#oauth) · [Oauth apps](#oauth-apps) · [Plans](#plans) · [Plans versions](#plans-versions) · [Product families](#product-families) · [Products](#products) · [Products entitlements](#products-entitlements) · [Subscriptions](#subscriptions) · [Usage](#usage) · [Usage customer](#usage-customer) · [Usage costs](#usage-costs) · [Usage costs customer](#usage-costs-customer) · [Usage costs subscription](#usage-costs-subscription) · [Webhook endpoints](#webhook-endpoints) · [Webhook endpoints endpoints](#webhook-endpoints-endpoints) +[Add ons](#add-ons) · [Add ons entitlements](#add-ons-entitlements) · [Batch jobs](#batch-jobs) · [Budgets](#budgets) · [Checkout sessions](#checkout-sessions) · [Checks](#checks) · [Connect](#connect) · [Coupons](#coupons) · [Credit notes](#credit-notes) · [Credit units](#credit-units) · [Credit units prices](#credit-units-prices) · [Credits](#credits) · [Credits grants](#credits-grants) · [Credits wallets](#credits-wallets) · [Custom properties](#custom-properties) · [Customers](#customers) · [Entitlements](#entitlements) · [Events](#events) · [Features](#features) · [Invoices](#invoices) · [Licenses](#licenses) · [Metrics](#metrics) · [Oauth](#oauth) · [Oauth apps](#oauth-apps) · [Plans](#plans) · [Plans versions](#plans-versions) · [Product families](#product-families) · [Products](#products) · [Products entitlements](#products-entitlements) · [Subscriptions](#subscriptions) · [Usage](#usage) · [Usage customer](#usage-customer) · [Usage costs](#usage-costs) · [Usage costs customer](#usage-costs-customer) · [Usage costs subscription](#usage-costs-subscription) · [Webhook endpoints](#webhook-endpoints) · [Webhook endpoints endpoints](#webhook-endpoints-endpoints) ## Add ons @@ -234,6 +234,20 @@ model (`AddOnListResponse`) with the paging members `items`, `has_next_page()`, | `client.invoices.refresh(invoice_id: str) -> Invoice` | `POST /api/v1/invoices/{invoice_id}/refresh` | [`Invoice`](meteroid/models/invoice.py) | | `client.invoices.download_xml(invoice_id: str) -> bytes` | `GET /api/v1/invoices/{invoice_id}/xml` | bytes | +## Licenses + +[`client.licenses`](meteroid/api/licenses.py) + +| Method | Request | Returns | +| --- | --- | --- | +| `client.licenses.list(*, customer_id=..., subscription_id=..., include_revoked=...) -> LicenseListResponse` | `GET /api/v1/licenses` | [`LicenseListResponse`](meteroid/models/license_list_response.py) | +| `client.licenses.create(*, customer_id: str, instance_id=..., offline=..., subscription_id=..., validity_days=...) -> IssueLicenseResponse` | `POST /api/v1/licenses` | [`IssueLicenseResponse`](meteroid/models/issue_license_response.py) | +| `client.licenses.retrieve(license_id: str) -> License` | `GET /api/v1/licenses/{license_id}` | [`License`](meteroid/models/license.py) | +| `client.licenses.retrieve_activation_code(license_id: str) -> LicenseActivationCode` | `GET /api/v1/licenses/{license_id}/activation-code` | [`LicenseActivationCode`](meteroid/models/license_activation_code.py) | +| `client.licenses.offline(license_id: str, *, valid_days: int, instance_id=...) -> OfflineLicense` | `POST /api/v1/licenses/{license_id}/offline` | [`OfflineLicense`](meteroid/models/offline_license.py) | +| `client.licenses.revoke(license_id: str, *, immediately=...) -> License` | `POST /api/v1/licenses/{license_id}/revoke` | [`License`](meteroid/models/license.py) | +| `client.licenses.list_licensing_signing_keys() -> LicenseSigningKeysResponse` | `GET /api/v1/licensing/signing-keys` | [`LicenseSigningKeysResponse`](meteroid/models/license_signing_keys_response.py) | + ## Metrics [`client.metrics`](meteroid/api/metrics.py) diff --git a/meteroid/api/__init__.py b/meteroid/api/__init__.py index 5efa1f4..6dc1d4c 100644 --- a/meteroid/api/__init__.py +++ b/meteroid/api/__init__.py @@ -98,6 +98,7 @@ from .events import AsyncEvents, Events from .features import AsyncFeatures, Features from .invoices import AsyncInvoices, Invoices + from .licenses import AsyncLicenses, Licenses from .metrics import AsyncMetrics, Metrics from .oauth import AsyncOauth, Oauth from .oauth_apps import AsyncOauthApps, OauthApps @@ -163,6 +164,8 @@ "Features": "features", "AsyncInvoices": "invoices", "Invoices": "invoices", + "AsyncLicenses": "licenses", + "Licenses": "licenses", "AsyncMetrics": "metrics", "Metrics": "metrics", "AsyncOauth": "oauth", @@ -317,6 +320,8 @@ def __dir__() -> list[str]: "Features", "AsyncInvoices", "Invoices", + "AsyncLicenses", + "Licenses", "AsyncMetrics", "Metrics", "AsyncOauth", diff --git a/meteroid/api/client.py b/meteroid/api/client.py index 48fcaf1..a5ef38b 100644 --- a/meteroid/api/client.py +++ b/meteroid/api/client.py @@ -113,6 +113,12 @@ Invoices, InvoicesWithRawResponse, ) + from .licenses import ( + AsyncLicenses, + AsyncLicensesWithRawResponse, + Licenses, + LicensesWithRawResponse, + ) from .metrics import ( AsyncMetrics, AsyncMetricsWithRawResponse, @@ -432,6 +438,13 @@ def invoices(self) -> Invoices: return Invoices(self._cfg, self._httpx_client) + @functools.cached_property + def licenses(self) -> Licenses: + """The licenses API.""" + from .licenses import Licenses + + return Licenses(self._cfg, self._httpx_client) + @functools.cached_property def metrics(self) -> Metrics: """The metrics API.""" @@ -621,6 +634,13 @@ def invoices(self) -> InvoicesWithRawResponse: return InvoicesWithRawResponse(self._client.invoices) + @property + def licenses(self) -> LicensesWithRawResponse: + """The licenses API.""" + from .licenses import LicensesWithRawResponse + + return LicensesWithRawResponse(self._client.licenses) + @property def metrics(self) -> MetricsWithRawResponse: """The metrics API.""" @@ -910,6 +930,13 @@ def invoices(self) -> AsyncInvoices: return AsyncInvoices(self._cfg, self._httpx_client) + @functools.cached_property + def licenses(self) -> AsyncLicenses: + """The licenses API.""" + from .licenses import AsyncLicenses + + return AsyncLicenses(self._cfg, self._httpx_client) + @functools.cached_property def metrics(self) -> AsyncMetrics: """The metrics API.""" @@ -1099,6 +1126,13 @@ def invoices(self) -> AsyncInvoicesWithRawResponse: return AsyncInvoicesWithRawResponse(self._client.invoices) + @property + def licenses(self) -> AsyncLicensesWithRawResponse: + """The licenses API.""" + from .licenses import AsyncLicensesWithRawResponse + + return AsyncLicensesWithRawResponse(self._client.licenses) + @property def metrics(self) -> AsyncMetricsWithRawResponse: """The metrics API.""" diff --git a/meteroid/api/licenses.py b/meteroid/api/licenses.py new file mode 100644 index 0000000..fa41445 --- /dev/null +++ b/meteroid/api/licenses.py @@ -0,0 +1,609 @@ +# this file is @generated +"""Licenses API.""" + +from __future__ import annotations + +import typing as t + +from .. import models as _models +from ..models import ( + IssueLicenseRequest, + IssueLicenseResponse, + IssueOfflineLicenseRequest, + License, + LicenseActivationCode, + LicenseListResponse, + LicenseSigningKeysResponse, + OfflineLicense, + RevokeLicenseRequest, +) +from ..serialization import UNSET, Unset, to_json_value +from ._response import async_to_raw_response_wrapper, to_raw_response_wrapper +from .common import ( + ApiBaseAsync, + ApiBaseSync, + ApiRequest, + Timeout, + decode_response, + serialize_query_params, +) + + +class AsyncLicenses(ApiBaseAsync): + """Licenses API, for asyncio.""" + + @property + def with_raw_response(self) -> AsyncLicensesWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return AsyncLicensesWithRawResponse(self) + + async def list( + self, + *, + customer_id: str | None = None, + subscription_id: str | None = None, + include_revoked: bool | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> LicenseListResponse: + """List licenses + + :param customer_id: Filter by customer ID or alias + :param include_revoked: Include revoked licenses. Default: false.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/licenses", + query_params=serialize_query_params( + { + "customer_id": customer_id, + "subscription_id": subscription_id, + "include_revoked": include_revoked, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, LicenseListResponse) + + async def create( + self, + *, + customer_id: str, + instance_id: str | None | Unset = UNSET, + offline: bool | None = None, + subscription_id: str | None | Unset = UNSET, + validity_days: int | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> IssueLicenseResponse: + """Issue a license + + Issues a license to a customer, optionally tied to one of its subscriptions. Online, the + license is pending until their installation activates it with the returned one-time code. + Offline, it is returned signed at once, for an installation that can't reach the server. + + :param customer_id: Customer ID or alias + :param instance_id: Offline only. The installation's id as it reports it, up to 255 characters without spaces: the license names it (`aud`) and stays bound to it. Without one, the license is not tied to an installation. + :param offline: Delivers the license at once as a signed offline license, for an installation that can't reach the license server: no activation code, no refresh. Default: false. + :param subscription_id: Ties the license to this subscription of the customer: it is renewed while the subscription is paid, and carries its plan's entitlements. + :param validity_days: Online: lifetime of each license token the installation refreshes, 1 to 31 days. Default: 7. Offline: lifetime of the signed license, 1 to 400 days. Default: 365.""" + response = await self._request( + ApiRequest( + method="post", + path="/api/v1/licenses", + json_body=to_json_value( + IssueLicenseRequest( + customer_id=customer_id, + instance_id=instance_id, + offline=offline, + subscription_id=subscription_id, + validity_days=validity_days, + ), + IssueLicenseRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, IssueLicenseResponse) + + async def retrieve( + self, + license_id: str, + *, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> License: + """Get a license""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/licenses/{license_id}", + path_params={ + "license_id": license_id, + }, + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, License) + + async def retrieve_activation_code( + self, + license_id: str, + *, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> LicenseActivationCode: + """Get a license's activation code + + Available until the license is activated or revoked.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/licenses/{license_id}/activation-code", + path_params={ + "license_id": license_id, + }, + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, LicenseActivationCode) + + async def offline( + self, + license_id: str, + *, + valid_days: int, + instance_id: str | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> OfflineLicense: + """Issue an offline license + + Signs a long-lived license for an air-gapped installation from an existing license, e.g. to + renew an offline license before it expires. Given an instance id, binds the license to it. + + :param instance_id: The installation's id as it reports it, up to 255 characters without spaces: the license names it (`aud`) and stays bound to it. Without one, the license is not tied to one. + :param valid_days: Lifetime of the offline license, 1 to 400 days.""" + response = await self._request( + ApiRequest( + method="post", + path="/api/v1/licenses/{license_id}/offline", + path_params={ + "license_id": license_id, + }, + json_body=to_json_value( + IssueOfflineLicenseRequest( + instance_id=instance_id, + valid_days=valid_days, + ), + IssueOfflineLicenseRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, OfflineLicense) + + async def revoke( + self, + license_id: str, + *, + immediately: bool | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> License: + """Revoke a license + + :param immediately: Stop at once: the installation drops its license on its next refresh. Otherwise the license keeps working until it expires, and is not renewed.""" + response = await self._request( + ApiRequest( + method="post", + path="/api/v1/licenses/{license_id}/revoke", + path_params={ + "license_id": license_id, + }, + json_body=to_json_value( + RevokeLicenseRequest( + immediately=immediately, + ), + RevokeLicenseRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, License) + + async def list_licensing_signing_keys( + self, + *, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> LicenseSigningKeysResponse: + """Get the licensing signing keys + + The keys licenses are signed with, and the public endpoints installations talk to.""" + response = await self._request( + ApiRequest( + method="get", + path="/api/v1/licensing/signing-keys", + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, LicenseSigningKeysResponse) + + +class AsyncLicensesWithRawResponse: + """The methods of :class:`AsyncLicenses`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: AsyncLicenses) -> None: + self.list = async_to_raw_response_wrapper(resource.list) + self.create = async_to_raw_response_wrapper(resource.create) + self.retrieve = async_to_raw_response_wrapper(resource.retrieve) + self.retrieve_activation_code = async_to_raw_response_wrapper( + resource.retrieve_activation_code + ) + self.offline = async_to_raw_response_wrapper(resource.offline) + self.revoke = async_to_raw_response_wrapper(resource.revoke) + self.list_licensing_signing_keys = async_to_raw_response_wrapper( + resource.list_licensing_signing_keys + ) + + +class Licenses(ApiBaseSync): + """Licenses API.""" + + @property + def with_raw_response(self) -> LicensesWithRawResponse: + """These methods, returning an :class:`APIResponse` with the status and headers.""" + return LicensesWithRawResponse(self) + + def list( + self, + *, + customer_id: str | None = None, + subscription_id: str | None = None, + include_revoked: bool | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> LicenseListResponse: + """List licenses + + :param customer_id: Filter by customer ID or alias + :param include_revoked: Include revoked licenses. Default: false.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/licenses", + query_params=serialize_query_params( + { + "customer_id": customer_id, + "subscription_id": subscription_id, + "include_revoked": include_revoked, + }, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, LicenseListResponse) + + def create( + self, + *, + customer_id: str, + instance_id: str | None | Unset = UNSET, + offline: bool | None = None, + subscription_id: str | None | Unset = UNSET, + validity_days: int | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> IssueLicenseResponse: + """Issue a license + + Issues a license to a customer, optionally tied to one of its subscriptions. Online, the + license is pending until their installation activates it with the returned one-time code. + Offline, it is returned signed at once, for an installation that can't reach the server. + + :param customer_id: Customer ID or alias + :param instance_id: Offline only. The installation's id as it reports it, up to 255 characters without spaces: the license names it (`aud`) and stays bound to it. Without one, the license is not tied to an installation. + :param offline: Delivers the license at once as a signed offline license, for an installation that can't reach the license server: no activation code, no refresh. Default: false. + :param subscription_id: Ties the license to this subscription of the customer: it is renewed while the subscription is paid, and carries its plan's entitlements. + :param validity_days: Online: lifetime of each license token the installation refreshes, 1 to 31 days. Default: 7. Offline: lifetime of the signed license, 1 to 400 days. Default: 365.""" + response = self._request( + ApiRequest( + method="post", + path="/api/v1/licenses", + json_body=to_json_value( + IssueLicenseRequest( + customer_id=customer_id, + instance_id=instance_id, + offline=offline, + subscription_id=subscription_id, + validity_days=validity_days, + ), + IssueLicenseRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, IssueLicenseResponse) + + def retrieve( + self, + license_id: str, + *, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> License: + """Get a license""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/licenses/{license_id}", + path_params={ + "license_id": license_id, + }, + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, License) + + def retrieve_activation_code( + self, + license_id: str, + *, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> LicenseActivationCode: + """Get a license's activation code + + Available until the license is activated or revoked.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/licenses/{license_id}/activation-code", + path_params={ + "license_id": license_id, + }, + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, LicenseActivationCode) + + def offline( + self, + license_id: str, + *, + valid_days: int, + instance_id: str | None | Unset = UNSET, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> OfflineLicense: + """Issue an offline license + + Signs a long-lived license for an air-gapped installation from an existing license, e.g. to + renew an offline license before it expires. Given an instance id, binds the license to it. + + :param instance_id: The installation's id as it reports it, up to 255 characters without spaces: the license names it (`aud`) and stays bound to it. Without one, the license is not tied to one. + :param valid_days: Lifetime of the offline license, 1 to 400 days.""" + response = self._request( + ApiRequest( + method="post", + path="/api/v1/licenses/{license_id}/offline", + path_params={ + "license_id": license_id, + }, + json_body=to_json_value( + IssueOfflineLicenseRequest( + instance_id=instance_id, + valid_days=valid_days, + ), + IssueOfflineLicenseRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, OfflineLicense) + + def revoke( + self, + license_id: str, + *, + immediately: bool | None = None, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> License: + """Revoke a license + + :param immediately: Stop at once: the installation drops its license on its next refresh. Otherwise the license keeps working until it expires, and is not renewed.""" + response = self._request( + ApiRequest( + method="post", + path="/api/v1/licenses/{license_id}/revoke", + path_params={ + "license_id": license_id, + }, + json_body=to_json_value( + RevokeLicenseRequest( + immediately=immediately, + ), + RevokeLicenseRequest, + ), + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, License) + + def list_licensing_signing_keys( + self, + *, + extra_headers: t.Mapping[str, str] | None = None, + extra_query: t.Mapping[str, object] | None = None, + extra_body: t.Mapping[str, object] | None = None, + timeout: Timeout | Unset = UNSET, + max_retries: int | None = None, + ) -> LicenseSigningKeysResponse: + """Get the licensing signing keys + + The keys licenses are signed with, and the public endpoints installations talk to.""" + response = self._request( + ApiRequest( + method="get", + path="/api/v1/licensing/signing-keys", + error_types={ + "default": _models.RestErrorResponse, + }, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + max_retries=max_retries, + ) + ) + return decode_response(response, LicenseSigningKeysResponse) + + +class LicensesWithRawResponse: + """The methods of :class:`Licenses`, returning an :class:`APIResponse`.""" + + def __init__(self, resource: Licenses) -> None: + self.list = to_raw_response_wrapper(resource.list) + self.create = to_raw_response_wrapper(resource.create) + self.retrieve = to_raw_response_wrapper(resource.retrieve) + self.retrieve_activation_code = to_raw_response_wrapper( + resource.retrieve_activation_code + ) + self.offline = to_raw_response_wrapper(resource.offline) + self.revoke = to_raw_response_wrapper(resource.revoke) + self.list_licensing_signing_keys = to_raw_response_wrapper( + resource.list_licensing_signing_keys + ) diff --git a/meteroid/models/__init__.py b/meteroid/models/__init__.py index bc0713a..5ee31ac 100644 --- a/meteroid/models/__init__.py +++ b/meteroid/models/__init__.py @@ -281,11 +281,26 @@ from .invoice_status import InvoiceStatus, InvoiceStatusLiteral from .invoice_type import InvoiceType, InvoiceTypeLiteral from .invoicing_entity_id import InvoicingEntityId + from .issue_license_request import IssueLicenseRequest + from .issue_license_response import IssueLicenseResponse + from .issue_offline_license_request import IssueOfflineLicenseRequest from .json_config_value import JsonConfigValue from .ledger_entry import LedgerEntry from .ledger_entry_id import LedgerEntryId from .ledger_entry_type import LedgerEntryType, LedgerEntryTypeLiteral from .ledger_list_response import LedgerListResponse + from .license import License + from .license_activation_code import LicenseActivationCode + from .license_id import LicenseId + from .license_list_response import LicenseListResponse + from .license_signing_key import LicenseSigningKey + from .license_signing_key_status import ( + LicenseSigningKeyStatus, + LicenseSigningKeyStatusLiteral, + ) + from .license_signing_keys_response import LicenseSigningKeysResponse + from .license_status import LicenseStatus, LicenseStatusLiteral + from .licensing_endpoints import LicensingEndpoints from .linked_segmentation_matrix import LinkedSegmentationMatrix from .list_checkout_sessions_response import ListCheckoutSessionsResponse from .list_customer_usage_costs_granularity import ( @@ -332,6 +347,7 @@ from .o_auth_apps_response import OAuthAppsResponse from .o_auth_error_code import OAuthErrorCode, OAuthErrorCodeLiteral from .o_auth_error_response import OAuthErrorResponse + from .offline_license import OfflineLicense from .onboarding_link_response import OnboardingLinkResponse from .onboarding_mode import OnboardingMode, OnboardingModeLiteral from .one_time_fee import OneTimeFee @@ -413,6 +429,7 @@ from .rest_error_response import RestErrorResponse from .reversal_kind import ReversalKind, ReversalKindLiteral from .revocation_request import RevocationRequest + from .revoke_license_request import RevokeLicenseRequest from .rotated_secret import RotatedSecret from .select_option import SelectOption from .shipping_address import ShippingAddress @@ -829,12 +846,26 @@ "InvoiceType": "invoice_type", "InvoiceTypeLiteral": "invoice_type", "InvoicingEntityId": "invoicing_entity_id", + "IssueLicenseRequest": "issue_license_request", + "IssueLicenseResponse": "issue_license_response", + "IssueOfflineLicenseRequest": "issue_offline_license_request", "JsonConfigValue": "json_config_value", "LedgerEntry": "ledger_entry", "LedgerEntryId": "ledger_entry_id", "LedgerEntryType": "ledger_entry_type", "LedgerEntryTypeLiteral": "ledger_entry_type", "LedgerListResponse": "ledger_list_response", + "License": "license", + "LicenseActivationCode": "license_activation_code", + "LicenseId": "license_id", + "LicenseListResponse": "license_list_response", + "LicenseSigningKey": "license_signing_key", + "LicenseSigningKeyStatus": "license_signing_key_status", + "LicenseSigningKeyStatusLiteral": "license_signing_key_status", + "LicenseSigningKeysResponse": "license_signing_keys_response", + "LicenseStatus": "license_status", + "LicenseStatusLiteral": "license_status", + "LicensingEndpoints": "licensing_endpoints", "LinkedSegmentationMatrix": "linked_segmentation_matrix", "ListCheckoutSessionsResponse": "list_checkout_sessions_response", "ListCustomerUsageCostsGranularity": "list_customer_usage_costs_granularity", @@ -876,6 +907,7 @@ "OAuthErrorCode": "o_auth_error_code", "OAuthErrorCodeLiteral": "o_auth_error_code", "OAuthErrorResponse": "o_auth_error_response", + "OfflineLicense": "offline_license", "OnboardingLinkResponse": "onboarding_link_response", "OnboardingMode": "onboarding_mode", "OnboardingModeLiteral": "onboarding_mode", @@ -963,6 +995,7 @@ "ReversalKind": "reversal_kind", "ReversalKindLiteral": "reversal_kind", "RevocationRequest": "revocation_request", + "RevokeLicenseRequest": "revoke_license_request", "RotatedSecret": "rotated_secret", "SelectOption": "select_option", "ShippingAddress": "shipping_address", @@ -1385,12 +1418,26 @@ def __dir__() -> list[str]: "InvoiceType", "InvoiceTypeLiteral", "InvoicingEntityId", + "IssueLicenseRequest", + "IssueLicenseResponse", + "IssueOfflineLicenseRequest", "JsonConfigValue", "LedgerEntry", "LedgerEntryId", "LedgerEntryType", "LedgerEntryTypeLiteral", "LedgerListResponse", + "License", + "LicenseActivationCode", + "LicenseId", + "LicenseListResponse", + "LicenseSigningKey", + "LicenseSigningKeyStatus", + "LicenseSigningKeyStatusLiteral", + "LicenseSigningKeysResponse", + "LicenseStatus", + "LicenseStatusLiteral", + "LicensingEndpoints", "LinkedSegmentationMatrix", "ListCheckoutSessionsResponse", "ListCustomerUsageCostsGranularity", @@ -1432,6 +1479,7 @@ def __dir__() -> list[str]: "OAuthErrorCode", "OAuthErrorCodeLiteral", "OAuthErrorResponse", + "OfflineLicense", "OnboardingLinkResponse", "OnboardingMode", "OnboardingModeLiteral", @@ -1519,6 +1567,7 @@ def __dir__() -> list[str]: "ReversalKind", "ReversalKindLiteral", "RevocationRequest", + "RevokeLicenseRequest", "RotatedSecret", "SelectOption", "ShippingAddress", diff --git a/meteroid/models/issue_license_request.py b/meteroid/models/issue_license_request.py new file mode 100644 index 0000000..65974b9 --- /dev/null +++ b/meteroid/models/issue_license_request.py @@ -0,0 +1,31 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses + +from ..serialization import UNSET, BaseModel, Unset + + +@dataclasses.dataclass(kw_only=True) +class IssueLicenseRequest(BaseModel): + """The `IssueLicenseRequest` object.""" + + customer_id: str + """Customer ID or alias""" + + instance_id: str | None | Unset = UNSET + """Offline only. The installation's id as it reports it, up to 255 characters without + spaces: the license names it (`aud`) and stays bound to it. Without one, the license is + not tied to an installation.""" + + offline: bool | None = None + """Delivers the license at once as a signed offline license, for an installation that + can't reach the license server: no activation code, no refresh. Default: false.""" + + subscription_id: str | None | Unset = UNSET + """Ties the license to this subscription of the customer: it is renewed while the + subscription is paid, and carries its plan's entitlements.""" + + validity_days: int | None | Unset = UNSET + """Online: lifetime of each license token the installation refreshes, 1 to 31 days. + Default: 7. Offline: lifetime of the signed license, 1 to 400 days. Default: 365.""" diff --git a/meteroid/models/issue_license_response.py b/meteroid/models/issue_license_response.py new file mode 100644 index 0000000..ac3032f --- /dev/null +++ b/meteroid/models/issue_license_response.py @@ -0,0 +1,23 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .license import License + + +@dataclasses.dataclass(kw_only=True) +class IssueLicenseResponse(BaseModel): + """The `IssueLicenseResponse` object.""" + + license: License + + activation_code: str | None = None + """Online: one-time code the customer enters in their installation to activate the license.""" + + offline_license: str | None = None + """Offline: the signed license (a JWT) to install in the installation.""" diff --git a/meteroid/models/issue_offline_license_request.py b/meteroid/models/issue_offline_license_request.py new file mode 100644 index 0000000..3a55630 --- /dev/null +++ b/meteroid/models/issue_offline_license_request.py @@ -0,0 +1,18 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses + +from ..serialization import UNSET, BaseModel, Unset + + +@dataclasses.dataclass(kw_only=True) +class IssueOfflineLicenseRequest(BaseModel): + """The `IssueOfflineLicenseRequest` object.""" + + valid_days: int + """Lifetime of the offline license, 1 to 400 days.""" + + instance_id: str | None | Unset = UNSET + """The installation's id as it reports it, up to 255 characters without spaces: the license + names it (`aud`) and stays bound to it. Without one, the license is not tied to one.""" diff --git a/meteroid/models/license.py b/meteroid/models/license.py new file mode 100644 index 0000000..ea82ef5 --- /dev/null +++ b/meteroid/models/license.py @@ -0,0 +1,57 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .customer_id import CustomerId + from .license_id import LicenseId + from .license_status import LicenseStatus + from .subscription_id import SubscriptionId + + +@dataclasses.dataclass(kw_only=True) +class License(BaseModel): + """The `License` object.""" + + auto_issued: bool + """Issued automatically for a subscription to a plan that issues licenses.""" + + created_at: datetime + + customer_id: CustomerId + + customer_name: str + + has_activation_code: bool + """Whether the activation code can still be retrieved: only until activation or revocation.""" + + id: LicenseId + + offline: bool + """Issued as a signed offline license: never activated nor refreshed.""" + + revoked_immediately: bool + """Revoked to stop at once: the installation drops its license on its next refresh instead + of keeping it until it expires.""" + + status: LicenseStatus + + validity_days: int + """Lifetime of each license token the installation receives on refresh; for an offline + license, the lifetime it was issued with.""" + + instance_id: str | None = None + """The installation the license is bound to, once activated.""" + + last_refreshed_at: datetime | None = None + + plan_name: str | None = None + + revoked_at: datetime | None = None + + subscription_id: SubscriptionId | None = None diff --git a/meteroid/models/license_activation_code.py b/meteroid/models/license_activation_code.py new file mode 100644 index 0000000..51cbc4b --- /dev/null +++ b/meteroid/models/license_activation_code.py @@ -0,0 +1,13 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class LicenseActivationCode(BaseModel): + """The `LicenseActivationCode` object.""" + + activation_code: str diff --git a/meteroid/models/license_id.py b/meteroid/models/license_id.py new file mode 100644 index 0000000..ad55917 --- /dev/null +++ b/meteroid/models/license_id.py @@ -0,0 +1,4 @@ +# this file is @generated +import typing as t + +LicenseId: t.TypeAlias = str diff --git a/meteroid/models/license_list_response.py b/meteroid/models/license_list_response.py new file mode 100644 index 0000000..20691f0 --- /dev/null +++ b/meteroid/models/license_list_response.py @@ -0,0 +1,17 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .license import License + + +@dataclasses.dataclass(kw_only=True) +class LicenseListResponse(BaseModel): + """The `LicenseListResponse` object.""" + + data: list[License] diff --git a/meteroid/models/license_signing_key.py b/meteroid/models/license_signing_key.py new file mode 100644 index 0000000..c085ced --- /dev/null +++ b/meteroid/models/license_signing_key.py @@ -0,0 +1,28 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t +from datetime import datetime + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .license_signing_key_status import LicenseSigningKeyStatus + + +@dataclasses.dataclass(kw_only=True) +class LicenseSigningKey(BaseModel): + """The `LicenseSigningKey` object.""" + + created_at: datetime + + kid: str + """The key id, the `kid` header of the licenses it signs.""" + + public_key_pem: str + """Ed25519 public key, PEM-encoded.""" + + status: LicenseSigningKeyStatus + + retired_at: datetime | None = None diff --git a/meteroid/models/license_signing_key_status.py b/meteroid/models/license_signing_key_status.py new file mode 100644 index 0000000..10d5e5d --- /dev/null +++ b/meteroid/models/license_signing_key_status.py @@ -0,0 +1,15 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class LicenseSigningKeyStatus(StrEnum): + """The values of `LicenseSigningKeyStatus`; others are kept as received.""" + + ACTIVE = "ACTIVE" + RETIRED = "RETIRED" + + +LicenseSigningKeyStatusLiteral: t.TypeAlias = t.Literal["ACTIVE", "RETIRED"] +"""The values of :class:`LicenseSigningKeyStatus`, which arguments take as plain strings too.""" diff --git a/meteroid/models/license_signing_keys_response.py b/meteroid/models/license_signing_keys_response.py new file mode 100644 index 0000000..bbba3ca --- /dev/null +++ b/meteroid/models/license_signing_keys_response.py @@ -0,0 +1,21 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses +import typing as t + +from ..serialization import BaseModel + +if t.TYPE_CHECKING: + from .license_signing_key import LicenseSigningKey + from .licensing_endpoints import LicensingEndpoints + + +@dataclasses.dataclass(kw_only=True) +class LicenseSigningKeysResponse(BaseModel): + """The `LicenseSigningKeysResponse` object.""" + + endpoints: LicensingEndpoints + + keys: list[LicenseSigningKey] + """Active key first. The first key is created on first issue.""" diff --git a/meteroid/models/license_status.py b/meteroid/models/license_status.py new file mode 100644 index 0000000..b1ba18c --- /dev/null +++ b/meteroid/models/license_status.py @@ -0,0 +1,16 @@ +# this file is @generated +import typing as t + +from ..serialization import StrEnum + + +class LicenseStatus(StrEnum): + """The values of `LicenseStatus`; others are kept as received.""" + + PENDING = "PENDING" + ACTIVE = "ACTIVE" + REVOKED = "REVOKED" + + +LicenseStatusLiteral: t.TypeAlias = t.Literal["PENDING", "ACTIVE", "REVOKED"] +"""The values of :class:`LicenseStatus`, which arguments take as plain strings too.""" diff --git a/meteroid/models/licensing_endpoints.py b/meteroid/models/licensing_endpoints.py new file mode 100644 index 0000000..3a6c544 --- /dev/null +++ b/meteroid/models/licensing_endpoints.py @@ -0,0 +1,19 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class LicensingEndpoints(BaseModel): + """The public endpoints your installations talk to.""" + + activate_url: str + + events_url: str + + jwks_url: str + + refresh_url: str diff --git a/meteroid/models/offline_license.py b/meteroid/models/offline_license.py new file mode 100644 index 0000000..e38cdb7 --- /dev/null +++ b/meteroid/models/offline_license.py @@ -0,0 +1,14 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class OfflineLicense(BaseModel): + """The `OfflineLicense` object.""" + + license: str + """The signed license (a JWT) to install in the air-gapped installation.""" diff --git a/meteroid/models/plan.py b/meteroid/models/plan.py index cd59852..cd7d5a5 100644 --- a/meteroid/models/plan.py +++ b/meteroid/models/plan.py @@ -64,6 +64,9 @@ class Plan(BaseModel): entitlements: list[Entitlement] | None = None + issues_license: bool | None = None + """Subscriptions to this plan are issued a software license automatically.""" + minimum_commitment: MinimumCommitment | None = None period_start_day: int | None = None diff --git a/meteroid/models/revoke_license_request.py b/meteroid/models/revoke_license_request.py new file mode 100644 index 0000000..5543a71 --- /dev/null +++ b/meteroid/models/revoke_license_request.py @@ -0,0 +1,15 @@ +# this file is @generated +from __future__ import annotations + +import dataclasses + +from ..serialization import BaseModel + + +@dataclasses.dataclass(kw_only=True) +class RevokeLicenseRequest(BaseModel): + """The `RevokeLicenseRequest` object.""" + + immediately: bool | None = None + """Stop at once: the installation drops its license on its next refresh. Otherwise the + license keeps working until it expires, and is not renewed.""" diff --git a/tests/test_licenses.py b/tests/test_licenses.py new file mode 100644 index 0000000..17b8b22 --- /dev/null +++ b/tests/test_licenses.py @@ -0,0 +1,79 @@ +# this file is @generated +# ruff: noqa: I001 (where the package sorts depends on whether it is generated yet) +import unittest + +from meteroid.models import ( + IssueLicenseRequest, + IssueOfflineLicenseRequest, + RevokeLicenseRequest, +) + +from perseid_mock import call, decode, mock + + +class LicensesTest(unittest.TestCase): + def test_list(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"data":[{"auto_issued":false,"created_at":"2023-12-31T23:59:59.999-05:30","customer_id":"customer_id_2","customer_name":"sample","has_activation_code":false,"id":"license_id_0","offline":false,"revoked_immediately":false,"status":"REVOKED","validity_days":-123456789}]}', + ) + call(client.licenses.list) + self.assertEqual(requests, ["GET /api/v1/licenses"]) + + def test_create(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"license":{"auto_issued":false,"created_at":"2023-12-31T23:59:59.999-05:30","customer_id":"customer_id_37","customer_name":"sample","has_activation_code":true,"id":"license_id_40","offline":true,"revoked_immediately":true,"status":"PENDING","validity_days":123456789}}', + ) + call( + client.licenses.create, + body=decode(IssueLicenseRequest, '{"customer_id":"sample"}'), + ) + self.assertEqual(requests, ["POST /api/v1/licenses"]) + + def test_retrieve(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"auto_issued":false,"created_at":"2024-03-15T10:30:45.123+02:00","customer_id":"customer_id_79","customer_name":"sample","has_activation_code":false,"id":"license_id_90","offline":false,"revoked_immediately":true,"status":"PENDING","validity_days":-123456789}', + ) + call(client.licenses.retrieve, "license_id") + self.assertEqual(requests, ["GET /api/v1/licenses/license_id"]) + + def test_retrieve_activation_code(self) -> None: + client, requests = mock(200, "application/json", '{"activation_code":"sample"}') + call(client.licenses.retrieve_activation_code, "license_id") + self.assertEqual(requests, ["GET /api/v1/licenses/license_id/activation-code"]) + + def test_offline(self) -> None: + client, requests = mock(200, "application/json", '{"license":"sample"}') + call( + client.licenses.offline, + "license_id", + body=decode(IssueOfflineLicenseRequest, '{"valid_days":123456789}'), + ) + self.assertEqual(requests, ["POST /api/v1/licenses/license_id/offline"]) + + def test_revoke(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"auto_issued":false,"created_at":"2024-03-15T10:30:45.123+02:00","customer_id":"customer_id_79","customer_name":"sample","has_activation_code":false,"id":"license_id_90","offline":false,"revoked_immediately":true,"status":"PENDING","validity_days":-123456789}', + ) + call( + client.licenses.revoke, + "license_id", + body=decode(RevokeLicenseRequest, "{}"), + ) + self.assertEqual(requests, ["POST /api/v1/licenses/license_id/revoke"]) + + def test_list_licensing_signing_keys(self) -> None: + client, requests = mock( + 200, + "application/json", + '{"endpoints":{"activate_url":"sample","events_url":"sample","jwks_url":"sample","refresh_url":"sample"},"keys":[{"created_at":"2023-12-31T23:59:59.999-05:30","kid":"sample","public_key_pem":"sample","status":"ACTIVE"}]}', + ) + call(client.licenses.list_licensing_signing_keys) + self.assertEqual(requests, ["GET /api/v1/licensing/signing-keys"]) diff --git a/tests/test_plans.py b/tests/test_plans.py index b873302..6c4a733 100644 --- a/tests/test_plans.py +++ b/tests/test_plans.py @@ -46,7 +46,7 @@ def test_list(self) -> None: client, requests = mock( 200, "application/json", - '{"data":[{"available_parameters":{},"created_at":"2024-03-15T10:30:45.123+02:00","currency":"TMT","id":"plan_id_25","name":"sample","net_terms":-2147483648,"plan_type":"CUSTOM","price_components":[{"id":"price_component_id_8","name":"sample"}],"product_family":{"id":"product_family_id_2","name":"sample"},"status":"DRAFT","tax_inclusive":false,"version":123456789,"version_id":"plan_version_id_3"}],"pagination_meta":{"page":-123456789,"per_page":-123456789,"total_items":-9007199254740993,"total_pages":123456789}}', + '{"data":[{"available_parameters":{},"created_at":"2024-03-15T10:30:45.123+02:00","currency":"TMT","id":"plan_id_25","name":"sample","net_terms":-2147483648,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_2","name":"sample"}],"product_family":{"id":"product_family_id_19","name":"sample"},"status":"ARCHIVED","tax_inclusive":false,"version":-2147483648,"version_id":"plan_version_id_67"}],"pagination_meta":{"page":-123456789,"per_page":-123456789,"total_items":-9007199254740993,"total_pages":123456789}}', ) call(client.plans.list) self.assertEqual(requests, ["GET /api/v1/plans"]) @@ -55,7 +55,7 @@ def test_create(self) -> None: client, requests = mock( 200, "application/json", - '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":-123456789,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_88","name":"sample"}],"product_family":{"id":"product_family_id_15","name":"sample"},"status":"ACTIVE","tax_inclusive":true,"version":123456789,"version_id":"plan_version_id_79"}', + '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":2147483647,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_40","name":"sample"}],"product_family":{"id":"product_family_id_5","name":"sample"},"status":"INACTIVE","tax_inclusive":true,"version":-2147483648,"version_id":"plan_version_id_81"}', ) call( client.plans.create, @@ -70,7 +70,7 @@ def test_retrieve(self) -> None: client, requests = mock( 200, "application/json", - '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":-123456789,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_88","name":"sample"}],"product_family":{"id":"product_family_id_15","name":"sample"},"status":"ACTIVE","tax_inclusive":true,"version":123456789,"version_id":"plan_version_id_79"}', + '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":2147483647,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_40","name":"sample"}],"product_family":{"id":"product_family_id_5","name":"sample"},"status":"INACTIVE","tax_inclusive":true,"version":-2147483648,"version_id":"plan_version_id_81"}', ) call(client.plans.retrieve, "plan_id") self.assertEqual(requests, ["GET /api/v1/plans/plan_id"]) @@ -79,7 +79,7 @@ def test_replace(self) -> None: client, requests = mock( 200, "application/json", - '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":-123456789,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_88","name":"sample"}],"product_family":{"id":"product_family_id_15","name":"sample"},"status":"ACTIVE","tax_inclusive":true,"version":123456789,"version_id":"plan_version_id_79"}', + '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":2147483647,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_40","name":"sample"}],"product_family":{"id":"product_family_id_5","name":"sample"},"status":"INACTIVE","tax_inclusive":true,"version":-2147483648,"version_id":"plan_version_id_81"}', ) call( client.plans.replace, @@ -95,7 +95,7 @@ def test_update(self) -> None: client, requests = mock( 200, "application/json", - '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":-123456789,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_88","name":"sample"}],"product_family":{"id":"product_family_id_15","name":"sample"},"status":"ACTIVE","tax_inclusive":true,"version":123456789,"version_id":"plan_version_id_79"}', + '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":2147483647,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_40","name":"sample"}],"product_family":{"id":"product_family_id_5","name":"sample"},"status":"INACTIVE","tax_inclusive":true,"version":-2147483648,"version_id":"plan_version_id_81"}', ) call(client.plans.update, "plan_id", body=decode(PatchPlanRequest, "{}")) self.assertEqual(requests, ["PATCH /api/v1/plans/plan_id"]) @@ -109,7 +109,7 @@ def test_publish(self) -> None: client, requests = mock( 200, "application/json", - '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":-123456789,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_88","name":"sample"}],"product_family":{"id":"product_family_id_15","name":"sample"},"status":"ACTIVE","tax_inclusive":true,"version":123456789,"version_id":"plan_version_id_79"}', + '{"available_parameters":{},"created_at":"2023-12-31T23:59:59.999-05:30","currency":"MMK","id":"plan_id_40","name":"sample","net_terms":2147483647,"plan_type":"STANDARD","price_components":[{"id":"price_component_id_40","name":"sample"}],"product_family":{"id":"product_family_id_5","name":"sample"},"status":"INACTIVE","tax_inclusive":true,"version":-2147483648,"version_id":"plan_version_id_81"}', ) call(client.plans.publish, "plan_id") self.assertEqual(requests, ["POST /api/v1/plans/plan_id/publish"])