diff --git a/.stats.yml b/.stats.yml index 58526a7..85f79b6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 48 +configured_endpoints: 49 diff --git a/api.md b/api.md index a331b68..177624f 100644 --- a/api.md +++ b/api.md @@ -206,6 +206,18 @@ Methods: - client.accounts.usage.get(\*\*params) -> UsageGetResponse +## UsageAnalytics + +Types: + +```python +from imagekitio.types.accounts import RequestBandwidthEntry, UsageAnalyticsResponse +``` + +Methods: + +- client.accounts.usage_analytics.get(\*\*params) -> UsageAnalyticsResponse + ## Origins Types: diff --git a/src/imagekitio/resources/accounts/__init__.py b/src/imagekitio/resources/accounts/__init__.py index fc56413..1e10f73 100644 --- a/src/imagekitio/resources/accounts/__init__.py +++ b/src/imagekitio/resources/accounts/__init__.py @@ -32,6 +32,14 @@ URLEndpointsResourceWithStreamingResponse, AsyncURLEndpointsResourceWithStreamingResponse, ) +from .usage_analytics import ( + UsageAnalyticsResource, + AsyncUsageAnalyticsResource, + UsageAnalyticsResourceWithRawResponse, + AsyncUsageAnalyticsResourceWithRawResponse, + UsageAnalyticsResourceWithStreamingResponse, + AsyncUsageAnalyticsResourceWithStreamingResponse, +) __all__ = [ "UsageResource", @@ -40,6 +48,12 @@ "AsyncUsageResourceWithRawResponse", "UsageResourceWithStreamingResponse", "AsyncUsageResourceWithStreamingResponse", + "UsageAnalyticsResource", + "AsyncUsageAnalyticsResource", + "UsageAnalyticsResourceWithRawResponse", + "AsyncUsageAnalyticsResourceWithRawResponse", + "UsageAnalyticsResourceWithStreamingResponse", + "AsyncUsageAnalyticsResourceWithStreamingResponse", "OriginsResource", "AsyncOriginsResource", "OriginsResourceWithRawResponse", diff --git a/src/imagekitio/resources/accounts/accounts.py b/src/imagekitio/resources/accounts/accounts.py index 461e8cf..a15e343 100644 --- a/src/imagekitio/resources/accounts/accounts.py +++ b/src/imagekitio/resources/accounts/accounts.py @@ -28,6 +28,14 @@ URLEndpointsResourceWithStreamingResponse, AsyncURLEndpointsResourceWithStreamingResponse, ) +from .usage_analytics import ( + UsageAnalyticsResource, + AsyncUsageAnalyticsResource, + UsageAnalyticsResourceWithRawResponse, + AsyncUsageAnalyticsResourceWithRawResponse, + UsageAnalyticsResourceWithStreamingResponse, + AsyncUsageAnalyticsResourceWithStreamingResponse, +) __all__ = ["AccountsResource", "AsyncAccountsResource"] @@ -37,6 +45,10 @@ class AccountsResource(SyncAPIResource): def usage(self) -> UsageResource: return UsageResource(self._client) + @cached_property + def usage_analytics(self) -> UsageAnalyticsResource: + return UsageAnalyticsResource(self._client) + @cached_property def origins(self) -> OriginsResource: return OriginsResource(self._client) @@ -70,6 +82,10 @@ class AsyncAccountsResource(AsyncAPIResource): def usage(self) -> AsyncUsageResource: return AsyncUsageResource(self._client) + @cached_property + def usage_analytics(self) -> AsyncUsageAnalyticsResource: + return AsyncUsageAnalyticsResource(self._client) + @cached_property def origins(self) -> AsyncOriginsResource: return AsyncOriginsResource(self._client) @@ -106,6 +122,10 @@ def __init__(self, accounts: AccountsResource) -> None: def usage(self) -> UsageResourceWithRawResponse: return UsageResourceWithRawResponse(self._accounts.usage) + @cached_property + def usage_analytics(self) -> UsageAnalyticsResourceWithRawResponse: + return UsageAnalyticsResourceWithRawResponse(self._accounts.usage_analytics) + @cached_property def origins(self) -> OriginsResourceWithRawResponse: return OriginsResourceWithRawResponse(self._accounts.origins) @@ -123,6 +143,10 @@ def __init__(self, accounts: AsyncAccountsResource) -> None: def usage(self) -> AsyncUsageResourceWithRawResponse: return AsyncUsageResourceWithRawResponse(self._accounts.usage) + @cached_property + def usage_analytics(self) -> AsyncUsageAnalyticsResourceWithRawResponse: + return AsyncUsageAnalyticsResourceWithRawResponse(self._accounts.usage_analytics) + @cached_property def origins(self) -> AsyncOriginsResourceWithRawResponse: return AsyncOriginsResourceWithRawResponse(self._accounts.origins) @@ -140,6 +164,10 @@ def __init__(self, accounts: AccountsResource) -> None: def usage(self) -> UsageResourceWithStreamingResponse: return UsageResourceWithStreamingResponse(self._accounts.usage) + @cached_property + def usage_analytics(self) -> UsageAnalyticsResourceWithStreamingResponse: + return UsageAnalyticsResourceWithStreamingResponse(self._accounts.usage_analytics) + @cached_property def origins(self) -> OriginsResourceWithStreamingResponse: return OriginsResourceWithStreamingResponse(self._accounts.origins) @@ -157,6 +185,10 @@ def __init__(self, accounts: AsyncAccountsResource) -> None: def usage(self) -> AsyncUsageResourceWithStreamingResponse: return AsyncUsageResourceWithStreamingResponse(self._accounts.usage) + @cached_property + def usage_analytics(self) -> AsyncUsageAnalyticsResourceWithStreamingResponse: + return AsyncUsageAnalyticsResourceWithStreamingResponse(self._accounts.usage_analytics) + @cached_property def origins(self) -> AsyncOriginsResourceWithStreamingResponse: return AsyncOriginsResourceWithStreamingResponse(self._accounts.origins) diff --git a/src/imagekitio/resources/accounts/usage.py b/src/imagekitio/resources/accounts/usage.py index b35d3c9..3501e72 100644 --- a/src/imagekitio/resources/accounts/usage.py +++ b/src/imagekitio/resources/accounts/usage.py @@ -63,6 +63,12 @@ def get( other words, the data covers the period starting from the specified start date up to, but not including, the end date. + For an agency account, the returned usage is aggregated across the agency and + all of its child accounts that are billed to it. + + The response is cached for 6 hours per account, date range and requested + metrics. + Args: end_date: Specify a `endDate` in `YYYY-MM-DD` format. It should be after the `startDate`. The difference between `startDate` and `endDate` should be less than 90 days. @@ -136,6 +142,12 @@ async def get( other words, the data covers the period starting from the specified start date up to, but not including, the end date. + For an agency account, the returned usage is aggregated across the agency and + all of its child accounts that are billed to it. + + The response is cached for 6 hours per account, date range and requested + metrics. + Args: end_date: Specify a `endDate` in `YYYY-MM-DD` format. It should be after the `startDate`. The difference between `startDate` and `endDate` should be less than 90 days. diff --git a/src/imagekitio/resources/accounts/usage_analytics.py b/src/imagekitio/resources/accounts/usage_analytics.py new file mode 100644 index 0000000..e2880fe --- /dev/null +++ b/src/imagekitio/resources/accounts/usage_analytics.py @@ -0,0 +1,224 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date + +import httpx + +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._utils import maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.accounts import usage_analytics_get_params +from ...types.accounts.usage_analytics_response import UsageAnalyticsResponse + +__all__ = ["UsageAnalyticsResource", "AsyncUsageAnalyticsResource"] + + +class UsageAnalyticsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> UsageAnalyticsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/imagekit-developer/imagekit-python#accessing-raw-response-data-eg-headers + """ + return UsageAnalyticsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> UsageAnalyticsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/imagekit-developer/imagekit-python#with_streaming_response + """ + return UsageAnalyticsResourceWithStreamingResponse(self) + + def get( + self, + *, + end_date: Union[str, date], + start_date: Union[str, date], + # 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, + ) -> UsageAnalyticsResponse: + """ + **Note:** This API is currently in beta. + + Get the account analytics data between two dates. The response covers the period + from the start date to the end date, both dates inclusive. Both dates are + interpreted as UTC calendar days. + + The returned data is scoped to the requesting account only. Unlike + `/v1/accounts/usage`, an agency account's analytics are not aggregated across + its child accounts. + + The response is cached for 5 minutes per account and date range. Use + `generatedAt` to check how fresh the returned data is. + + Args: + end_date: Specify an `endDate` in `YYYY-MM-DD` format, interpreted as a UTC calendar day. + It should be after the `startDate`. The difference between `startDate` and + `endDate` should be less than 90 days. + + start_date: Specify a `startDate` in `YYYY-MM-DD` format, interpreted as a UTC calendar day. + It should be before the `endDate`. The difference between `startDate` and + `endDate` should be less than 90 days. + + 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( + "/v1/accounts/usage-analytics", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "end_date": end_date, + "start_date": start_date, + }, + usage_analytics_get_params.UsageAnalyticsGetParams, + ), + ), + cast_to=UsageAnalyticsResponse, + ) + + +class AsyncUsageAnalyticsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncUsageAnalyticsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/imagekit-developer/imagekit-python#accessing-raw-response-data-eg-headers + """ + return AsyncUsageAnalyticsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncUsageAnalyticsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/imagekit-developer/imagekit-python#with_streaming_response + """ + return AsyncUsageAnalyticsResourceWithStreamingResponse(self) + + async def get( + self, + *, + end_date: Union[str, date], + start_date: Union[str, date], + # 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, + ) -> UsageAnalyticsResponse: + """ + **Note:** This API is currently in beta. + + Get the account analytics data between two dates. The response covers the period + from the start date to the end date, both dates inclusive. Both dates are + interpreted as UTC calendar days. + + The returned data is scoped to the requesting account only. Unlike + `/v1/accounts/usage`, an agency account's analytics are not aggregated across + its child accounts. + + The response is cached for 5 minutes per account and date range. Use + `generatedAt` to check how fresh the returned data is. + + Args: + end_date: Specify an `endDate` in `YYYY-MM-DD` format, interpreted as a UTC calendar day. + It should be after the `startDate`. The difference between `startDate` and + `endDate` should be less than 90 days. + + start_date: Specify a `startDate` in `YYYY-MM-DD` format, interpreted as a UTC calendar day. + It should be before the `endDate`. The difference between `startDate` and + `endDate` should be less than 90 days. + + 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 await self._get( + "/v1/accounts/usage-analytics", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "end_date": end_date, + "start_date": start_date, + }, + usage_analytics_get_params.UsageAnalyticsGetParams, + ), + ), + cast_to=UsageAnalyticsResponse, + ) + + +class UsageAnalyticsResourceWithRawResponse: + def __init__(self, usage_analytics: UsageAnalyticsResource) -> None: + self._usage_analytics = usage_analytics + + self.get = to_raw_response_wrapper( + usage_analytics.get, + ) + + +class AsyncUsageAnalyticsResourceWithRawResponse: + def __init__(self, usage_analytics: AsyncUsageAnalyticsResource) -> None: + self._usage_analytics = usage_analytics + + self.get = async_to_raw_response_wrapper( + usage_analytics.get, + ) + + +class UsageAnalyticsResourceWithStreamingResponse: + def __init__(self, usage_analytics: UsageAnalyticsResource) -> None: + self._usage_analytics = usage_analytics + + self.get = to_streamed_response_wrapper( + usage_analytics.get, + ) + + +class AsyncUsageAnalyticsResourceWithStreamingResponse: + def __init__(self, usage_analytics: AsyncUsageAnalyticsResource) -> None: + self._usage_analytics = usage_analytics + + self.get = async_to_streamed_response_wrapper( + usage_analytics.get, + ) diff --git a/src/imagekitio/types/accounts/__init__.py b/src/imagekitio/types/accounts/__init__.py index 3d713db..60a394c 100644 --- a/src/imagekitio/types/accounts/__init__.py +++ b/src/imagekitio/types/accounts/__init__.py @@ -10,6 +10,9 @@ from .origin_request_param import OriginRequestParam as OriginRequestParam from .origin_update_params import OriginUpdateParams as OriginUpdateParams from .url_endpoint_response import URLEndpointResponse as URLEndpointResponse +from .request_bandwidth_entry import RequestBandwidthEntry as RequestBandwidthEntry +from .usage_analytics_response import UsageAnalyticsResponse as UsageAnalyticsResponse from .url_endpoint_create_params import URLEndpointCreateParams as URLEndpointCreateParams from .url_endpoint_list_response import URLEndpointListResponse as URLEndpointListResponse from .url_endpoint_update_params import URLEndpointUpdateParams as URLEndpointUpdateParams +from .usage_analytics_get_params import UsageAnalyticsGetParams as UsageAnalyticsGetParams diff --git a/src/imagekitio/types/accounts/request_bandwidth_entry.py b/src/imagekitio/types/accounts/request_bandwidth_entry.py new file mode 100644 index 0000000..ea245b6 --- /dev/null +++ b/src/imagekitio/types/accounts/request_bandwidth_entry.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["RequestBandwidthEntry"] + + +class RequestBandwidthEntry(BaseModel): + bandwidth_bytes: float = FieldInfo(alias="bandwidthBytes") + """Total bandwidth used in bytes.""" + + request_count: float = FieldInfo(alias="requestCount") + """Number of requests.""" diff --git a/src/imagekitio/types/accounts/usage_analytics_get_params.py b/src/imagekitio/types/accounts/usage_analytics_get_params.py new file mode 100644 index 0000000..7d882ad --- /dev/null +++ b/src/imagekitio/types/accounts/usage_analytics_get_params.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union +from datetime import date +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["UsageAnalyticsGetParams"] + + +class UsageAnalyticsGetParams(TypedDict, total=False): + end_date: Required[Annotated[Union[str, date], PropertyInfo(alias="endDate", format="iso8601")]] + """Specify an `endDate` in `YYYY-MM-DD` format, interpreted as a UTC calendar day. + + It should be after the `startDate`. The difference between `startDate` and + `endDate` should be less than 90 days. + """ + + start_date: Required[Annotated[Union[str, date], PropertyInfo(alias="startDate", format="iso8601")]] + """Specify a `startDate` in `YYYY-MM-DD` format, interpreted as a UTC calendar day. + + It should be before the `endDate`. The difference between `startDate` and + `endDate` should be less than 90 days. + """ diff --git a/src/imagekitio/types/accounts/usage_analytics_response.py b/src/imagekitio/types/accounts/usage_analytics_response.py new file mode 100644 index 0000000..131cf71 --- /dev/null +++ b/src/imagekitio/types/accounts/usage_analytics_response.py @@ -0,0 +1,488 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import TYPE_CHECKING, Dict, List +from datetime import date, datetime + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from .request_bandwidth_entry import RequestBandwidthEntry + +__all__ = [ + "UsageAnalyticsResponse", + "Browser", + "BrowserByBandwidth", + "BrowserByRequest", + "Cache", + "Country", + "CountryByBandwidth", + "CountryByRequest", + "Device", + "DeviceByBandwidth", + "DeviceByRequest", + "ErrorReason", + "Extension", + "Format", + "FormatByBandwidth", + "FormatByRequest", + "StatusCode", + "Top404Asset", + "TopImages", + "TopImagesByBandwidth", + "TopImagesByRequest", + "TopImageTransforms", + "TopImageTransformsByBandwidth", + "TopImageTransformsByRequest", + "TopOtherAssets", + "TopOtherAssetsByBandwidth", + "TopOtherAssetsByRequest", + "TopReferrers", + "TopReferrersByBandwidth", + "TopReferrersByRequest", + "TopUserAgents", + "TopUserAgentsByBandwidth", + "TopUserAgentsByRequest", + "TopVideos", + "TopVideosByBandwidth", + "TopVideosByRequest", + "TopVideoTransforms", + "TopVideoTransformsByBandwidth", + "TopVideoTransformsByRequest", + "URLEndpoints", + "URLEndpointsByBandwidth", + "URLEndpointsByRequest", + "VideoProcessing", +] + + +class BrowserByBandwidth(RequestBandwidthEntry): + name: str + """Browser name (e.g. `Chrome`).""" + + +class BrowserByRequest(RequestBandwidthEntry): + name: str + """Browser name (e.g. `Chrome`).""" + + +class Browser(BaseModel): + """CDN traffic grouped by browser.""" + + by_bandwidth: List[BrowserByBandwidth] = FieldInfo(alias="byBandwidth") + """Top browsers sorted by bandwidth utilized.""" + + by_requests: List[BrowserByRequest] = FieldInfo(alias="byRequests") + """Top browsers sorted by request count.""" + + +class Cache(BaseModel): + """CDN cache hit, miss and error counts for the date range.""" + + error_count: float = FieldInfo(alias="errorCount") + """ + Number of requests where the CDN encountered a cache error or exceeded capacity + while serving the response. + """ + + hit_count: float = FieldInfo(alias="hitCount") + """Number of requests served from cache, including full hits and revalidated hits.""" + + miss_count: float = FieldInfo(alias="missCount") + """ + Number of requests that were not found in cache and had to be fetched from + origin. + """ + + +class CountryByBandwidth(RequestBandwidthEntry): + code: str + """ISO country code.""" + + name: str + """Country name.""" + + +class CountryByRequest(RequestBandwidthEntry): + code: str + """ISO country code.""" + + name: str + """Country name.""" + + +class Country(BaseModel): + """CDN traffic grouped by country.""" + + by_bandwidth: List[CountryByBandwidth] = FieldInfo(alias="byBandwidth") + """Top requesting countries sorted by total bandwidth utilized.""" + + by_requests: List[CountryByRequest] = FieldInfo(alias="byRequests") + """Top requesting countries sorted by request count.""" + + +class DeviceByBandwidth(RequestBandwidthEntry): + name: str + """Device category combined with operating system or vendor (e.g. + + `Desktop - Windows PC`). + """ + + +class DeviceByRequest(RequestBandwidthEntry): + name: str + """Device category combined with operating system or vendor (e.g. + + `Desktop - Windows PC`). + """ + + +class Device(BaseModel): + """CDN traffic grouped by device and operating system (e.g. + + `Desktop - Apple Mac`, `Smartphone - Apple iPhone`). + """ + + by_bandwidth: List[DeviceByBandwidth] = FieldInfo(alias="byBandwidth") + """Top device/OS combinations sorted by bandwidth utilized.""" + + by_requests: List[DeviceByRequest] = FieldInfo(alias="byRequests") + """Top device/OS combinations sorted by request count.""" + + +class ErrorReason(BaseModel): + name: str + """Description of the error reason.""" + + request_count: float = FieldInfo(alias="requestCount") + """Number of requests that failed with this error reason.""" + + +class Extension(BaseModel): + name: str + """Extension identifier.""" + + operation_count: float = FieldInfo(alias="operationCount") + """Number of times this extension ran during the date range.""" + + +class FormatByBandwidth(RequestBandwidthEntry): + name: str + """MIME type (e.g. `image/webp`).""" + + +class FormatByRequest(RequestBandwidthEntry): + name: str + """MIME type (e.g. `image/webp`).""" + + +class Format(BaseModel): + """CDN traffic grouped by response `Content-Type`.""" + + by_bandwidth: List[FormatByBandwidth] = FieldInfo(alias="byBandwidth") + """Top content types sorted by bandwidth utilized.""" + + by_requests: List[FormatByRequest] = FieldInfo(alias="byRequests") + """Top content types sorted by request count.""" + + +class StatusCode(BaseModel): + name: str + """HTTP status code.""" + + request_count: float = FieldInfo(alias="requestCount") + """Number of requests that received this status code.""" + + +class Top404Asset(BaseModel): + name: str + """URL that returned a 404 response.""" + + request_count: float = FieldInfo(alias="requestCount") + """Number of requests to this URL that returned a 404 response.""" + + +class TopImagesByBandwidth(RequestBandwidthEntry): + name: str + """URL of the image asset.""" + + +class TopImagesByRequest(RequestBandwidthEntry): + name: str + """URL of the image asset.""" + + +class TopImages(BaseModel): + """Top image assets by traffic.""" + + by_bandwidth: List[TopImagesByBandwidth] = FieldInfo(alias="byBandwidth") + """Top image assets sorted by bandwidth utilized.""" + + by_requests: List[TopImagesByRequest] = FieldInfo(alias="byRequests") + """Top image assets sorted by request count.""" + + +class TopImageTransformsByBandwidth(RequestBandwidthEntry): + name: str + """Image transformation string (e.g. `tr:w-400,h-400`).""" + + +class TopImageTransformsByRequest(RequestBandwidthEntry): + name: str + """Image transformation string (e.g. `tr:w-400,h-400`).""" + + +class TopImageTransforms(BaseModel): + """Top image transformation strings by traffic.""" + + by_bandwidth: List[TopImageTransformsByBandwidth] = FieldInfo(alias="byBandwidth") + """Top image transformation strings sorted by bandwidth utilized.""" + + by_requests: List[TopImageTransformsByRequest] = FieldInfo(alias="byRequests") + """Top image transformation strings sorted by request count.""" + + +class TopOtherAssetsByBandwidth(RequestBandwidthEntry): + name: str + """URL of the non-image, non-video asset.""" + + +class TopOtherAssetsByRequest(RequestBandwidthEntry): + name: str + """URL of the non-image, non-video asset.""" + + +class TopOtherAssets(BaseModel): + """Top non-image, non-video assets by traffic.""" + + by_bandwidth: List[TopOtherAssetsByBandwidth] = FieldInfo(alias="byBandwidth") + """Top non-image, non-video assets sorted by bandwidth utilized.""" + + by_requests: List[TopOtherAssetsByRequest] = FieldInfo(alias="byRequests") + """Top non-image, non-video assets sorted by request count.""" + + +class TopReferrersByBandwidth(RequestBandwidthEntry): + name: str + """Referrer URL.""" + + +class TopReferrersByRequest(RequestBandwidthEntry): + name: str + """Referrer URL.""" + + +class TopReferrers(BaseModel): + """Top HTTP referrers by traffic.""" + + by_bandwidth: List[TopReferrersByBandwidth] = FieldInfo(alias="byBandwidth") + """Top HTTP referrers sorted by bandwidth utilized.""" + + by_requests: List[TopReferrersByRequest] = FieldInfo(alias="byRequests") + """Top HTTP referrers sorted by request count.""" + + +class TopUserAgentsByBandwidth(RequestBandwidthEntry): + name: str + """User agent string.""" + + +class TopUserAgentsByRequest(RequestBandwidthEntry): + name: str + """User agent string.""" + + +class TopUserAgents(BaseModel): + """Top user agents by traffic.""" + + by_bandwidth: List[TopUserAgentsByBandwidth] = FieldInfo(alias="byBandwidth") + """Top user agents sorted by bandwidth utilized.""" + + by_requests: List[TopUserAgentsByRequest] = FieldInfo(alias="byRequests") + """Top user agents sorted by request count.""" + + +class TopVideosByBandwidth(RequestBandwidthEntry): + name: str + """URL of the video asset.""" + + +class TopVideosByRequest(RequestBandwidthEntry): + name: str + """Full URL of the video asset (e.g. `https://ik.imagekit.io/demo/clip.mp4`).""" + + +class TopVideos(BaseModel): + """Top video assets by traffic.""" + + by_bandwidth: List[TopVideosByBandwidth] = FieldInfo(alias="byBandwidth") + """Top video assets sorted by bandwidth utilized.""" + + by_requests: List[TopVideosByRequest] = FieldInfo(alias="byRequests") + """Top video assets sorted by request count.""" + + +class TopVideoTransformsByBandwidth(RequestBandwidthEntry): + name: str + """Video transformation string (e.g. `tr:h-720,f-mp4`).""" + + +class TopVideoTransformsByRequest(RequestBandwidthEntry): + name: str + """Video transformation string (e.g. `tr:h-720,f-mp4`).""" + + +class TopVideoTransforms(BaseModel): + """Top video transformation strings by traffic.""" + + by_bandwidth: List[TopVideoTransformsByBandwidth] = FieldInfo(alias="byBandwidth") + """Top video transformation strings sorted by bandwidth utilized.""" + + by_requests: List[TopVideoTransformsByRequest] = FieldInfo(alias="byRequests") + """Top video transformation strings sorted by request count.""" + + +class URLEndpointsByBandwidth(RequestBandwidthEntry): + name: str + """ + URL endpoint name, or `Default` for traffic that does not match a named + endpoint. + """ + + +class URLEndpointsByRequest(RequestBandwidthEntry): + name: str + """ + URL endpoint name, or `Default` for traffic that does not match a named + endpoint. + """ + + +class URLEndpoints(BaseModel): + """CDN traffic grouped by configured URL endpoint. + + Traffic that does not match any named URL endpoint pattern is grouped under `Default`. + """ + + by_bandwidth: List[URLEndpointsByBandwidth] = FieldInfo(alias="byBandwidth") + """Top URL endpoints sorted by bandwidth utilized.""" + + by_requests: List[URLEndpointsByRequest] = FieldInfo(alias="byRequests") + """Top URL endpoints sorted by request count.""" + + +class VideoProcessing(BaseModel): + codec: str + """Video codec used for the output (e.g. `h264`, `av1`).""" + + duration_seconds: float = FieldInfo(alias="durationSeconds") + """Total output duration, in seconds, for this resolution and codec combination.""" + + resolution: str + """Output resolution tier (e.g. `SD`, `HD`, `4K`).""" + + +class UsageAnalyticsResponse(BaseModel): + bandwidth_bytes: float = FieldInfo(alias="bandwidthBytes") + """Total bandwidth, in bytes, utilized during the specified date range.""" + + browser: Browser + """CDN traffic grouped by browser.""" + + cache: Cache + """CDN cache hit, miss and error counts for the date range.""" + + country: Country + """CDN traffic grouped by country.""" + + device: Device + """CDN traffic grouped by device and operating system (e.g. + + `Desktop - Apple Mac`, `Smartphone - Apple iPhone`). + """ + + end_date: date = FieldInfo(alias="endDate") + """End date of the computed analytics data.""" + + error_reasons: List[ErrorReason] = FieldInfo(alias="errorReasons") + """Request count grouped by origin error reason. + + This covers failed origin fetches, such as an asset not found at origin or an + origin timeout. It is not the HTTP status code returned to the client, see + `statusCodes` for that. + """ + + extensions: List[Extension] + """Raw per-extension operation counts for the date range. + + These are raw operation counts, not billable extension units. For billable + usage, use the `/v1/accounts/usage` endpoint. + """ + + format: Format + """CDN traffic grouped by response `Content-Type`.""" + + generated_at: datetime = FieldInfo(alias="generatedAt") + """Date and time when the analytics data was computed. + + Use this to gauge how fresh the returned data is. The date and time is in + ISO8601 format. + """ + + request_count: float = FieldInfo(alias="requestCount") + """Total number of requests made during the specified date range.""" + + start_date: date = FieldInfo(alias="startDate") + """Start date of the computed analytics data.""" + + status_codes: List[StatusCode] = FieldInfo(alias="statusCodes") + """Request count grouped by HTTP status code.""" + + top404_assets: List[Top404Asset] = FieldInfo(alias="top404Assets") + """Top URLs that returned a 404 response.""" + + top_images: TopImages = FieldInfo(alias="topImages") + """Top image assets by traffic.""" + + top_image_transforms: TopImageTransforms = FieldInfo(alias="topImageTransforms") + """Top image transformation strings by traffic.""" + + top_other_assets: TopOtherAssets = FieldInfo(alias="topOtherAssets") + """Top non-image, non-video assets by traffic.""" + + top_referrers: TopReferrers = FieldInfo(alias="topReferrers") + """Top HTTP referrers by traffic.""" + + top_user_agents: TopUserAgents = FieldInfo(alias="topUserAgents") + """Top user agents by traffic.""" + + top_videos: TopVideos = FieldInfo(alias="topVideos") + """Top video assets by traffic.""" + + top_video_transforms: TopVideoTransforms = FieldInfo(alias="topVideoTransforms") + """Top video transformation strings by traffic.""" + + url_endpoints: URLEndpoints = FieldInfo(alias="urlEndpoints") + """CDN traffic grouped by configured URL endpoint. + + Traffic that does not match any named URL endpoint pattern is grouped under + `Default`. + """ + + video_processing: List[VideoProcessing] = FieldInfo(alias="videoProcessing") + """ + Raw observed video transcode output duration, in seconds, grouped by resolution + and codec. These are raw seconds, not billable Video Processing Units (VPU). For + billable VPU totals, use the `/v1/accounts/usage` endpoint. + """ + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] diff --git a/tests/api_resources/accounts/test_usage_analytics.py b/tests/api_resources/accounts/test_usage_analytics.py new file mode 100644 index 0000000..3881e5a --- /dev/null +++ b/tests/api_resources/accounts/test_usage_analytics.py @@ -0,0 +1,99 @@ +# 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 imagekitio import ImageKit, AsyncImageKit +from tests.utils import assert_matches_type +from imagekitio._utils import parse_date +from imagekitio.types.accounts import UsageAnalyticsResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestUsageAnalytics: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_get(self, client: ImageKit) -> None: + usage_analytics = client.accounts.usage_analytics.get( + end_date=parse_date("2019-12-27"), + start_date=parse_date("2019-12-27"), + ) + assert_matches_type(UsageAnalyticsResponse, usage_analytics, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_get(self, client: ImageKit) -> None: + response = client.accounts.usage_analytics.with_raw_response.get( + end_date=parse_date("2019-12-27"), + start_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage_analytics = response.parse() + assert_matches_type(UsageAnalyticsResponse, usage_analytics, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_get(self, client: ImageKit) -> None: + with client.accounts.usage_analytics.with_streaming_response.get( + end_date=parse_date("2019-12-27"), + start_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage_analytics = response.parse() + assert_matches_type(UsageAnalyticsResponse, usage_analytics, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncUsageAnalytics: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_get(self, async_client: AsyncImageKit) -> None: + usage_analytics = await async_client.accounts.usage_analytics.get( + end_date=parse_date("2019-12-27"), + start_date=parse_date("2019-12-27"), + ) + assert_matches_type(UsageAnalyticsResponse, usage_analytics, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_get(self, async_client: AsyncImageKit) -> None: + response = await async_client.accounts.usage_analytics.with_raw_response.get( + end_date=parse_date("2019-12-27"), + start_date=parse_date("2019-12-27"), + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + usage_analytics = await response.parse() + assert_matches_type(UsageAnalyticsResponse, usage_analytics, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_get(self, async_client: AsyncImageKit) -> None: + async with async_client.accounts.usage_analytics.with_streaming_response.get( + end_date=parse_date("2019-12-27"), + start_date=parse_date("2019-12-27"), + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + usage_analytics = await response.parse() + assert_matches_type(UsageAnalyticsResponse, usage_analytics, path=["response"]) + + assert cast(Any, response.is_closed) is True