From e95eb1d93a4990d3fe2d722abf05b4bfa558c745 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 23:13:35 +0000 Subject: [PATCH 01/21] chore(internal): version bump (#469) From 441731c3d5efe8a38ec0fc5243327bfa928fe651 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 01:17:42 +0000 Subject: [PATCH 02/21] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index fcff3ba6..3147fc5b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 97 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-02151f7654870aee7820ee1c04659a469e6b67ac4977116334512c6b6e6a2016.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-d0a97f690d07742fea0d6c9b9b0d9dae5e8490eb03a7f3da27989fdf53d3e45d.yml From d23ec0e08cc5e8bd681a8c7947778a8445b81f78 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 23:40:28 +0000 Subject: [PATCH 03/21] feat(api): api update (#471) --- .stats.yml | 2 +- .../resources/customers/credits/credits.py | 24 +++++++++++++++---- .../credit_list_by_external_id_params.py | 5 +++- src/orb/types/customers/credit_list_params.py | 5 +++- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3147fc5b..09794c42 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 97 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-d0a97f690d07742fea0d6c9b9b0d9dae5e8490eb03a7f3da27989fdf53d3e45d.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-77f4e8cf0fc3b3f18c894408f322af7988ae90606235fe5058442409142a33e1.yml diff --git a/src/orb/resources/customers/credits/credits.py b/src/orb/resources/customers/credits/credits.py index 2fd36621..7b27df42 100644 --- a/src/orb/resources/customers/credits/credits.py +++ b/src/orb/resources/customers/credits/credits.py @@ -83,6 +83,9 @@ def list( """ Returns a paginated list of unexpired, non-zero credit blocks for a customer. + If `include_all_blocks` is set to `true`, all credit blocks (including expired + and depleted blocks) will be included in the response. + Note that `currency` defaults to credits if not specified. To use a real world currency, set `currency` to an ISO 4217 string. @@ -92,7 +95,8 @@ def list( cursor: Cursor for pagination. This can be populated by the `next_cursor` value returned from the initial request. - include_all_blocks: Include all blocks, not just active ones. + include_all_blocks: If set to True, all expired and depleted blocks, as well as active block will be + returned. limit: The number of items to fetch. Defaults to 20. @@ -145,6 +149,9 @@ def list_by_external_id( """ Returns a paginated list of unexpired, non-zero credit blocks for a customer. + If `include_all_blocks` is set to `true`, all credit blocks (including expired + and depleted blocks) will be included in the response. + Note that `currency` defaults to credits if not specified. To use a real world currency, set `currency` to an ISO 4217 string. @@ -154,7 +161,8 @@ def list_by_external_id( cursor: Cursor for pagination. This can be populated by the `next_cursor` value returned from the initial request. - include_all_blocks: Include all blocks, not just active ones. + include_all_blocks: If set to True, all expired and depleted blocks, as well as active block will be + returned. limit: The number of items to fetch. Defaults to 20. @@ -238,6 +246,9 @@ def list( """ Returns a paginated list of unexpired, non-zero credit blocks for a customer. + If `include_all_blocks` is set to `true`, all credit blocks (including expired + and depleted blocks) will be included in the response. + Note that `currency` defaults to credits if not specified. To use a real world currency, set `currency` to an ISO 4217 string. @@ -247,7 +258,8 @@ def list( cursor: Cursor for pagination. This can be populated by the `next_cursor` value returned from the initial request. - include_all_blocks: Include all blocks, not just active ones. + include_all_blocks: If set to True, all expired and depleted blocks, as well as active block will be + returned. limit: The number of items to fetch. Defaults to 20. @@ -300,6 +312,9 @@ def list_by_external_id( """ Returns a paginated list of unexpired, non-zero credit blocks for a customer. + If `include_all_blocks` is set to `true`, all credit blocks (including expired + and depleted blocks) will be included in the response. + Note that `currency` defaults to credits if not specified. To use a real world currency, set `currency` to an ISO 4217 string. @@ -309,7 +324,8 @@ def list_by_external_id( cursor: Cursor for pagination. This can be populated by the `next_cursor` value returned from the initial request. - include_all_blocks: Include all blocks, not just active ones. + include_all_blocks: If set to True, all expired and depleted blocks, as well as active block will be + returned. limit: The number of items to fetch. Defaults to 20. diff --git a/src/orb/types/customers/credit_list_by_external_id_params.py b/src/orb/types/customers/credit_list_by_external_id_params.py index b192dc5c..4074c82e 100644 --- a/src/orb/types/customers/credit_list_by_external_id_params.py +++ b/src/orb/types/customers/credit_list_by_external_id_params.py @@ -20,7 +20,10 @@ class CreditListByExternalIDParams(TypedDict, total=False): """ include_all_blocks: bool - """Include all blocks, not just active ones.""" + """ + If set to True, all expired and depleted blocks, as well as active block will be + returned. + """ limit: int """The number of items to fetch. Defaults to 20.""" diff --git a/src/orb/types/customers/credit_list_params.py b/src/orb/types/customers/credit_list_params.py index 9e8d7ba7..cb3f767b 100644 --- a/src/orb/types/customers/credit_list_params.py +++ b/src/orb/types/customers/credit_list_params.py @@ -20,7 +20,10 @@ class CreditListParams(TypedDict, total=False): """ include_all_blocks: bool - """Include all blocks, not just active ones.""" + """ + If set to True, all expired and depleted blocks, as well as active block will be + returned. + """ limit: int """The number of items to fetch. Defaults to 20.""" From 0a2235016cfba90da5f68e950579d0720bd40045 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 10:11:21 +0000 Subject: [PATCH 04/21] feat(api): api update (#472) --- .stats.yml | 4 +- api.md | 20 + src/orb/_client.py | 17 + src/orb/resources/__init__.py | 14 + .../dimensional_price_groups/__init__.py | 33 ++ .../dimensional_price_groups.py | 463 ++++++++++++++++++ .../external_dimensional_price_group_id.py | 163 ++++++ src/orb/types/__init__.py | 6 + src/orb/types/dimensional_price_group.py | 35 ++ .../dimensional_price_group_create_params.py | 26 + .../dimensional_price_group_list_params.py | 20 + .../dimensional_price_groups/__init__.py | 5 + .../dimensional_price_groups.py | 15 + .../dimensional_price_groups/__init__.py | 1 + ...est_external_dimensional_price_group_id.py | 108 ++++ .../test_dimensional_price_groups.py | 265 ++++++++++ 16 files changed, 1193 insertions(+), 2 deletions(-) create mode 100644 src/orb/resources/dimensional_price_groups/__init__.py create mode 100644 src/orb/resources/dimensional_price_groups/dimensional_price_groups.py create mode 100644 src/orb/resources/dimensional_price_groups/external_dimensional_price_group_id.py create mode 100644 src/orb/types/dimensional_price_group.py create mode 100644 src/orb/types/dimensional_price_group_create_params.py create mode 100644 src/orb/types/dimensional_price_group_list_params.py create mode 100644 src/orb/types/dimensional_price_groups/__init__.py create mode 100644 src/orb/types/dimensional_price_groups/dimensional_price_groups.py create mode 100644 tests/api_resources/dimensional_price_groups/__init__.py create mode 100644 tests/api_resources/dimensional_price_groups/test_external_dimensional_price_group_id.py create mode 100644 tests/api_resources/test_dimensional_price_groups.py diff --git a/.stats.yml b/.stats.yml index 09794c42..c963fb86 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ -configured_endpoints: 97 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-77f4e8cf0fc3b3f18c894408f322af7988ae90606235fe5058442409142a33e1.yml +configured_endpoints: 101 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-726c25fdf0fdd4b7c5a9c36d30e33990d2a4b63c4260be340400f8ded23b578f.yml diff --git a/api.md b/api.md index f9a7248e..c6fdb8fa 100644 --- a/api.md +++ b/api.md @@ -387,3 +387,23 @@ Methods: - client.alerts.create_for_subscription(subscription_id, \*\*params) -> Alert - client.alerts.disable(alert_configuration_id, \*\*params) -> Alert - client.alerts.enable(alert_configuration_id, \*\*params) -> Alert + +# DimensionalPriceGroups + +Types: + +```python +from orb.types import DimensionalPriceGroup, DimensionalPriceGroups +``` + +Methods: + +- client.dimensional_price_groups.create(\*\*params) -> DimensionalPriceGroup +- client.dimensional_price_groups.retrieve(dimensional_price_group_id) -> DimensionalPriceGroup +- client.dimensional_price_groups.list(\*\*params) -> SyncPage[DimensionalPriceGroup] + +## ExternalDimensionalPriceGroupID + +Methods: + +- client.dimensional_price_groups.external_dimensional_price_group_id.retrieve(external_dimensional_price_group_id) -> DimensionalPriceGroup diff --git a/src/orb/_client.py b/src/orb/_client.py index 4a341ccc..2a1f1497 100644 --- a/src/orb/_client.py +++ b/src/orb/_client.py @@ -48,6 +48,7 @@ from .resources.prices import prices from .resources.coupons import coupons from .resources.customers import customers +from .resources.dimensional_price_groups import dimensional_price_groups __all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "Orb", "AsyncOrb", "Client", "AsyncClient"] @@ -67,6 +68,7 @@ class Orb(SyncAPIClient): subscriptions: subscriptions.Subscriptions webhooks: webhooks.Webhooks alerts: alerts.Alerts + dimensional_price_groups: dimensional_price_groups.DimensionalPriceGroups with_raw_response: OrbWithRawResponse with_streaming_response: OrbWithStreamedResponse @@ -148,6 +150,7 @@ def __init__( self.subscriptions = subscriptions.Subscriptions(self) self.webhooks = webhooks.Webhooks(self) self.alerts = alerts.Alerts(self) + self.dimensional_price_groups = dimensional_price_groups.DimensionalPriceGroups(self) self.with_raw_response = OrbWithRawResponse(self) self.with_streaming_response = OrbWithStreamedResponse(self) @@ -321,6 +324,7 @@ class AsyncOrb(AsyncAPIClient): subscriptions: subscriptions.AsyncSubscriptions webhooks: webhooks.AsyncWebhooks alerts: alerts.AsyncAlerts + dimensional_price_groups: dimensional_price_groups.AsyncDimensionalPriceGroups with_raw_response: AsyncOrbWithRawResponse with_streaming_response: AsyncOrbWithStreamedResponse @@ -402,6 +406,7 @@ def __init__( self.subscriptions = subscriptions.AsyncSubscriptions(self) self.webhooks = webhooks.AsyncWebhooks(self) self.alerts = alerts.AsyncAlerts(self) + self.dimensional_price_groups = dimensional_price_groups.AsyncDimensionalPriceGroups(self) self.with_raw_response = AsyncOrbWithRawResponse(self) self.with_streaming_response = AsyncOrbWithStreamedResponse(self) @@ -575,6 +580,9 @@ def __init__(self, client: Orb) -> None: self.prices = prices.PricesWithRawResponse(client.prices) self.subscriptions = subscriptions.SubscriptionsWithRawResponse(client.subscriptions) self.alerts = alerts.AlertsWithRawResponse(client.alerts) + self.dimensional_price_groups = dimensional_price_groups.DimensionalPriceGroupsWithRawResponse( + client.dimensional_price_groups + ) class AsyncOrbWithRawResponse: @@ -592,6 +600,9 @@ def __init__(self, client: AsyncOrb) -> None: self.prices = prices.AsyncPricesWithRawResponse(client.prices) self.subscriptions = subscriptions.AsyncSubscriptionsWithRawResponse(client.subscriptions) self.alerts = alerts.AsyncAlertsWithRawResponse(client.alerts) + self.dimensional_price_groups = dimensional_price_groups.AsyncDimensionalPriceGroupsWithRawResponse( + client.dimensional_price_groups + ) class OrbWithStreamedResponse: @@ -609,6 +620,9 @@ def __init__(self, client: Orb) -> None: self.prices = prices.PricesWithStreamingResponse(client.prices) self.subscriptions = subscriptions.SubscriptionsWithStreamingResponse(client.subscriptions) self.alerts = alerts.AlertsWithStreamingResponse(client.alerts) + self.dimensional_price_groups = dimensional_price_groups.DimensionalPriceGroupsWithStreamingResponse( + client.dimensional_price_groups + ) class AsyncOrbWithStreamedResponse: @@ -628,6 +642,9 @@ def __init__(self, client: AsyncOrb) -> None: self.prices = prices.AsyncPricesWithStreamingResponse(client.prices) self.subscriptions = subscriptions.AsyncSubscriptionsWithStreamingResponse(client.subscriptions) self.alerts = alerts.AsyncAlertsWithStreamingResponse(client.alerts) + self.dimensional_price_groups = dimensional_price_groups.AsyncDimensionalPriceGroupsWithStreamingResponse( + client.dimensional_price_groups + ) Client = Orb diff --git a/src/orb/resources/__init__.py b/src/orb/resources/__init__.py index f5515f6c..ccb3f489 100644 --- a/src/orb/resources/__init__.py +++ b/src/orb/resources/__init__.py @@ -108,6 +108,14 @@ InvoiceLineItemsWithStreamingResponse, AsyncInvoiceLineItemsWithStreamingResponse, ) +from .dimensional_price_groups import ( + DimensionalPriceGroups, + AsyncDimensionalPriceGroups, + DimensionalPriceGroupsWithRawResponse, + AsyncDimensionalPriceGroupsWithRawResponse, + DimensionalPriceGroupsWithStreamingResponse, + AsyncDimensionalPriceGroupsWithStreamingResponse, +) __all__ = [ "TopLevel", @@ -190,4 +198,10 @@ "AsyncAlertsWithRawResponse", "AlertsWithStreamingResponse", "AsyncAlertsWithStreamingResponse", + "DimensionalPriceGroups", + "AsyncDimensionalPriceGroups", + "DimensionalPriceGroupsWithRawResponse", + "AsyncDimensionalPriceGroupsWithRawResponse", + "DimensionalPriceGroupsWithStreamingResponse", + "AsyncDimensionalPriceGroupsWithStreamingResponse", ] diff --git a/src/orb/resources/dimensional_price_groups/__init__.py b/src/orb/resources/dimensional_price_groups/__init__.py new file mode 100644 index 00000000..d3533ab1 --- /dev/null +++ b/src/orb/resources/dimensional_price_groups/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .dimensional_price_groups import ( + DimensionalPriceGroups, + AsyncDimensionalPriceGroups, + DimensionalPriceGroupsWithRawResponse, + AsyncDimensionalPriceGroupsWithRawResponse, + DimensionalPriceGroupsWithStreamingResponse, + AsyncDimensionalPriceGroupsWithStreamingResponse, +) +from .external_dimensional_price_group_id import ( + ExternalDimensionalPriceGroupID, + AsyncExternalDimensionalPriceGroupID, + ExternalDimensionalPriceGroupIDWithRawResponse, + AsyncExternalDimensionalPriceGroupIDWithRawResponse, + ExternalDimensionalPriceGroupIDWithStreamingResponse, + AsyncExternalDimensionalPriceGroupIDWithStreamingResponse, +) + +__all__ = [ + "ExternalDimensionalPriceGroupID", + "AsyncExternalDimensionalPriceGroupID", + "ExternalDimensionalPriceGroupIDWithRawResponse", + "AsyncExternalDimensionalPriceGroupIDWithRawResponse", + "ExternalDimensionalPriceGroupIDWithStreamingResponse", + "AsyncExternalDimensionalPriceGroupIDWithStreamingResponse", + "DimensionalPriceGroups", + "AsyncDimensionalPriceGroups", + "DimensionalPriceGroupsWithRawResponse", + "AsyncDimensionalPriceGroupsWithRawResponse", + "DimensionalPriceGroupsWithStreamingResponse", + "AsyncDimensionalPriceGroupsWithStreamingResponse", +] diff --git a/src/orb/resources/dimensional_price_groups/dimensional_price_groups.py b/src/orb/resources/dimensional_price_groups/dimensional_price_groups.py new file mode 100644 index 00000000..8d220f77 --- /dev/null +++ b/src/orb/resources/dimensional_price_groups/dimensional_price_groups.py @@ -0,0 +1,463 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Optional + +import httpx + +from ... import _legacy_response +from ...types import dimensional_price_group_list_params, dimensional_price_group_create_params +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._utils import ( + maybe_transform, + async_maybe_transform, +) +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ...pagination import SyncPage, AsyncPage +from ..._base_client import AsyncPaginator, make_request_options +from ...types.dimensional_price_group import DimensionalPriceGroup +from .external_dimensional_price_group_id import ( + ExternalDimensionalPriceGroupID, + AsyncExternalDimensionalPriceGroupID, + ExternalDimensionalPriceGroupIDWithRawResponse, + AsyncExternalDimensionalPriceGroupIDWithRawResponse, + ExternalDimensionalPriceGroupIDWithStreamingResponse, + AsyncExternalDimensionalPriceGroupIDWithStreamingResponse, +) + +__all__ = ["DimensionalPriceGroups", "AsyncDimensionalPriceGroups"] + + +class DimensionalPriceGroups(SyncAPIResource): + @cached_property + def external_dimensional_price_group_id(self) -> ExternalDimensionalPriceGroupID: + return ExternalDimensionalPriceGroupID(self._client) + + @cached_property + def with_raw_response(self) -> DimensionalPriceGroupsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/orbcorp/orb-python#accessing-raw-response-data-eg-headers + """ + return DimensionalPriceGroupsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> DimensionalPriceGroupsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/orbcorp/orb-python#with_streaming_response + """ + return DimensionalPriceGroupsWithStreamingResponse(self) + + def create( + self, + *, + billable_metric_id: str, + dimensions: List[str], + name: str, + external_dimensional_price_group_id: Optional[str] | NotGiven = NOT_GIVEN, + metadata: Optional[Dict[str, Optional[str]]] | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + idempotency_key: str | None = None, + ) -> DimensionalPriceGroup: + """ + A dimensional price group is used to partition the result of a billable metric + by a set of dimensions. Prices in a price group must specify the parition used + to derive their usage. + + For example, suppose we have a billable metric that measures the number of + widgets used and we want to charge differently depending on the color of the + widget. We can create a price group with a dimension "color" and two prices: one + that charges $10 per red widget and one that charges $20 per blue widget. + + Args: + dimensions: The set of keys (in order) used to disambiguate prices in the group. + + metadata: User-specified key/value pairs for the resource. Individual keys can be removed + by setting the value to `null`, and the entire metadata mapping can be cleared + by setting `metadata` to `null`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + return self._post( + "/dimensional_price_groups", + body=maybe_transform( + { + "billable_metric_id": billable_metric_id, + "dimensions": dimensions, + "name": name, + "external_dimensional_price_group_id": external_dimensional_price_group_id, + "metadata": metadata, + }, + dimensional_price_group_create_params.DimensionalPriceGroupCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=DimensionalPriceGroup, + ) + + def retrieve( + self, + dimensional_price_group_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> DimensionalPriceGroup: + """ + Fetch dimensional price group + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not dimensional_price_group_id: + raise ValueError( + f"Expected a non-empty value for `dimensional_price_group_id` but received {dimensional_price_group_id!r}" + ) + return self._get( + f"/dimensional_price_groups/{dimensional_price_group_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DimensionalPriceGroup, + ) + + def list( + self, + *, + cursor: Optional[str] | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncPage[DimensionalPriceGroup]: + """List dimensional price groups + + Args: + cursor: Cursor for pagination. + + This can be populated by the `next_cursor` value returned + from the initial request. + + limit: The number of items to fetch. Defaults to 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/dimensional_price_groups", + page=SyncPage[DimensionalPriceGroup], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "cursor": cursor, + "limit": limit, + }, + dimensional_price_group_list_params.DimensionalPriceGroupListParams, + ), + ), + model=DimensionalPriceGroup, + ) + + +class AsyncDimensionalPriceGroups(AsyncAPIResource): + @cached_property + def external_dimensional_price_group_id(self) -> AsyncExternalDimensionalPriceGroupID: + return AsyncExternalDimensionalPriceGroupID(self._client) + + @cached_property + def with_raw_response(self) -> AsyncDimensionalPriceGroupsWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/orbcorp/orb-python#accessing-raw-response-data-eg-headers + """ + return AsyncDimensionalPriceGroupsWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncDimensionalPriceGroupsWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/orbcorp/orb-python#with_streaming_response + """ + return AsyncDimensionalPriceGroupsWithStreamingResponse(self) + + async def create( + self, + *, + billable_metric_id: str, + dimensions: List[str], + name: str, + external_dimensional_price_group_id: Optional[str] | NotGiven = NOT_GIVEN, + metadata: Optional[Dict[str, Optional[str]]] | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + idempotency_key: str | None = None, + ) -> DimensionalPriceGroup: + """ + A dimensional price group is used to partition the result of a billable metric + by a set of dimensions. Prices in a price group must specify the parition used + to derive their usage. + + For example, suppose we have a billable metric that measures the number of + widgets used and we want to charge differently depending on the color of the + widget. We can create a price group with a dimension "color" and two prices: one + that charges $10 per red widget and one that charges $20 per blue widget. + + Args: + dimensions: The set of keys (in order) used to disambiguate prices in the group. + + metadata: User-specified key/value pairs for the resource. Individual keys can be removed + by setting the value to `null`, and the entire metadata mapping can be cleared + by setting `metadata` to `null`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + return await self._post( + "/dimensional_price_groups", + body=await async_maybe_transform( + { + "billable_metric_id": billable_metric_id, + "dimensions": dimensions, + "name": name, + "external_dimensional_price_group_id": external_dimensional_price_group_id, + "metadata": metadata, + }, + dimensional_price_group_create_params.DimensionalPriceGroupCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=DimensionalPriceGroup, + ) + + async def retrieve( + self, + dimensional_price_group_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> DimensionalPriceGroup: + """ + Fetch dimensional price group + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not dimensional_price_group_id: + raise ValueError( + f"Expected a non-empty value for `dimensional_price_group_id` but received {dimensional_price_group_id!r}" + ) + return await self._get( + f"/dimensional_price_groups/{dimensional_price_group_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DimensionalPriceGroup, + ) + + def list( + self, + *, + cursor: Optional[str] | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[DimensionalPriceGroup, AsyncPage[DimensionalPriceGroup]]: + """List dimensional price groups + + Args: + cursor: Cursor for pagination. + + This can be populated by the `next_cursor` value returned + from the initial request. + + limit: The number of items to fetch. Defaults to 20. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/dimensional_price_groups", + page=AsyncPage[DimensionalPriceGroup], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "cursor": cursor, + "limit": limit, + }, + dimensional_price_group_list_params.DimensionalPriceGroupListParams, + ), + ), + model=DimensionalPriceGroup, + ) + + +class DimensionalPriceGroupsWithRawResponse: + def __init__(self, dimensional_price_groups: DimensionalPriceGroups) -> None: + self._dimensional_price_groups = dimensional_price_groups + + self.create = _legacy_response.to_raw_response_wrapper( + dimensional_price_groups.create, + ) + self.retrieve = _legacy_response.to_raw_response_wrapper( + dimensional_price_groups.retrieve, + ) + self.list = _legacy_response.to_raw_response_wrapper( + dimensional_price_groups.list, + ) + + @cached_property + def external_dimensional_price_group_id(self) -> ExternalDimensionalPriceGroupIDWithRawResponse: + return ExternalDimensionalPriceGroupIDWithRawResponse( + self._dimensional_price_groups.external_dimensional_price_group_id + ) + + +class AsyncDimensionalPriceGroupsWithRawResponse: + def __init__(self, dimensional_price_groups: AsyncDimensionalPriceGroups) -> None: + self._dimensional_price_groups = dimensional_price_groups + + self.create = _legacy_response.async_to_raw_response_wrapper( + dimensional_price_groups.create, + ) + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + dimensional_price_groups.retrieve, + ) + self.list = _legacy_response.async_to_raw_response_wrapper( + dimensional_price_groups.list, + ) + + @cached_property + def external_dimensional_price_group_id(self) -> AsyncExternalDimensionalPriceGroupIDWithRawResponse: + return AsyncExternalDimensionalPriceGroupIDWithRawResponse( + self._dimensional_price_groups.external_dimensional_price_group_id + ) + + +class DimensionalPriceGroupsWithStreamingResponse: + def __init__(self, dimensional_price_groups: DimensionalPriceGroups) -> None: + self._dimensional_price_groups = dimensional_price_groups + + self.create = to_streamed_response_wrapper( + dimensional_price_groups.create, + ) + self.retrieve = to_streamed_response_wrapper( + dimensional_price_groups.retrieve, + ) + self.list = to_streamed_response_wrapper( + dimensional_price_groups.list, + ) + + @cached_property + def external_dimensional_price_group_id(self) -> ExternalDimensionalPriceGroupIDWithStreamingResponse: + return ExternalDimensionalPriceGroupIDWithStreamingResponse( + self._dimensional_price_groups.external_dimensional_price_group_id + ) + + +class AsyncDimensionalPriceGroupsWithStreamingResponse: + def __init__(self, dimensional_price_groups: AsyncDimensionalPriceGroups) -> None: + self._dimensional_price_groups = dimensional_price_groups + + self.create = async_to_streamed_response_wrapper( + dimensional_price_groups.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + dimensional_price_groups.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + dimensional_price_groups.list, + ) + + @cached_property + def external_dimensional_price_group_id(self) -> AsyncExternalDimensionalPriceGroupIDWithStreamingResponse: + return AsyncExternalDimensionalPriceGroupIDWithStreamingResponse( + self._dimensional_price_groups.external_dimensional_price_group_id + ) diff --git a/src/orb/resources/dimensional_price_groups/external_dimensional_price_group_id.py b/src/orb/resources/dimensional_price_groups/external_dimensional_price_group_id.py new file mode 100644 index 00000000..f137f4d3 --- /dev/null +++ b/src/orb/resources/dimensional_price_groups/external_dimensional_price_group_id.py @@ -0,0 +1,163 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ... import _legacy_response +from ..._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import to_streamed_response_wrapper, async_to_streamed_response_wrapper +from ..._base_client import make_request_options +from ...types.dimensional_price_group import DimensionalPriceGroup + +__all__ = ["ExternalDimensionalPriceGroupID", "AsyncExternalDimensionalPriceGroupID"] + + +class ExternalDimensionalPriceGroupID(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ExternalDimensionalPriceGroupIDWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/orbcorp/orb-python#accessing-raw-response-data-eg-headers + """ + return ExternalDimensionalPriceGroupIDWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ExternalDimensionalPriceGroupIDWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/orbcorp/orb-python#with_streaming_response + """ + return ExternalDimensionalPriceGroupIDWithStreamingResponse(self) + + def retrieve( + self, + external_dimensional_price_group_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> DimensionalPriceGroup: + """ + Fetch dimensional price group by external ID + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_dimensional_price_group_id: + raise ValueError( + f"Expected a non-empty value for `external_dimensional_price_group_id` but received {external_dimensional_price_group_id!r}" + ) + return self._get( + f"/dimensional_price_groups/external_dimensional_price_group_id/{external_dimensional_price_group_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DimensionalPriceGroup, + ) + + +class AsyncExternalDimensionalPriceGroupID(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncExternalDimensionalPriceGroupIDWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return the + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/orbcorp/orb-python#accessing-raw-response-data-eg-headers + """ + return AsyncExternalDimensionalPriceGroupIDWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncExternalDimensionalPriceGroupIDWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/orbcorp/orb-python#with_streaming_response + """ + return AsyncExternalDimensionalPriceGroupIDWithStreamingResponse(self) + + async def retrieve( + self, + external_dimensional_price_group_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> DimensionalPriceGroup: + """ + Fetch dimensional price group by external ID + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not external_dimensional_price_group_id: + raise ValueError( + f"Expected a non-empty value for `external_dimensional_price_group_id` but received {external_dimensional_price_group_id!r}" + ) + return await self._get( + f"/dimensional_price_groups/external_dimensional_price_group_id/{external_dimensional_price_group_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DimensionalPriceGroup, + ) + + +class ExternalDimensionalPriceGroupIDWithRawResponse: + def __init__(self, external_dimensional_price_group_id: ExternalDimensionalPriceGroupID) -> None: + self._external_dimensional_price_group_id = external_dimensional_price_group_id + + self.retrieve = _legacy_response.to_raw_response_wrapper( + external_dimensional_price_group_id.retrieve, + ) + + +class AsyncExternalDimensionalPriceGroupIDWithRawResponse: + def __init__(self, external_dimensional_price_group_id: AsyncExternalDimensionalPriceGroupID) -> None: + self._external_dimensional_price_group_id = external_dimensional_price_group_id + + self.retrieve = _legacy_response.async_to_raw_response_wrapper( + external_dimensional_price_group_id.retrieve, + ) + + +class ExternalDimensionalPriceGroupIDWithStreamingResponse: + def __init__(self, external_dimensional_price_group_id: ExternalDimensionalPriceGroupID) -> None: + self._external_dimensional_price_group_id = external_dimensional_price_group_id + + self.retrieve = to_streamed_response_wrapper( + external_dimensional_price_group_id.retrieve, + ) + + +class AsyncExternalDimensionalPriceGroupIDWithStreamingResponse: + def __init__(self, external_dimensional_price_group_id: AsyncExternalDimensionalPriceGroupID) -> None: + self._external_dimensional_price_group_id = external_dimensional_price_group_id + + self.retrieve = async_to_streamed_response_wrapper( + external_dimensional_price_group_id.retrieve, + ) diff --git a/src/orb/types/__init__.py b/src/orb/types/__init__.py index 18c8b027..c7eb0152 100644 --- a/src/orb/types/__init__.py +++ b/src/orb/types/__init__.py @@ -57,8 +57,10 @@ from .customer_create_params import CustomerCreateParams as CustomerCreateParams from .customer_update_params import CustomerUpdateParams as CustomerUpdateParams from .credit_note_list_params import CreditNoteListParams as CreditNoteListParams +from .dimensional_price_group import DimensionalPriceGroup as DimensionalPriceGroup from .price_evaluate_response import PriceEvaluateResponse as PriceEvaluateResponse from .top_level_ping_response import TopLevelPingResponse as TopLevelPingResponse +from .dimensional_price_groups import DimensionalPriceGroups as DimensionalPriceGroups from .event_deprecate_response import EventDeprecateResponse as EventDeprecateResponse from .invoice_mark_paid_params import InvoiceMarkPaidParams as InvoiceMarkPaidParams from .subscription_list_params import SubscriptionListParams as SubscriptionListParams @@ -80,11 +82,15 @@ from .subscription_trigger_phase_params import SubscriptionTriggerPhaseParams as SubscriptionTriggerPhaseParams from .subscription_fetch_schedule_params import SubscriptionFetchScheduleParams as SubscriptionFetchScheduleParams from .subscription_update_trial_response import SubscriptionUpdateTrialResponse as SubscriptionUpdateTrialResponse +from .dimensional_price_group_list_params import DimensionalPriceGroupListParams as DimensionalPriceGroupListParams from .subscription_price_intervals_params import SubscriptionPriceIntervalsParams as SubscriptionPriceIntervalsParams from .subscription_trigger_phase_response import SubscriptionTriggerPhaseResponse as SubscriptionTriggerPhaseResponse from .alert_create_for_subscription_params import AlertCreateForSubscriptionParams as AlertCreateForSubscriptionParams from .subscription_fetch_schedule_response import SubscriptionFetchScheduleResponse as SubscriptionFetchScheduleResponse from .customer_update_by_external_id_params import CustomerUpdateByExternalIDParams as CustomerUpdateByExternalIDParams +from .dimensional_price_group_create_params import ( + DimensionalPriceGroupCreateParams as DimensionalPriceGroupCreateParams, +) from .subscription_price_intervals_response import ( SubscriptionPriceIntervalsResponse as SubscriptionPriceIntervalsResponse, ) diff --git a/src/orb/types/dimensional_price_group.py b/src/orb/types/dimensional_price_group.py new file mode 100644 index 00000000..da103128 --- /dev/null +++ b/src/orb/types/dimensional_price_group.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from .._models import BaseModel + +__all__ = ["DimensionalPriceGroup"] + + +class DimensionalPriceGroup(BaseModel): + id: str + + billable_metric_id: str + """The billable metric associated with this dimensional price group. + + All prices associated with this dimensional price group will be computed using + this billable metric. + """ + + dimensions: List[str] + """The dimensions that this dimensional price group is defined over""" + + external_dimensional_price_group_id: Optional[str] = None + """An alias for the dimensional price group""" + + metadata: Dict[str, str] + """User specified key-value pairs for the resource. + + If not present, this defaults to an empty dictionary. Individual keys can be + removed by setting the value to `null`, and the entire metadata mapping can be + cleared by setting `metadata` to `null`. + """ + + name: str + """The name of the dimensional price group""" diff --git a/src/orb/types/dimensional_price_group_create_params.py b/src/orb/types/dimensional_price_group_create_params.py new file mode 100644 index 00000000..36c07bf1 --- /dev/null +++ b/src/orb/types/dimensional_price_group_create_params.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Optional +from typing_extensions import Required, TypedDict + +__all__ = ["DimensionalPriceGroupCreateParams"] + + +class DimensionalPriceGroupCreateParams(TypedDict, total=False): + billable_metric_id: Required[str] + + dimensions: Required[List[str]] + """The set of keys (in order) used to disambiguate prices in the group.""" + + name: Required[str] + + external_dimensional_price_group_id: Optional[str] + + metadata: Optional[Dict[str, Optional[str]]] + """User-specified key/value pairs for the resource. + + Individual keys can be removed by setting the value to `null`, and the entire + metadata mapping can be cleared by setting `metadata` to `null`. + """ diff --git a/src/orb/types/dimensional_price_group_list_params.py b/src/orb/types/dimensional_price_group_list_params.py new file mode 100644 index 00000000..f385535e --- /dev/null +++ b/src/orb/types/dimensional_price_group_list_params.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["DimensionalPriceGroupListParams"] + + +class DimensionalPriceGroupListParams(TypedDict, total=False): + cursor: Optional[str] + """Cursor for pagination. + + This can be populated by the `next_cursor` value returned from the initial + request. + """ + + limit: int + """The number of items to fetch. Defaults to 20.""" diff --git a/src/orb/types/dimensional_price_groups/__init__.py b/src/orb/types/dimensional_price_groups/__init__.py new file mode 100644 index 00000000..6237c9ac --- /dev/null +++ b/src/orb/types/dimensional_price_groups/__init__.py @@ -0,0 +1,5 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .dimensional_price_groups import DimensionalPriceGroups as DimensionalPriceGroups diff --git a/src/orb/types/dimensional_price_groups/dimensional_price_groups.py b/src/orb/types/dimensional_price_groups/dimensional_price_groups.py new file mode 100644 index 00000000..78893603 --- /dev/null +++ b/src/orb/types/dimensional_price_groups/dimensional_price_groups.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from ..._models import BaseModel +from ..dimensional_price_group import DimensionalPriceGroup +from ..shared.pagination_metadata import PaginationMetadata + +__all__ = ["DimensionalPriceGroups"] + + +class DimensionalPriceGroups(BaseModel): + data: List[DimensionalPriceGroup] + + pagination_metadata: PaginationMetadata diff --git a/tests/api_resources/dimensional_price_groups/__init__.py b/tests/api_resources/dimensional_price_groups/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/dimensional_price_groups/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/dimensional_price_groups/test_external_dimensional_price_group_id.py b/tests/api_resources/dimensional_price_groups/test_external_dimensional_price_group_id.py new file mode 100644 index 00000000..d4fd8c5a --- /dev/null +++ b/tests/api_resources/dimensional_price_groups/test_external_dimensional_price_group_id.py @@ -0,0 +1,108 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from orb import Orb, AsyncOrb +from orb.types import DimensionalPriceGroup +from tests.utils import assert_matches_type + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestExternalDimensionalPriceGroupID: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_retrieve(self, client: Orb) -> None: + external_dimensional_price_group_id = ( + client.dimensional_price_groups.external_dimensional_price_group_id.retrieve( + "external_dimensional_price_group_id", + ) + ) + assert_matches_type(DimensionalPriceGroup, external_dimensional_price_group_id, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Orb) -> None: + response = client.dimensional_price_groups.external_dimensional_price_group_id.with_raw_response.retrieve( + "external_dimensional_price_group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_dimensional_price_group_id = response.parse() + assert_matches_type(DimensionalPriceGroup, external_dimensional_price_group_id, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Orb) -> None: + with client.dimensional_price_groups.external_dimensional_price_group_id.with_streaming_response.retrieve( + "external_dimensional_price_group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_dimensional_price_group_id = response.parse() + assert_matches_type(DimensionalPriceGroup, external_dimensional_price_group_id, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Orb) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_dimensional_price_group_id` but received ''" + ): + client.dimensional_price_groups.external_dimensional_price_group_id.with_raw_response.retrieve( + "", + ) + + +class TestAsyncExternalDimensionalPriceGroupID: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOrb) -> None: + external_dimensional_price_group_id = ( + await async_client.dimensional_price_groups.external_dimensional_price_group_id.retrieve( + "external_dimensional_price_group_id", + ) + ) + assert_matches_type(DimensionalPriceGroup, external_dimensional_price_group_id, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOrb) -> None: + response = ( + await async_client.dimensional_price_groups.external_dimensional_price_group_id.with_raw_response.retrieve( + "external_dimensional_price_group_id", + ) + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + external_dimensional_price_group_id = response.parse() + assert_matches_type(DimensionalPriceGroup, external_dimensional_price_group_id, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOrb) -> None: + async with async_client.dimensional_price_groups.external_dimensional_price_group_id.with_streaming_response.retrieve( + "external_dimensional_price_group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + external_dimensional_price_group_id = await response.parse() + assert_matches_type(DimensionalPriceGroup, external_dimensional_price_group_id, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOrb) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `external_dimensional_price_group_id` but received ''" + ): + await async_client.dimensional_price_groups.external_dimensional_price_group_id.with_raw_response.retrieve( + "", + ) diff --git a/tests/api_resources/test_dimensional_price_groups.py b/tests/api_resources/test_dimensional_price_groups.py new file mode 100644 index 00000000..d371b7c5 --- /dev/null +++ b/tests/api_resources/test_dimensional_price_groups.py @@ -0,0 +1,265 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from orb import Orb, AsyncOrb +from orb.types import DimensionalPriceGroup +from tests.utils import assert_matches_type +from orb.pagination import SyncPage, AsyncPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestDimensionalPriceGroups: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Orb) -> None: + dimensional_price_group = client.dimensional_price_groups.create( + billable_metric_id="billable_metric_id", + dimensions=["region", "instance_type"], + name="name", + ) + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Orb) -> None: + dimensional_price_group = client.dimensional_price_groups.create( + billable_metric_id="billable_metric_id", + dimensions=["region", "instance_type"], + name="name", + external_dimensional_price_group_id="external_dimensional_price_group_id", + metadata={"foo": "string"}, + ) + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Orb) -> None: + response = client.dimensional_price_groups.with_raw_response.create( + billable_metric_id="billable_metric_id", + dimensions=["region", "instance_type"], + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + dimensional_price_group = response.parse() + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Orb) -> None: + with client.dimensional_price_groups.with_streaming_response.create( + billable_metric_id="billable_metric_id", + dimensions=["region", "instance_type"], + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + dimensional_price_group = response.parse() + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_retrieve(self, client: Orb) -> None: + dimensional_price_group = client.dimensional_price_groups.retrieve( + "dimensional_price_group_id", + ) + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + @parametrize + def test_raw_response_retrieve(self, client: Orb) -> None: + response = client.dimensional_price_groups.with_raw_response.retrieve( + "dimensional_price_group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + dimensional_price_group = response.parse() + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + @parametrize + def test_streaming_response_retrieve(self, client: Orb) -> None: + with client.dimensional_price_groups.with_streaming_response.retrieve( + "dimensional_price_group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + dimensional_price_group = response.parse() + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_retrieve(self, client: Orb) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `dimensional_price_group_id` but received ''" + ): + client.dimensional_price_groups.with_raw_response.retrieve( + "", + ) + + @parametrize + def test_method_list(self, client: Orb) -> None: + dimensional_price_group = client.dimensional_price_groups.list() + assert_matches_type(SyncPage[DimensionalPriceGroup], dimensional_price_group, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Orb) -> None: + dimensional_price_group = client.dimensional_price_groups.list( + cursor="cursor", + limit=1, + ) + assert_matches_type(SyncPage[DimensionalPriceGroup], dimensional_price_group, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Orb) -> None: + response = client.dimensional_price_groups.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + dimensional_price_group = response.parse() + assert_matches_type(SyncPage[DimensionalPriceGroup], dimensional_price_group, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Orb) -> None: + with client.dimensional_price_groups.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + dimensional_price_group = response.parse() + assert_matches_type(SyncPage[DimensionalPriceGroup], dimensional_price_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncDimensionalPriceGroups: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + async def test_method_create(self, async_client: AsyncOrb) -> None: + dimensional_price_group = await async_client.dimensional_price_groups.create( + billable_metric_id="billable_metric_id", + dimensions=["region", "instance_type"], + name="name", + ) + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncOrb) -> None: + dimensional_price_group = await async_client.dimensional_price_groups.create( + billable_metric_id="billable_metric_id", + dimensions=["region", "instance_type"], + name="name", + external_dimensional_price_group_id="external_dimensional_price_group_id", + metadata={"foo": "string"}, + ) + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncOrb) -> None: + response = await async_client.dimensional_price_groups.with_raw_response.create( + billable_metric_id="billable_metric_id", + dimensions=["region", "instance_type"], + name="name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + dimensional_price_group = response.parse() + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncOrb) -> None: + async with async_client.dimensional_price_groups.with_streaming_response.create( + billable_metric_id="billable_metric_id", + dimensions=["region", "instance_type"], + name="name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + dimensional_price_group = await response.parse() + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_retrieve(self, async_client: AsyncOrb) -> None: + dimensional_price_group = await async_client.dimensional_price_groups.retrieve( + "dimensional_price_group_id", + ) + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncOrb) -> None: + response = await async_client.dimensional_price_groups.with_raw_response.retrieve( + "dimensional_price_group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + dimensional_price_group = response.parse() + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncOrb) -> None: + async with async_client.dimensional_price_groups.with_streaming_response.retrieve( + "dimensional_price_group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + dimensional_price_group = await response.parse() + assert_matches_type(DimensionalPriceGroup, dimensional_price_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncOrb) -> None: + with pytest.raises( + ValueError, match=r"Expected a non-empty value for `dimensional_price_group_id` but received ''" + ): + await async_client.dimensional_price_groups.with_raw_response.retrieve( + "", + ) + + @parametrize + async def test_method_list(self, async_client: AsyncOrb) -> None: + dimensional_price_group = await async_client.dimensional_price_groups.list() + assert_matches_type(AsyncPage[DimensionalPriceGroup], dimensional_price_group, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncOrb) -> None: + dimensional_price_group = await async_client.dimensional_price_groups.list( + cursor="cursor", + limit=1, + ) + assert_matches_type(AsyncPage[DimensionalPriceGroup], dimensional_price_group, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncOrb) -> None: + response = await async_client.dimensional_price_groups.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + dimensional_price_group = response.parse() + assert_matches_type(AsyncPage[DimensionalPriceGroup], dimensional_price_group, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncOrb) -> None: + async with async_client.dimensional_price_groups.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + dimensional_price_group = await response.parse() + assert_matches_type(AsyncPage[DimensionalPriceGroup], dimensional_price_group, path=["response"]) + + assert cast(Any, response.is_closed) is True From 9406c9ab61d6969699bbb028b3d953cba02b0706 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 20:37:36 +0000 Subject: [PATCH 05/21] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index c963fb86..0b824ed2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 101 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-726c25fdf0fdd4b7c5a9c36d30e33990d2a4b63c4260be340400f8ded23b578f.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-c5fe55b056b10d6581c877beb0639a8f0a623e52fd9778e56fab8a86439bd31b.yml From c24985f1ff9e87de455a4b862045701f6c646bab Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 1 Jan 2025 19:36:14 +0000 Subject: [PATCH 06/21] chore(internal): codegen related update (#473) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 782200ee..00b1f9c8 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2024 Orb + Copyright 2025 Orb Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From d2fb2aa50c27160a8fd386aec812e8f3ace527fe Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 19:47:41 +0000 Subject: [PATCH 07/21] feat(api): api update (#474) --- .stats.yml | 2 +- src/orb/resources/events/backfills.py | 12 +++++--- src/orb/resources/events/events.py | 8 +++--- src/orb/types/alert.py | 41 +++++++++++++++++++++++---- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/.stats.yml b/.stats.yml index 0b824ed2..7dd05a09 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 101 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-c5fe55b056b10d6581c877beb0639a8f0a623e52fd9778e56fab8a86439bd31b.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-52bd3046e73f201c4d08edfa92756791c015be907691a7893f8e7782cc2aea6f.yml diff --git a/src/orb/resources/events/backfills.py b/src/orb/resources/events/backfills.py index 112f7807..a7894d16 100644 --- a/src/orb/resources/events/backfills.py +++ b/src/orb/resources/events/backfills.py @@ -97,8 +97,10 @@ def create( only affect events for that customer. If neither is specified, the backfill will affect all customers. - When `replace_existing_events` is `true`, the field `filter` can be optionally - added which enables filtering using + When `replace_existing_events` is `true`, this indicates that existing events in + the timeframe should no longer be counted towards invoiced usage. In this + scenario, the parameter `filter` can be optionally added which enables filtering + using [computed properties](../guides/extensibility/advanced-metrics#computed-properties). The expressiveness of computed properties allows you to deprecate existing events based on both a period of time and specific property values. @@ -407,8 +409,10 @@ async def create( only affect events for that customer. If neither is specified, the backfill will affect all customers. - When `replace_existing_events` is `true`, the field `filter` can be optionally - added which enables filtering using + When `replace_existing_events` is `true`, this indicates that existing events in + the timeframe should no longer be counted towards invoiced usage. In this + scenario, the parameter `filter` can be optionally added which enables filtering + using [computed properties](../guides/extensibility/advanced-metrics#computed-properties). The expressiveness of computed properties allows you to deprecate existing events based on both a period of time and specific property values. diff --git a/src/orb/resources/events/events.py b/src/orb/resources/events/events.py index 3d4efed8..31f731ad 100644 --- a/src/orb/resources/events/events.py +++ b/src/orb/resources/events/events.py @@ -211,8 +211,8 @@ def deprecate( call to a payment gateway failed and the user should not be billed) If you want to only change specific properties of an event, but keep the event - as part of the billing calculation, use the [Amend single event](amend-event) - endpoint instead. + as part of the billing calculation, use the [Amend event](amend-event) endpoint + instead. This API is always audit-safe. The process will still retain the deprecated event, though it will be ignored for billing calculations. For auditing and data @@ -760,8 +760,8 @@ async def deprecate( call to a payment gateway failed and the user should not be billed) If you want to only change specific properties of an event, but keep the event - as part of the billing calculation, use the [Amend single event](amend-event) - endpoint instead. + as part of the billing calculation, use the [Amend event](amend-event) endpoint + instead. This API is always audit-safe. The process will still retain the deprecated event, though it will be ignored for billing calculations. For auditing and data diff --git a/src/orb/types/alert.py b/src/orb/types/alert.py index 7a7dd4b2..0ef04360 100644 --- a/src/orb/types/alert.py +++ b/src/orb/types/alert.py @@ -1,12 +1,41 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Optional +from typing import List, Optional from datetime import datetime from typing_extensions import Literal from .._models import BaseModel -__all__ = ["Alert", "Threshold"] +__all__ = ["Alert", "Customer", "Metric", "Plan", "Subscription", "Threshold"] + + +class Customer(BaseModel): + id: str + + external_customer_id: Optional[str] = None + + +class Metric(BaseModel): + id: str + + +class Plan(BaseModel): + id: Optional[str] = None + + external_plan_id: Optional[str] = None + """ + An optional user-defined ID for this plan resource, used throughout the system + as an alias for this Plan. Use this field to identify a plan by an existing + identifier in your system. + """ + + name: Optional[str] = None + + plan_version: str + + +class Subscription(BaseModel): + id: str class Threshold(BaseModel): @@ -28,19 +57,19 @@ class Alert(BaseModel): currency: Optional[str] = None """The name of the currency the credit balance or invoice cost is denominated in.""" - customer: Optional[Dict[str, Optional[str]]] = None + customer: Optional[Customer] = None """The customer the alert applies to.""" enabled: bool """Whether the alert is enabled or disabled.""" - metric: Optional[Dict[str, Optional[str]]] = None + metric: Optional[Metric] = None """The metric the alert applies to.""" - plan: Optional[Dict[str, Optional[str]]] = None + plan: Optional[Plan] = None """The plan the alert applies to.""" - subscription: Optional[Dict[str, Optional[str]]] = None + subscription: Optional[Subscription] = None """The subscription the alert applies to.""" thresholds: Optional[List[Threshold]] = None From f684ed42a5452933f4fe5a495b309a74267e4728 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:46:06 +0000 Subject: [PATCH 08/21] chore(client): simplify `Optional[object]` to just `object` (#475) --- src/orb/types/invoice.py | 2 +- src/orb/types/invoice_fetch_upcoming_response.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/orb/types/invoice.py b/src/orb/types/invoice.py index 964e0f7d..1daa1271 100644 --- a/src/orb/types/invoice.py +++ b/src/orb/types/invoice.py @@ -918,7 +918,7 @@ class Invoice(BaseModel): | Vietnam | `vn_tin` | Vietnamese Tax ID Number | """ - discount: Optional[object] = None + discount: object """This field is deprecated in favor of `discounts`. If a `discounts` list is provided, the first discount in the list will be diff --git a/src/orb/types/invoice_fetch_upcoming_response.py b/src/orb/types/invoice_fetch_upcoming_response.py index 8ec2e987..3a716af2 100644 --- a/src/orb/types/invoice_fetch_upcoming_response.py +++ b/src/orb/types/invoice_fetch_upcoming_response.py @@ -918,7 +918,7 @@ class InvoiceFetchUpcomingResponse(BaseModel): | Vietnam | `vn_tin` | Vietnamese Tax ID Number | """ - discount: Optional[object] = None + discount: object """This field is deprecated in favor of `discounts`. If a `discounts` list is provided, the first discount in the list will be From cafafe4cfdfd12d1bbad5d1fce0ad0b992741993 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:14:03 +0000 Subject: [PATCH 09/21] chore: add missing isclass check (#476) --- src/orb/_models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/orb/_models.py b/src/orb/_models.py index 7a547ce5..d56ea1d9 100644 --- a/src/orb/_models.py +++ b/src/orb/_models.py @@ -488,7 +488,11 @@ def construct_type(*, value: object, type_: object) -> object: _, items_type = get_args(type_) # Dict[_, items_type] return {key: construct_type(value=item, type_=items_type) for key, item in value.items()} - if not is_literal_type(type_) and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel)): + if ( + not is_literal_type(type_) + and inspect.isclass(origin) + and (issubclass(origin, BaseModel) or issubclass(origin, GenericModel)) + ): if is_list(value): return [cast(Any, type_).construct(**entry) if is_mapping(entry) else entry for entry in value] From 59c3a8bdfa3b18311e956511afd31d211c6f320d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:56:43 +0000 Subject: [PATCH 10/21] feat(api): api update (#477) --- .stats.yml | 2 +- src/orb/types/invoice.py | 7 +++++-- src/orb/types/invoice_fetch_upcoming_response.py | 7 +++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index 7dd05a09..c4326132 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 101 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-52bd3046e73f201c4d08edfa92756791c015be907691a7893f8e7782cc2aea6f.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-b6d60f8edbdc94e65f06b0b002cc8e1f27aceccc67917bea849425701ba82fb8.yml diff --git a/src/orb/types/invoice.py b/src/orb/types/invoice.py index 1daa1271..9799e405 100644 --- a/src/orb/types/invoice.py +++ b/src/orb/types/invoice.py @@ -927,8 +927,11 @@ class Invoice(BaseModel): discounts: List[InvoiceLevelDiscount] - due_date: datetime - """When the invoice payment is due.""" + due_date: Optional[datetime] = None + """When the invoice payment is due. + + The due date is null if the invoice is not yet finalized. + """ eligible_to_issue_at: Optional[datetime] = None """ diff --git a/src/orb/types/invoice_fetch_upcoming_response.py b/src/orb/types/invoice_fetch_upcoming_response.py index 3a716af2..5b7f7d2c 100644 --- a/src/orb/types/invoice_fetch_upcoming_response.py +++ b/src/orb/types/invoice_fetch_upcoming_response.py @@ -927,8 +927,11 @@ class InvoiceFetchUpcomingResponse(BaseModel): discounts: List[InvoiceLevelDiscount] - due_date: datetime - """When the invoice payment is due.""" + due_date: Optional[datetime] = None + """When the invoice payment is due. + + The due date is null if the invoice is not yet finalized. + """ eligible_to_issue_at: Optional[datetime] = None """ From 4036ecaf1d14f3c235c11ce1551490a86fab8066 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 20:18:01 +0000 Subject: [PATCH 11/21] feat(api): api update (#478) --- .stats.yml | 2 +- src/orb/types/invoice.py | 7 ++----- src/orb/types/invoice_fetch_upcoming_response.py | 7 ++----- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.stats.yml b/.stats.yml index c4326132..7dd05a09 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 101 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-b6d60f8edbdc94e65f06b0b002cc8e1f27aceccc67917bea849425701ba82fb8.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-52bd3046e73f201c4d08edfa92756791c015be907691a7893f8e7782cc2aea6f.yml diff --git a/src/orb/types/invoice.py b/src/orb/types/invoice.py index 9799e405..1daa1271 100644 --- a/src/orb/types/invoice.py +++ b/src/orb/types/invoice.py @@ -927,11 +927,8 @@ class Invoice(BaseModel): discounts: List[InvoiceLevelDiscount] - due_date: Optional[datetime] = None - """When the invoice payment is due. - - The due date is null if the invoice is not yet finalized. - """ + due_date: datetime + """When the invoice payment is due.""" eligible_to_issue_at: Optional[datetime] = None """ diff --git a/src/orb/types/invoice_fetch_upcoming_response.py b/src/orb/types/invoice_fetch_upcoming_response.py index 5b7f7d2c..3a716af2 100644 --- a/src/orb/types/invoice_fetch_upcoming_response.py +++ b/src/orb/types/invoice_fetch_upcoming_response.py @@ -927,11 +927,8 @@ class InvoiceFetchUpcomingResponse(BaseModel): discounts: List[InvoiceLevelDiscount] - due_date: Optional[datetime] = None - """When the invoice payment is due. - - The due date is null if the invoice is not yet finalized. - """ + due_date: datetime + """When the invoice payment is due.""" eligible_to_issue_at: Optional[datetime] = None """ From 6b04deaf0406eb2130ca9f159d42f84bd5ecac4a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 09:34:37 +0000 Subject: [PATCH 12/21] chore(internal): bump httpx dependency (#480) --- pyproject.toml | 2 +- requirements-dev.lock | 5 ++--- requirements.lock | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5e98c63e..6e3ae75c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ dev-dependencies = [ "dirty-equals>=0.6.0", "importlib-metadata>=6.7.0", "rich>=13.7.1", - "nest_asyncio==1.6.0" + "nest_asyncio==1.6.0", ] [tool.rye.scripts] diff --git a/requirements-dev.lock b/requirements-dev.lock index a9f3cea1..29b7f1c5 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -35,7 +35,7 @@ h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx -httpx==0.25.2 +httpx==0.28.1 # via orb-billing # via respx idna==3.4 @@ -76,7 +76,7 @@ python-dateutil==2.8.2 # via time-machine pytz==2023.3.post1 # via dirty-equals -respx==0.20.2 +respx==0.22.0 rich==13.7.1 ruff==0.6.9 setuptools==68.2.2 @@ -85,7 +85,6 @@ six==1.16.0 # via python-dateutil sniffio==1.3.0 # via anyio - # via httpx # via orb-billing time-machine==2.9.0 tomli==2.0.2 diff --git a/requirements.lock b/requirements.lock index 844a8552..dcfb84e9 100644 --- a/requirements.lock +++ b/requirements.lock @@ -25,7 +25,7 @@ h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx -httpx==0.25.2 +httpx==0.28.1 # via orb-billing idna==3.4 # via anyio @@ -36,7 +36,6 @@ pydantic-core==2.27.1 # via pydantic sniffio==1.3.0 # via anyio - # via httpx # via orb-billing typing-extensions==4.12.2 # via anyio From c14225cd09e514dea91586874e851cf1933b9af5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 19:58:55 +0000 Subject: [PATCH 13/21] fix(client): only call .close() when needed (#481) --- src/orb/_base_client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/orb/_base_client.py b/src/orb/_base_client.py index 7a91b0d5..8c04d929 100644 --- a/src/orb/_base_client.py +++ b/src/orb/_base_client.py @@ -768,6 +768,9 @@ def __init__(self, **kwargs: Any) -> None: class SyncHttpxClientWrapper(DefaultHttpxClient): def __del__(self) -> None: + if self.is_closed: + return + try: self.close() except Exception: @@ -1349,6 +1352,9 @@ def __init__(self, **kwargs: Any) -> None: class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): def __del__(self) -> None: + if self.is_closed: + return + try: # TODO(someday): support non asyncio runtimes here asyncio.get_running_loop().create_task(self.aclose()) From b8a2ce60542927b91329a10dd2d39efac96d6d9b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:57:24 +0000 Subject: [PATCH 14/21] docs: fix typos (#482) --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 89850273..20c1dbb3 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ except orb.APIStatusError as e: print(e.response) ``` -Error codes are as followed: +Error codes are as follows: | Status Code | Error Type | | ----------- | -------------------------- | @@ -328,7 +328,7 @@ customer = response.parse() # get the object that `customers.create()` would ha print(customer.id) ``` -These methods return an [`LegacyAPIResponse`](https://github.com/orbcorp/orb-python/tree/main/src/orb/_legacy_response.py) object. This is a legacy class as we're changing it slightly in the next major version. +These methods return a [`LegagcyAPIResponse`](https://github.com/orbcorp/orb-python/tree/main/src/orb/_legacy_response.py) object. This is a legacy class as we're changing it slightly in the next major version. For the sync client this will mostly be the same with the exception of `content` & `text` will be methods instead of properties. In the @@ -367,8 +367,7 @@ If you need to access undocumented endpoints, params, or response properties, th #### Undocumented endpoints To make requests to undocumented endpoints, you can make requests using `client.get`, `client.post`, and other -http verbs. Options on the client will be respected (such as retries) will be respected when making this -request. +http verbs. Options on the client will be respected (such as retries) when making this request. ```py import httpx From 569ad308f81bc99620d2d08b3afc4ea95f0fe8d5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:58:39 +0000 Subject: [PATCH 15/21] docs: more typo fixes (#483) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 20c1dbb3..27c3a725 100644 --- a/README.md +++ b/README.md @@ -328,7 +328,7 @@ customer = response.parse() # get the object that `customers.create()` would ha print(customer.id) ``` -These methods return a [`LegagcyAPIResponse`](https://github.com/orbcorp/orb-python/tree/main/src/orb/_legacy_response.py) object. This is a legacy class as we're changing it slightly in the next major version. +These methods return a [`LegacyAPIResponse`](https://github.com/orbcorp/orb-python/tree/main/src/orb/_legacy_response.py) object. This is a legacy class as we're changing it slightly in the next major version. For the sync client this will mostly be the same with the exception of `content` & `text` will be methods instead of properties. In the From a429148a83f0e51afcb0b99d5661c706023da48f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:59:24 +0000 Subject: [PATCH 16/21] chore(internal): codegen related update (#484) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27c3a725..b141afdf 100644 --- a/README.md +++ b/README.md @@ -439,7 +439,7 @@ with Orb() as client: This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: 1. Changes that only affect static types, without breaking runtime behavior. -2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals)_. +2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ 3. Changes that we do not expect to impact the vast majority of users in practice. We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. From 7a80005983ff3799ea685034dd750c08c74704e7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 16:54:56 +0000 Subject: [PATCH 17/21] fix: correctly handle deserialising `cls` fields (#485) --- src/orb/_models.py | 8 ++++---- tests/test_models.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/orb/_models.py b/src/orb/_models.py index d56ea1d9..9a918aab 100644 --- a/src/orb/_models.py +++ b/src/orb/_models.py @@ -179,14 +179,14 @@ def __str__(self) -> str: @classmethod @override def construct( # pyright: ignore[reportIncompatibleMethodOverride] - cls: Type[ModelT], + __cls: Type[ModelT], _fields_set: set[str] | None = None, **values: object, ) -> ModelT: - m = cls.__new__(cls) + m = __cls.__new__(__cls) fields_values: dict[str, object] = {} - config = get_model_config(cls) + config = get_model_config(__cls) populate_by_name = ( config.allow_population_by_field_name if isinstance(config, _ConfigProtocol) @@ -196,7 +196,7 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride] if _fields_set is None: _fields_set = set() - model_fields = get_model_fields(cls) + model_fields = get_model_fields(__cls) for name, field in model_fields.items(): key = field.alias if key is None or (key not in values and populate_by_name): diff --git a/tests/test_models.py b/tests/test_models.py index 52caebb2..011b9f92 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -844,3 +844,13 @@ class Model(BaseModel): assert m.alias == "foo" assert isinstance(m.union, str) assert m.union == "bar" + + +@pytest.mark.skipif(not PYDANTIC_V2, reason="TypeAliasType is not supported in Pydantic v1") +def test_field_named_cls() -> None: + class Model(BaseModel): + cls: str + + m = construct_type(value={"cls": "foo"}, type_=Model) + assert isinstance(m, Model) + assert isinstance(m.cls, str) From be53793ecadc6adbda0baab9c7b47b15a0b585ec Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:04:42 +0000 Subject: [PATCH 18/21] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 7dd05a09..b1872c09 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,2 +1,2 @@ configured_endpoints: 101 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-52bd3046e73f201c4d08edfa92756791c015be907691a7893f8e7782cc2aea6f.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/orb%2Forb-1720084d65e39f50455fe3a8756afc68fedf57306a727f92e4d020c28878df87.yml From 4677063f95f37b7b982816871d8fa98871efd15f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:48:17 +0000 Subject: [PATCH 19/21] chore(internal): update deps (#486) --- mypy.ini | 2 +- requirements-dev.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mypy.ini b/mypy.ini index 4f22c5a2..35b50327 100644 --- a/mypy.ini +++ b/mypy.ini @@ -41,7 +41,7 @@ cache_fine_grained = True # ``` # Changing this codegen to make mypy happy would increase complexity # and would not be worth it. -disable_error_code = func-returns-value +disable_error_code = func-returns-value,overload-cannot-match # https://github.com/python/mypy/issues/12162 [mypy.overrides] diff --git a/requirements-dev.lock b/requirements-dev.lock index 29b7f1c5..efeb6441 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -48,7 +48,7 @@ markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -mypy==1.13.0 +mypy==1.14.1 mypy-extensions==1.0.0 # via mypy nest-asyncio==1.6.0 @@ -68,7 +68,7 @@ pydantic-core==2.27.1 # via pydantic pygments==2.18.0 # via rich -pyright==1.1.390 +pyright==1.1.391 pytest==8.3.3 # via pytest-asyncio pytest-asyncio==0.24.0 From 667a330e4074c66434a19d25fdfaf2a710f2d2c8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:45:09 +0000 Subject: [PATCH 20/21] chore(internal): bump pyright dependency (#487) --- requirements-dev.lock | 2 +- src/orb/_legacy_response.py | 12 ++++++++++-- src/orb/_response.py | 8 +++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index efeb6441..172ccf7f 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -68,7 +68,7 @@ pydantic-core==2.27.1 # via pydantic pygments==2.18.0 # via rich -pyright==1.1.391 +pyright==1.1.392.post0 pytest==8.3.3 # via pytest-asyncio pytest-asyncio==0.24.0 diff --git a/src/orb/_legacy_response.py b/src/orb/_legacy_response.py index 4cbb4ddf..1be09f02 100644 --- a/src/orb/_legacy_response.py +++ b/src/orb/_legacy_response.py @@ -262,7 +262,9 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: if origin == LegacyAPIResponse: raise RuntimeError("Unexpected state - cast_to is `APIResponse`") - if inspect.isclass(origin) and issubclass(origin, httpx.Response): + if inspect.isclass( + origin # pyright: ignore[reportUnknownArgumentType] + ) and issubclass(origin, httpx.Response): # Because of the invariance of our ResponseT TypeVar, users can subclass httpx.Response # and pass that class to our request functions. We cannot change the variance to be either # covariant or contravariant as that makes our usage of ResponseT illegal. We could construct @@ -272,7 +274,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") return cast(R, response) - if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel): + if ( + inspect.isclass( + origin # pyright: ignore[reportUnknownArgumentType] + ) + and not issubclass(origin, BaseModel) + and issubclass(origin, pydantic.BaseModel) + ): raise TypeError("Pydantic models must subclass our base model type, e.g. `from orb import BaseModel`") if ( diff --git a/src/orb/_response.py b/src/orb/_response.py index 20f3cd4d..cefbd234 100644 --- a/src/orb/_response.py +++ b/src/orb/_response.py @@ -214,7 +214,13 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: raise ValueError(f"Subclasses of httpx.Response cannot be passed to `cast_to`") return cast(R, response) - if inspect.isclass(origin) and not issubclass(origin, BaseModel) and issubclass(origin, pydantic.BaseModel): + if ( + inspect.isclass( + origin # pyright: ignore[reportUnknownArgumentType] + ) + and not issubclass(origin, BaseModel) + and issubclass(origin, pydantic.BaseModel) + ): raise TypeError("Pydantic models must subclass our base model type, e.g. `from orb import BaseModel`") if ( From dd096711008701686dd8831c5d13d269f7718d91 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:45:35 +0000 Subject: [PATCH 21/21] release: 2.21.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 36 +++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/orb/_version.py | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index da2177c6..02bd31d3 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.20.0" + ".": "2.21.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 30d467ce..24b87d4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,41 @@ # Changelog +## 2.21.0 (2025-01-15) + +Full Changelog: [v2.20.0...v2.21.0](https://github.com/orbcorp/orb-python/compare/v2.20.0...v2.21.0) + +### Features + +* **api:** api update ([#471](https://github.com/orbcorp/orb-python/issues/471)) ([d23ec0e](https://github.com/orbcorp/orb-python/commit/d23ec0e08cc5e8bd681a8c7947778a8445b81f78)) +* **api:** api update ([#472](https://github.com/orbcorp/orb-python/issues/472)) ([0a22350](https://github.com/orbcorp/orb-python/commit/0a2235016cfba90da5f68e950579d0720bd40045)) +* **api:** api update ([#474](https://github.com/orbcorp/orb-python/issues/474)) ([d2fb2aa](https://github.com/orbcorp/orb-python/commit/d2fb2aa50c27160a8fd386aec812e8f3ace527fe)) +* **api:** api update ([#477](https://github.com/orbcorp/orb-python/issues/477)) ([59c3a8b](https://github.com/orbcorp/orb-python/commit/59c3a8bdfa3b18311e956511afd31d211c6f320d)) +* **api:** api update ([#478](https://github.com/orbcorp/orb-python/issues/478)) ([4036eca](https://github.com/orbcorp/orb-python/commit/4036ecaf1d14f3c235c11ce1551490a86fab8066)) + + +### Bug Fixes + +* **client:** only call .close() when needed ([#481](https://github.com/orbcorp/orb-python/issues/481)) ([c14225c](https://github.com/orbcorp/orb-python/commit/c14225cd09e514dea91586874e851cf1933b9af5)) +* correctly handle deserialising `cls` fields ([#485](https://github.com/orbcorp/orb-python/issues/485)) ([7a80005](https://github.com/orbcorp/orb-python/commit/7a80005983ff3799ea685034dd750c08c74704e7)) + + +### Chores + +* add missing isclass check ([#476](https://github.com/orbcorp/orb-python/issues/476)) ([cafafe4](https://github.com/orbcorp/orb-python/commit/cafafe4cfdfd12d1bbad5d1fce0ad0b992741993)) +* **client:** simplify `Optional[object]` to just `object` ([#475](https://github.com/orbcorp/orb-python/issues/475)) ([f684ed4](https://github.com/orbcorp/orb-python/commit/f684ed42a5452933f4fe5a495b309a74267e4728)) +* **internal:** bump httpx dependency ([#480](https://github.com/orbcorp/orb-python/issues/480)) ([6b04dea](https://github.com/orbcorp/orb-python/commit/6b04deaf0406eb2130ca9f159d42f84bd5ecac4a)) +* **internal:** bump pyright dependency ([#487](https://github.com/orbcorp/orb-python/issues/487)) ([667a330](https://github.com/orbcorp/orb-python/commit/667a330e4074c66434a19d25fdfaf2a710f2d2c8)) +* **internal:** codegen related update ([#473](https://github.com/orbcorp/orb-python/issues/473)) ([c24985f](https://github.com/orbcorp/orb-python/commit/c24985f1ff9e87de455a4b862045701f6c646bab)) +* **internal:** codegen related update ([#484](https://github.com/orbcorp/orb-python/issues/484)) ([a429148](https://github.com/orbcorp/orb-python/commit/a429148a83f0e51afcb0b99d5661c706023da48f)) +* **internal:** update deps ([#486](https://github.com/orbcorp/orb-python/issues/486)) ([4677063](https://github.com/orbcorp/orb-python/commit/4677063f95f37b7b982816871d8fa98871efd15f)) +* **internal:** version bump ([#469](https://github.com/orbcorp/orb-python/issues/469)) ([e95eb1d](https://github.com/orbcorp/orb-python/commit/e95eb1d93a4990d3fe2d722abf05b4bfa558c745)) + + +### Documentation + +* fix typos ([#482](https://github.com/orbcorp/orb-python/issues/482)) ([b8a2ce6](https://github.com/orbcorp/orb-python/commit/b8a2ce60542927b91329a10dd2d39efac96d6d9b)) +* more typo fixes ([#483](https://github.com/orbcorp/orb-python/issues/483)) ([569ad30](https://github.com/orbcorp/orb-python/commit/569ad308f81bc99620d2d08b3afc4ea95f0fe8d5)) + ## 2.20.0 (2024-12-19) Full Changelog: [v2.19.0...v2.20.0](https://github.com/orbcorp/orb-python/compare/v2.19.0...v2.20.0) diff --git a/pyproject.toml b/pyproject.toml index 6e3ae75c..c1f63b5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "orb-billing" -version = "2.20.0" +version = "2.21.0" description = "The official Python library for the orb API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/orb/_version.py b/src/orb/_version.py index 4efdcb3d..db5740a0 100644 --- a/src/orb/_version.py +++ b/src/orb/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "orb" -__version__ = "2.20.0" # x-release-please-version +__version__ = "2.21.0" # x-release-please-version