diff --git a/DO_OPENAPI_COMMIT_SHA.txt b/DO_OPENAPI_COMMIT_SHA.txt index 6485b2a..79a2277 100644 --- a/DO_OPENAPI_COMMIT_SHA.txt +++ b/DO_OPENAPI_COMMIT_SHA.txt @@ -1 +1 @@ -c8bfa37 +578e1b5 diff --git a/src/pydo/_client.py b/src/pydo/_client.py index 944a17e..0fe2140 100644 --- a/src/pydo/_client.py +++ b/src/pydo/_client.py @@ -21,6 +21,7 @@ AutoscalepoolsOperations, BalanceOperations, BillingHistoryOperations, + BillingInsightsOperations, ByoipPrefixesOperations, CdnOperations, CertificatesOperations, @@ -592,6 +593,8 @@ class GeneratedClient: # pylint: disable=client-accepts-api-version-keyword,too :vartype billing_history: pydo.operations.BillingHistoryOperations :ivar invoices: InvoicesOperations operations :vartype invoices: pydo.operations.InvoicesOperations + :ivar billing_insights: BillingInsightsOperations operations + :vartype billing_insights: pydo.operations.BillingInsightsOperations :ivar databases: DatabasesOperations operations :vartype databases: pydo.operations.DatabasesOperations :ivar domains: DomainsOperations operations @@ -737,6 +740,9 @@ def __init__( self.invoices = InvoicesOperations( self._client, self._config, self._serialize, self._deserialize ) + self.billing_insights = BillingInsightsOperations( + self._client, self._config, self._serialize, self._deserialize + ) self.databases = DatabasesOperations( self._client, self._config, self._serialize, self._deserialize ) diff --git a/src/pydo/aio/_client.py b/src/pydo/aio/_client.py index 34ebfe7..6935483 100644 --- a/src/pydo/aio/_client.py +++ b/src/pydo/aio/_client.py @@ -21,6 +21,7 @@ AutoscalepoolsOperations, BalanceOperations, BillingHistoryOperations, + BillingInsightsOperations, ByoipPrefixesOperations, CdnOperations, CertificatesOperations, @@ -592,6 +593,8 @@ class GeneratedClient: # pylint: disable=client-accepts-api-version-keyword,too :vartype billing_history: pydo.aio.operations.BillingHistoryOperations :ivar invoices: InvoicesOperations operations :vartype invoices: pydo.aio.operations.InvoicesOperations + :ivar billing_insights: BillingInsightsOperations operations + :vartype billing_insights: pydo.aio.operations.BillingInsightsOperations :ivar databases: DatabasesOperations operations :vartype databases: pydo.aio.operations.DatabasesOperations :ivar domains: DomainsOperations operations @@ -737,6 +740,9 @@ def __init__( self.invoices = InvoicesOperations( self._client, self._config, self._serialize, self._deserialize ) + self.billing_insights = BillingInsightsOperations( + self._client, self._config, self._serialize, self._deserialize + ) self.databases = DatabasesOperations( self._client, self._config, self._serialize, self._deserialize ) diff --git a/src/pydo/aio/operations/__init__.py b/src/pydo/aio/operations/__init__.py index e8e7b8a..4a74b7c 100644 --- a/src/pydo/aio/operations/__init__.py +++ b/src/pydo/aio/operations/__init__.py @@ -15,6 +15,7 @@ from ._operations import BalanceOperations from ._operations import BillingHistoryOperations from ._operations import InvoicesOperations +from ._operations import BillingInsightsOperations from ._operations import DatabasesOperations from ._operations import DomainsOperations from ._operations import DropletsOperations @@ -67,6 +68,7 @@ "BalanceOperations", "BillingHistoryOperations", "InvoicesOperations", + "BillingInsightsOperations", "DatabasesOperations", "DomainsOperations", "DropletsOperations", diff --git a/src/pydo/aio/operations/_operations.py b/src/pydo/aio/operations/_operations.py index 23bf09a..c08b66b 100644 --- a/src/pydo/aio/operations/_operations.py +++ b/src/pydo/aio/operations/_operations.py @@ -90,6 +90,7 @@ build_autoscalepools_update_request, build_balance_get_request, build_billing_history_list_request, + build_billing_insights_list_request, build_byoip_prefixes_create_request, build_byoip_prefixes_delete_request, build_byoip_prefixes_get_request, @@ -104085,6 +104086,185 @@ async def get_summary_by_uuid(self, invoice_uuid: str, **kwargs: Any) -> JSON: return cast(JSON, deserialized) # type: ignore +class BillingInsightsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~pydo.aio.GeneratedClient`'s + :attr:`billing_insights` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client = input_args.pop(0) if input_args else kwargs.pop("client") + self._config = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize = ( + input_args.pop(0) if input_args else kwargs.pop("deserializer") + ) + + @distributed_trace_async + async def list( + self, + account_urn: str, + start_date: datetime.date, + end_date: datetime.date, + *, + per_page: int = 20, + page: int = 1, + **kwargs: Any + ) -> JSON: + # pylint: disable=line-too-long + """List Billing Insights. + + This endpoint returns day-over-day changes in billing resource usage based on nightly invoice + items, including total amount, region, SKU, and description for a specified date range. It is + important to note that the daily resource usage may not reflect month-end billing totals when + totaled for a given month as nightly invoice item estimates do not necessarily encompass all + invoicing factors for the entire month. + + :param account_urn: URN of the customer account, can be a team (do:team:uuid) or an + organization (do:teamgroup:uuid). Required. + :type account_urn: str + :param start_date: Start date for billing insights in YYYY-MM-DD format. Required. + :type start_date: ~datetime.date + :param end_date: End date for billing insights in YYYY-MM-DD format. Must be within 31 days of + start_date. Required. + :type end_date: ~datetime.date + :keyword per_page: Number of items returned per page. Default value is 20. + :paramtype per_page: int + :keyword page: Which 'page' of paginated results to return. Default value is 1. + :paramtype page: int + :return: JSON object + :rtype: JSON + :raises ~azure.core.exceptions.HttpResponseError: + + Example: + .. code-block:: python + + # response body for status code(s): 200 + response == { + "current_page": 0, # Current page number. Required. + "data_points": [ + { + "description": "str", # Optional. Description of the billed + resource or service as shown on an invoice item. + "group_description": "str", # Optional. Optional invoice + item group name of the billed resource or service, blank when not part an + invoice item group. + "region": "str", # Optional. Region where the usage + occurred. + "sku": "str", # Optional. Unique SKU identifier for the + billed resource. + "start_date": "2020-02-20", # Optional. Start date of the + billing data point in YYYY-MM-DD format. + "total_amount": "str", # Optional. Total amount for this + data point in USD. + "usage_team_urn": "str" # Optional. URN of the team that + incurred the usage. + } + ], + "total_items": 0, # Total number of items available across all pages. + Required. + "total_pages": 0 # Total number of pages available. Required. + } + # response body for status code(s): 404 + response == { + "id": "str", # A short identifier corresponding to the HTTP status code + returned. For example, the ID for a response returning a 404 status code would + be "not_found.". Required. + "message": "str", # A message providing additional information about the + error, including details to help resolve it when possible. Required. + "request_id": "str" # Optional. Optionally, some endpoints may include a + request ID that should be provided when reporting bugs or opening support + tickets to help identify the issue. + } + """ + error_map: MutableMapping[int, Type[HttpResponseError]] = { + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + 401: cast( + Type[HttpResponseError], + lambda response: ClientAuthenticationError(response=response), + ), + 429: HttpResponseError, + 500: HttpResponseError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[JSON] = kwargs.pop("cls", None) + + _request = build_billing_insights_list_request( + account_urn=account_urn, + start_date=start_date, + end_date=end_date, + per_page=per_page, + page=page, + headers=_headers, + params=_params, + ) + _request.url = self._client.format_url(_request.url) + + _stream = False + pipeline_response: PipelineResponse = ( + await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + ) + + response = pipeline_response.http_response + + if response.status_code not in [200, 404]: + if _stream: + await response.read() # Load the body in memory and close the socket + map_error(status_code=response.status_code, response=response, error_map=error_map) # type: ignore + raise HttpResponseError(response=response) + + response_headers = {} + if response.status_code == 200: + response_headers["ratelimit-limit"] = self._deserialize( + "int", response.headers.get("ratelimit-limit") + ) + response_headers["ratelimit-remaining"] = self._deserialize( + "int", response.headers.get("ratelimit-remaining") + ) + response_headers["ratelimit-reset"] = self._deserialize( + "int", response.headers.get("ratelimit-reset") + ) + + if response.content: + deserialized = response.json() + else: + deserialized = None + + if response.status_code == 404: + response_headers["ratelimit-limit"] = self._deserialize( + "int", response.headers.get("ratelimit-limit") + ) + response_headers["ratelimit-remaining"] = self._deserialize( + "int", response.headers.get("ratelimit-remaining") + ) + response_headers["ratelimit-reset"] = self._deserialize( + "int", response.headers.get("ratelimit-reset") + ) + + if response.content: + deserialized = response.json() + else: + deserialized = None + + if cls: + return cls(pipeline_response, cast(JSON, deserialized), response_headers) # type: ignore + + return cast(JSON, deserialized) # type: ignore + + class DatabasesOperations: # pylint: disable=too-many-public-methods """ .. warning:: diff --git a/src/pydo/operations/__init__.py b/src/pydo/operations/__init__.py index e8e7b8a..4a74b7c 100644 --- a/src/pydo/operations/__init__.py +++ b/src/pydo/operations/__init__.py @@ -15,6 +15,7 @@ from ._operations import BalanceOperations from ._operations import BillingHistoryOperations from ._operations import InvoicesOperations +from ._operations import BillingInsightsOperations from ._operations import DatabasesOperations from ._operations import DomainsOperations from ._operations import DropletsOperations @@ -67,6 +68,7 @@ "BalanceOperations", "BillingHistoryOperations", "InvoicesOperations", + "BillingInsightsOperations", "DatabasesOperations", "DomainsOperations", "DropletsOperations", diff --git a/src/pydo/operations/_operations.py b/src/pydo/operations/_operations.py index 24f88d9..50c7270 100644 --- a/src/pydo/operations/_operations.py +++ b/src/pydo/operations/_operations.py @@ -1743,6 +1743,46 @@ def build_invoices_get_summary_by_uuid_request( # pylint: disable=name-too-long return HttpRequest(method="GET", url=_url, headers=_headers, **kwargs) +def build_billing_insights_list_request( + account_urn: str, + start_date: datetime.date, + end_date: datetime.date, + *, + per_page: int = 20, + page: int = 1, + **kwargs: Any, +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/v2/billing/{account_urn}/insights/{start_date}/{end_date}" + path_format_arguments = { + "account_urn": _SERIALIZER.url("account_urn", account_urn, "str"), + "start_date": _SERIALIZER.url("start_date", start_date, "date"), + "end_date": _SERIALIZER.url("end_date", end_date, "date"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + if per_page is not None: + _params["per_page"] = _SERIALIZER.query( + "per_page", per_page, "int", maximum=200, minimum=1 + ) + if page is not None: + _params["page"] = _SERIALIZER.query("page", page, "int", minimum=1) + + # Construct headers + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest( + method="GET", url=_url, params=_params, headers=_headers, **kwargs + ) + + def build_databases_list_options_request(**kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) @@ -116859,6 +116899,185 @@ def get_summary_by_uuid(self, invoice_uuid: str, **kwargs: Any) -> JSON: return cast(JSON, deserialized) # type: ignore +class BillingInsightsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~pydo.GeneratedClient`'s + :attr:`billing_insights` attribute. + """ + + def __init__(self, *args, **kwargs): + input_args = list(args) + self._client = input_args.pop(0) if input_args else kwargs.pop("client") + self._config = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize = ( + input_args.pop(0) if input_args else kwargs.pop("deserializer") + ) + + @distributed_trace + def list( + self, + account_urn: str, + start_date: datetime.date, + end_date: datetime.date, + *, + per_page: int = 20, + page: int = 1, + **kwargs: Any, + ) -> JSON: + # pylint: disable=line-too-long + """List Billing Insights. + + This endpoint returns day-over-day changes in billing resource usage based on nightly invoice + items, including total amount, region, SKU, and description for a specified date range. It is + important to note that the daily resource usage may not reflect month-end billing totals when + totaled for a given month as nightly invoice item estimates do not necessarily encompass all + invoicing factors for the entire month. + + :param account_urn: URN of the customer account, can be a team (do:team:uuid) or an + organization (do:teamgroup:uuid). Required. + :type account_urn: str + :param start_date: Start date for billing insights in YYYY-MM-DD format. Required. + :type start_date: ~datetime.date + :param end_date: End date for billing insights in YYYY-MM-DD format. Must be within 31 days of + start_date. Required. + :type end_date: ~datetime.date + :keyword per_page: Number of items returned per page. Default value is 20. + :paramtype per_page: int + :keyword page: Which 'page' of paginated results to return. Default value is 1. + :paramtype page: int + :return: JSON object + :rtype: JSON + :raises ~azure.core.exceptions.HttpResponseError: + + Example: + .. code-block:: python + + # response body for status code(s): 200 + response == { + "current_page": 0, # Current page number. Required. + "data_points": [ + { + "description": "str", # Optional. Description of the billed + resource or service as shown on an invoice item. + "group_description": "str", # Optional. Optional invoice + item group name of the billed resource or service, blank when not part an + invoice item group. + "region": "str", # Optional. Region where the usage + occurred. + "sku": "str", # Optional. Unique SKU identifier for the + billed resource. + "start_date": "2020-02-20", # Optional. Start date of the + billing data point in YYYY-MM-DD format. + "total_amount": "str", # Optional. Total amount for this + data point in USD. + "usage_team_urn": "str" # Optional. URN of the team that + incurred the usage. + } + ], + "total_items": 0, # Total number of items available across all pages. + Required. + "total_pages": 0 # Total number of pages available. Required. + } + # response body for status code(s): 404 + response == { + "id": "str", # A short identifier corresponding to the HTTP status code + returned. For example, the ID for a response returning a 404 status code would + be "not_found.". Required. + "message": "str", # A message providing additional information about the + error, including details to help resolve it when possible. Required. + "request_id": "str" # Optional. Optionally, some endpoints may include a + request ID that should be provided when reporting bugs or opening support + tickets to help identify the issue. + } + """ + error_map: MutableMapping[int, Type[HttpResponseError]] = { + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + 401: cast( + Type[HttpResponseError], + lambda response: ClientAuthenticationError(response=response), + ), + 429: HttpResponseError, + 500: HttpResponseError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[JSON] = kwargs.pop("cls", None) + + _request = build_billing_insights_list_request( + account_urn=account_urn, + start_date=start_date, + end_date=end_date, + per_page=per_page, + page=page, + headers=_headers, + params=_params, + ) + _request.url = self._client.format_url(_request.url) + + _stream = False + pipeline_response: PipelineResponse = ( + self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + ) + + response = pipeline_response.http_response + + if response.status_code not in [200, 404]: + if _stream: + response.read() # Load the body in memory and close the socket + map_error(status_code=response.status_code, response=response, error_map=error_map) # type: ignore + raise HttpResponseError(response=response) + + response_headers = {} + if response.status_code == 200: + response_headers["ratelimit-limit"] = self._deserialize( + "int", response.headers.get("ratelimit-limit") + ) + response_headers["ratelimit-remaining"] = self._deserialize( + "int", response.headers.get("ratelimit-remaining") + ) + response_headers["ratelimit-reset"] = self._deserialize( + "int", response.headers.get("ratelimit-reset") + ) + + if response.content: + deserialized = response.json() + else: + deserialized = None + + if response.status_code == 404: + response_headers["ratelimit-limit"] = self._deserialize( + "int", response.headers.get("ratelimit-limit") + ) + response_headers["ratelimit-remaining"] = self._deserialize( + "int", response.headers.get("ratelimit-remaining") + ) + response_headers["ratelimit-reset"] = self._deserialize( + "int", response.headers.get("ratelimit-reset") + ) + + if response.content: + deserialized = response.json() + else: + deserialized = None + + if cls: + return cls(pipeline_response, cast(JSON, deserialized), response_headers) # type: ignore + + return cast(JSON, deserialized) # type: ignore + + class DatabasesOperations: # pylint: disable=too-many-public-methods """ .. warning::