From afc9a7340f3210de79d79b27e5ea9722ac5a5123 Mon Sep 17 00:00:00 2001
From: fern-api <115122769+fern-api[bot]@users.noreply.github.com>
Date: Fri, 12 Jun 2026 23:54:54 +0000
Subject: [PATCH 1/3] [fern-generated] Update SDK
Generated by Fern
CLI Version: unknown
Generators:
- fernapi/fern-python-sdk: 4.55.4
---
.fern/metadata.json | 2 +-
poetry.lock | 6 +-
pyproject.toml | 2 +-
reference.md | 145 ++++++++++++
src/square/__init__.py | 3 +
src/square/client.py | 19 ++
src/square/core/client_wrapper.py | 4 +-
src/square/reporting/__init__.py | 4 +
src/square/reporting/client.py | 196 ++++++++++++++++
src/square/reporting/raw_client.py | 216 ++++++++++++++++++
src/square/requests/cube.py | 31 +++
src/square/requests/cube_join.py | 8 +
src/square/requests/custom_numeric_format.py | 26 +++
src/square/requests/custom_time_format.py | 21 ++
src/square/requests/dimension.py | 39 ++++
src/square/requests/dimension_granularity.py | 12 +
src/square/requests/folder.py | 10 +
src/square/requests/format.py | 10 +
src/square/requests/format_description.py | 24 ++
src/square/requests/hierarchy.py | 17 ++
src/square/requests/join_subquery.py | 11 +
src/square/requests/link_format.py | 21 ++
src/square/requests/load_response.py | 16 ++
src/square/requests/load_result.py | 22 ++
src/square/requests/load_result_annotation.py | 13 ++
src/square/requests/load_result_data.py | 9 +
.../requests/load_result_data_columnar.py | 21 ++
.../requests/load_result_data_compact.py | 21 ++
src/square/requests/measure.py | 31 +++
src/square/requests/metadata_response.py | 12 +
src/square/requests/nested_folder.py | 10 +
src/square/requests/query.py | 35 +++
src/square/requests/query_filter.py | 9 +
src/square/requests/query_filter_and.py | 12 +
src/square/requests/query_filter_condition.py | 11 +
src/square/requests/query_filter_or.py | 12 +
src/square/requests/reporting_error.py | 11 +
src/square/requests/segment.py | 14 ++
src/square/requests/time_dimension.py | 14 ++
src/square/types/cache_mode.py | 7 +
src/square/types/cube.py | 45 ++++
src/square/types/cube_join.py | 21 ++
src/square/types/cube_type.py | 5 +
src/square/types/custom_numeric_format.py | 37 +++
src/square/types/custom_time_format.py | 32 +++
src/square/types/dimension.py | 58 +++++
src/square/types/dimension_granularity.py | 25 ++
src/square/types/dimension_order.py | 5 +
src/square/types/folder.py | 21 ++
src/square/types/format.py | 10 +
src/square/types/format_description.py | 37 +++
src/square/types/hierarchy.py | 32 +++
src/square/types/join_hint.py | 5 +
src/square/types/join_subquery.py | 25 ++
src/square/types/link_format.py | 32 +++
src/square/types/load_response.py | 34 +++
src/square/types/load_result.py | 36 +++
src/square/types/load_result_annotation.py | 27 +++
src/square/types/load_result_data.py | 9 +
src/square/types/load_result_data_columnar.py | 32 +++
src/square/types/load_result_data_compact.py | 32 +++
src/square/types/load_result_data_row.py | 5 +
src/square/types/measure.py | 52 +++++
src/square/types/metadata_response.py | 26 +++
src/square/types/nested_folder.py | 21 ++
src/square/types/query.py | 51 +++++
src/square/types/query_filter.py | 9 +
src/square/types/query_filter_and.py | 26 +++
src/square/types/query_filter_condition.py | 22 ++
src/square/types/query_filter_or.py | 26 +++
src/square/types/reporting_error.py | 24 ++
src/square/types/response_format.py | 5 +
src/square/types/segment.py | 26 +++
src/square/types/simple_format.py | 5 +
src/square/types/time_dimension.py | 28 +++
75 files changed, 1986 insertions(+), 7 deletions(-)
create mode 100644 src/square/reporting/__init__.py
create mode 100644 src/square/reporting/client.py
create mode 100644 src/square/reporting/raw_client.py
create mode 100644 src/square/requests/cube.py
create mode 100644 src/square/requests/cube_join.py
create mode 100644 src/square/requests/custom_numeric_format.py
create mode 100644 src/square/requests/custom_time_format.py
create mode 100644 src/square/requests/dimension.py
create mode 100644 src/square/requests/dimension_granularity.py
create mode 100644 src/square/requests/folder.py
create mode 100644 src/square/requests/format.py
create mode 100644 src/square/requests/format_description.py
create mode 100644 src/square/requests/hierarchy.py
create mode 100644 src/square/requests/join_subquery.py
create mode 100644 src/square/requests/link_format.py
create mode 100644 src/square/requests/load_response.py
create mode 100644 src/square/requests/load_result.py
create mode 100644 src/square/requests/load_result_annotation.py
create mode 100644 src/square/requests/load_result_data.py
create mode 100644 src/square/requests/load_result_data_columnar.py
create mode 100644 src/square/requests/load_result_data_compact.py
create mode 100644 src/square/requests/measure.py
create mode 100644 src/square/requests/metadata_response.py
create mode 100644 src/square/requests/nested_folder.py
create mode 100644 src/square/requests/query.py
create mode 100644 src/square/requests/query_filter.py
create mode 100644 src/square/requests/query_filter_and.py
create mode 100644 src/square/requests/query_filter_condition.py
create mode 100644 src/square/requests/query_filter_or.py
create mode 100644 src/square/requests/reporting_error.py
create mode 100644 src/square/requests/segment.py
create mode 100644 src/square/requests/time_dimension.py
create mode 100644 src/square/types/cache_mode.py
create mode 100644 src/square/types/cube.py
create mode 100644 src/square/types/cube_join.py
create mode 100644 src/square/types/cube_type.py
create mode 100644 src/square/types/custom_numeric_format.py
create mode 100644 src/square/types/custom_time_format.py
create mode 100644 src/square/types/dimension.py
create mode 100644 src/square/types/dimension_granularity.py
create mode 100644 src/square/types/dimension_order.py
create mode 100644 src/square/types/folder.py
create mode 100644 src/square/types/format.py
create mode 100644 src/square/types/format_description.py
create mode 100644 src/square/types/hierarchy.py
create mode 100644 src/square/types/join_hint.py
create mode 100644 src/square/types/join_subquery.py
create mode 100644 src/square/types/link_format.py
create mode 100644 src/square/types/load_response.py
create mode 100644 src/square/types/load_result.py
create mode 100644 src/square/types/load_result_annotation.py
create mode 100644 src/square/types/load_result_data.py
create mode 100644 src/square/types/load_result_data_columnar.py
create mode 100644 src/square/types/load_result_data_compact.py
create mode 100644 src/square/types/load_result_data_row.py
create mode 100644 src/square/types/measure.py
create mode 100644 src/square/types/metadata_response.py
create mode 100644 src/square/types/nested_folder.py
create mode 100644 src/square/types/query.py
create mode 100644 src/square/types/query_filter.py
create mode 100644 src/square/types/query_filter_and.py
create mode 100644 src/square/types/query_filter_condition.py
create mode 100644 src/square/types/query_filter_or.py
create mode 100644 src/square/types/reporting_error.py
create mode 100644 src/square/types/response_format.py
create mode 100644 src/square/types/segment.py
create mode 100644 src/square/types/simple_format.py
create mode 100644 src/square/types/time_dimension.py
diff --git a/.fern/metadata.json b/.fern/metadata.json
index f1041dfc..b3ec1e42 100644
--- a/.fern/metadata.json
+++ b/.fern/metadata.json
@@ -14,5 +14,5 @@
"use_typeddict_requests_for_file_upload": true,
"exclude_types_from_init_exports": true
},
- "sdkVersion": "44.1.0.20260520"
+ "sdkVersion": "44.2.0.20260520"
}
\ No newline at end of file
diff --git a/poetry.lock b/poetry.lock
index dfc85896..1f9536dc 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -38,13 +38,13 @@ trio = ["trio (>=0.26.1)"]
[[package]]
name = "certifi"
-version = "2026.4.22"
+version = "2026.5.20"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.7"
files = [
- {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"},
- {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"},
+ {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"},
+ {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"},
]
[[package]]
diff --git a/pyproject.toml b/pyproject.toml
index 98399848..37b24527 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ dynamic = ["version"]
[tool.poetry]
name = "squareup"
-version = "44.1.0.20260520"
+version = "44.2.0.20260520"
description = ""
readme = "README.md"
authors = []
diff --git a/reference.md b/reference.md
index d35be41d..4857a1df 100644
--- a/reference.md
+++ b/reference.md
@@ -18511,6 +18511,151 @@ information.
+
+
+
+
+## Reporting
+client.reporting.get_metadata() -> AsyncHttpResponse[MetadataResponse]
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Describes the data available to query: the cubes, views, measures, dimensions, and segments you can reference in a reporting query. Call this first to discover the schema, then pass the members you need to `load`.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from square import Square
+
+client = Square(
+ token="YOUR_TOKEN",
+)
+client.reporting.get_metadata()
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
+
+
+
+
+client.reporting.load(...) -> AsyncHttpResponse[LoadResponse]
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Runs a reporting query against the discovered schema and returns the aggregated results. Long-running queries may return a "Continue wait" response while processing — retry the same request until results are ready.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```python
+from square import Square
+
+client = Square(
+ token="YOUR_TOKEN",
+)
+client.reporting.load()
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**query_type:** `typing.Optional[str]`
+
+
+
+
+
+-
+
+**cache:** `typing.Optional[CacheMode]`
+
+
+
+
+
+-
+
+**query:** `typing.Optional[QueryParams]`
+
+
+
+
+
+-
+
+**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration.
+
+
+
+
+
+
+
diff --git a/src/square/__init__.py b/src/square/__init__.py
index 63ebc92b..fb397371 100644
--- a/src/square/__init__.py
+++ b/src/square/__init__.py
@@ -32,6 +32,7 @@
payments,
payouts,
refunds,
+ reporting,
sites,
snippets,
subscriptions,
@@ -74,6 +75,7 @@
"payments": ".payments",
"payouts": ".payouts",
"refunds": ".refunds",
+ "reporting": ".reporting",
"sites": ".sites",
"snippets": ".snippets",
"subscriptions": ".subscriptions",
@@ -137,6 +139,7 @@ def __dir__():
"payments",
"payouts",
"refunds",
+ "reporting",
"sites",
"snippets",
"subscriptions",
diff --git a/src/square/client.py b/src/square/client.py
index 4a0186d4..bbdcc76f 100644
--- a/src/square/client.py
+++ b/src/square/client.py
@@ -35,6 +35,7 @@
from .payments.client import AsyncPaymentsClient, PaymentsClient
from .payouts.client import AsyncPayoutsClient, PayoutsClient
from .refunds.client import AsyncRefundsClient, RefundsClient
+ from .reporting.client import AsyncReportingClient, ReportingClient
from .sites.client import AsyncSitesClient, SitesClient
from .snippets.client import AsyncSnippetsClient, SnippetsClient
from .subscriptions.client import AsyncSubscriptionsClient, SubscriptionsClient
@@ -148,6 +149,7 @@ def __init__(
self._terminal: typing.Optional[TerminalClient] = None
self._transfer_orders: typing.Optional[TransferOrdersClient] = None
self._vendors: typing.Optional[VendorsClient] = None
+ self._reporting: typing.Optional[ReportingClient] = None
self._cash_drawers: typing.Optional[CashDrawersClient] = None
self._webhooks: typing.Optional[WebhooksClient] = None
@@ -415,6 +417,14 @@ def vendors(self):
self._vendors = VendorsClient(client_wrapper=self._client_wrapper)
return self._vendors
+ @property
+ def reporting(self):
+ if self._reporting is None:
+ from .reporting.client import ReportingClient # noqa: E402
+
+ self._reporting = ReportingClient(client_wrapper=self._client_wrapper)
+ return self._reporting
+
@property
def cash_drawers(self):
if self._cash_drawers is None:
@@ -533,6 +543,7 @@ def __init__(
self._terminal: typing.Optional[AsyncTerminalClient] = None
self._transfer_orders: typing.Optional[AsyncTransferOrdersClient] = None
self._vendors: typing.Optional[AsyncVendorsClient] = None
+ self._reporting: typing.Optional[AsyncReportingClient] = None
self._cash_drawers: typing.Optional[AsyncCashDrawersClient] = None
self._webhooks: typing.Optional[AsyncWebhooksClient] = None
@@ -800,6 +811,14 @@ def vendors(self):
self._vendors = AsyncVendorsClient(client_wrapper=self._client_wrapper)
return self._vendors
+ @property
+ def reporting(self):
+ if self._reporting is None:
+ from .reporting.client import AsyncReportingClient # noqa: E402
+
+ self._reporting = AsyncReportingClient(client_wrapper=self._client_wrapper)
+ return self._reporting
+
@property
def cash_drawers(self):
if self._cash_drawers is None:
diff --git a/src/square/core/client_wrapper.py b/src/square/core/client_wrapper.py
index b4f6b9f0..a584dadd 100644
--- a/src/square/core/client_wrapper.py
+++ b/src/square/core/client_wrapper.py
@@ -26,12 +26,12 @@ def get_headers(self) -> typing.Dict[str, str]:
import platform
headers: typing.Dict[str, str] = {
- "User-Agent": "squareup/44.1.0.20260520",
+ "User-Agent": "squareup/44.2.0.20260520",
"X-Fern-Language": "Python",
"X-Fern-Runtime": f"python/{platform.python_version()}",
"X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}",
"X-Fern-SDK-Name": "squareup",
- "X-Fern-SDK-Version": "44.1.0.20260520",
+ "X-Fern-SDK-Version": "44.2.0.20260520",
**(self.get_custom_headers() or {}),
}
token = self._get_token()
diff --git a/src/square/reporting/__init__.py b/src/square/reporting/__init__.py
new file mode 100644
index 00000000..5cde0202
--- /dev/null
+++ b/src/square/reporting/__init__.py
@@ -0,0 +1,4 @@
+# This file was auto-generated by Fern from our API Definition.
+
+# isort: skip_file
+
diff --git a/src/square/reporting/client.py b/src/square/reporting/client.py
new file mode 100644
index 00000000..65567e6b
--- /dev/null
+++ b/src/square/reporting/client.py
@@ -0,0 +1,196 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ..core.request_options import RequestOptions
+from ..requests.query import QueryParams
+from ..types.cache_mode import CacheMode
+from ..types.load_response import LoadResponse
+from ..types.metadata_response import MetadataResponse
+from .raw_client import AsyncRawReportingClient, RawReportingClient
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class ReportingClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._raw_client = RawReportingClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> RawReportingClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ RawReportingClient
+ """
+ return self._raw_client
+
+ def get_metadata(self, *, request_options: typing.Optional[RequestOptions] = None) -> MetadataResponse:
+ """
+ Describes the data available to query: the cubes, views, measures, dimensions, and segments you can reference in a reporting query. Call this first to discover the schema, then pass the members you need to `load`.
+
+ Parameters
+ ----------
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ MetadataResponse
+ successful operation
+
+ Examples
+ --------
+ from square import Square
+
+ client = Square(
+ token="YOUR_TOKEN",
+ )
+ client.reporting.get_metadata()
+ """
+ _response = self._raw_client.get_metadata(request_options=request_options)
+ return _response.data
+
+ def load(
+ self,
+ *,
+ query_type: typing.Optional[str] = OMIT,
+ cache: typing.Optional[CacheMode] = OMIT,
+ query: typing.Optional[QueryParams] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> LoadResponse:
+ """
+ Runs a reporting query against the discovered schema and returns the aggregated results. Long-running queries may return a "Continue wait" response while processing — retry the same request until results are ready.
+
+ Parameters
+ ----------
+ query_type : typing.Optional[str]
+
+ cache : typing.Optional[CacheMode]
+
+ query : typing.Optional[QueryParams]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ LoadResponse
+ successful operation
+
+ Examples
+ --------
+ from square import Square
+
+ client = Square(
+ token="YOUR_TOKEN",
+ )
+ client.reporting.load()
+ """
+ _response = self._raw_client.load(
+ query_type=query_type, cache=cache, query=query, request_options=request_options
+ )
+ return _response.data
+
+
+class AsyncReportingClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._raw_client = AsyncRawReportingClient(client_wrapper=client_wrapper)
+
+ @property
+ def with_raw_response(self) -> AsyncRawReportingClient:
+ """
+ Retrieves a raw implementation of this client that returns raw responses.
+
+ Returns
+ -------
+ AsyncRawReportingClient
+ """
+ return self._raw_client
+
+ async def get_metadata(self, *, request_options: typing.Optional[RequestOptions] = None) -> MetadataResponse:
+ """
+ Describes the data available to query: the cubes, views, measures, dimensions, and segments you can reference in a reporting query. Call this first to discover the schema, then pass the members you need to `load`.
+
+ Parameters
+ ----------
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ MetadataResponse
+ successful operation
+
+ Examples
+ --------
+ import asyncio
+
+ from square import AsyncSquare
+
+ client = AsyncSquare(
+ token="YOUR_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.reporting.get_metadata()
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.get_metadata(request_options=request_options)
+ return _response.data
+
+ async def load(
+ self,
+ *,
+ query_type: typing.Optional[str] = OMIT,
+ cache: typing.Optional[CacheMode] = OMIT,
+ query: typing.Optional[QueryParams] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> LoadResponse:
+ """
+ Runs a reporting query against the discovered schema and returns the aggregated results. Long-running queries may return a "Continue wait" response while processing — retry the same request until results are ready.
+
+ Parameters
+ ----------
+ query_type : typing.Optional[str]
+
+ cache : typing.Optional[CacheMode]
+
+ query : typing.Optional[QueryParams]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ LoadResponse
+ successful operation
+
+ Examples
+ --------
+ import asyncio
+
+ from square import AsyncSquare
+
+ client = AsyncSquare(
+ token="YOUR_TOKEN",
+ )
+
+
+ async def main() -> None:
+ await client.reporting.load()
+
+
+ asyncio.run(main())
+ """
+ _response = await self._raw_client.load(
+ query_type=query_type, cache=cache, query=query, request_options=request_options
+ )
+ return _response.data
diff --git a/src/square/reporting/raw_client.py b/src/square/reporting/raw_client.py
new file mode 100644
index 00000000..d1b7d3b5
--- /dev/null
+++ b/src/square/reporting/raw_client.py
@@ -0,0 +1,216 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+from json.decoder import JSONDecodeError
+
+from ..core.api_error import ApiError
+from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
+from ..core.http_response import AsyncHttpResponse, HttpResponse
+from ..core.request_options import RequestOptions
+from ..core.serialization import convert_and_respect_annotation_metadata
+from ..core.unchecked_base_model import construct_type
+from ..requests.query import QueryParams
+from ..types.cache_mode import CacheMode
+from ..types.load_response import LoadResponse
+from ..types.metadata_response import MetadataResponse
+
+# this is used as the default value for optional parameters
+OMIT = typing.cast(typing.Any, ...)
+
+
+class RawReportingClient:
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ def get_metadata(
+ self, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> HttpResponse[MetadataResponse]:
+ """
+ Describes the data available to query: the cubes, views, measures, dimensions, and segments you can reference in a reporting query. Call this first to discover the schema, then pass the members you need to `load`.
+
+ Parameters
+ ----------
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[MetadataResponse]
+ successful operation
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ "reporting/v1/meta",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ MetadataResponse,
+ construct_type(
+ type_=MetadataResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ def load(
+ self,
+ *,
+ query_type: typing.Optional[str] = OMIT,
+ cache: typing.Optional[CacheMode] = OMIT,
+ query: typing.Optional[QueryParams] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> HttpResponse[LoadResponse]:
+ """
+ Runs a reporting query against the discovered schema and returns the aggregated results. Long-running queries may return a "Continue wait" response while processing — retry the same request until results are ready.
+
+ Parameters
+ ----------
+ query_type : typing.Optional[str]
+
+ cache : typing.Optional[CacheMode]
+
+ query : typing.Optional[QueryParams]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ HttpResponse[LoadResponse]
+ successful operation
+ """
+ _response = self._client_wrapper.httpx_client.request(
+ "reporting/v1/load",
+ method="POST",
+ json={
+ "queryType": query_type,
+ "cache": cache,
+ "query": convert_and_respect_annotation_metadata(
+ object_=query, annotation=QueryParams, direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ LoadResponse,
+ construct_type(
+ type_=LoadResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return HttpResponse(response=_response, data=_data)
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+
+class AsyncRawReportingClient:
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
+ self._client_wrapper = client_wrapper
+
+ async def get_metadata(
+ self, *, request_options: typing.Optional[RequestOptions] = None
+ ) -> AsyncHttpResponse[MetadataResponse]:
+ """
+ Describes the data available to query: the cubes, views, measures, dimensions, and segments you can reference in a reporting query. Call this first to discover the schema, then pass the members you need to `load`.
+
+ Parameters
+ ----------
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[MetadataResponse]
+ successful operation
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ "reporting/v1/meta",
+ method="GET",
+ request_options=request_options,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ MetadataResponse,
+ construct_type(
+ type_=MetadataResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
+
+ async def load(
+ self,
+ *,
+ query_type: typing.Optional[str] = OMIT,
+ cache: typing.Optional[CacheMode] = OMIT,
+ query: typing.Optional[QueryParams] = OMIT,
+ request_options: typing.Optional[RequestOptions] = None,
+ ) -> AsyncHttpResponse[LoadResponse]:
+ """
+ Runs a reporting query against the discovered schema and returns the aggregated results. Long-running queries may return a "Continue wait" response while processing — retry the same request until results are ready.
+
+ Parameters
+ ----------
+ query_type : typing.Optional[str]
+
+ cache : typing.Optional[CacheMode]
+
+ query : typing.Optional[QueryParams]
+
+ request_options : typing.Optional[RequestOptions]
+ Request-specific configuration.
+
+ Returns
+ -------
+ AsyncHttpResponse[LoadResponse]
+ successful operation
+ """
+ _response = await self._client_wrapper.httpx_client.request(
+ "reporting/v1/load",
+ method="POST",
+ json={
+ "queryType": query_type,
+ "cache": cache,
+ "query": convert_and_respect_annotation_metadata(
+ object_=query, annotation=QueryParams, direction="write"
+ ),
+ },
+ headers={
+ "content-type": "application/json",
+ },
+ request_options=request_options,
+ omit=OMIT,
+ )
+ try:
+ if 200 <= _response.status_code < 300:
+ _data = typing.cast(
+ LoadResponse,
+ construct_type(
+ type_=LoadResponse, # type: ignore
+ object_=_response.json(),
+ ),
+ )
+ return AsyncHttpResponse(response=_response, data=_data)
+ _response_json = _response.json()
+ except JSONDecodeError:
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
diff --git a/src/square/requests/cube.py b/src/square/requests/cube.py
new file mode 100644
index 00000000..8936da33
--- /dev/null
+++ b/src/square/requests/cube.py
@@ -0,0 +1,31 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+from ..types.cube_type import CubeType
+from .cube_join import CubeJoinParams
+from .dimension import DimensionParams
+from .folder import FolderParams
+from .hierarchy import HierarchyParams
+from .measure import MeasureParams
+from .nested_folder import NestedFolderParams
+from .segment import SegmentParams
+
+
+class CubeParams(typing_extensions.TypedDict):
+ name: str
+ title: typing_extensions.NotRequired[str]
+ type: CubeType
+ meta: typing_extensions.NotRequired[typing.Dict[str, typing.Any]]
+ description: typing_extensions.NotRequired[str]
+ measures: typing.Sequence[MeasureParams]
+ dimensions: typing.Sequence[DimensionParams]
+ segments: typing.Sequence[SegmentParams]
+ joins: typing_extensions.NotRequired[typing.Sequence[CubeJoinParams]]
+ folders: typing_extensions.NotRequired[typing.Sequence[FolderParams]]
+ nested_folders: typing_extensions.NotRequired[
+ typing_extensions.Annotated[typing.Sequence[NestedFolderParams], FieldMetadata(alias="nestedFolders")]
+ ]
+ hierarchies: typing_extensions.NotRequired[typing.Sequence[HierarchyParams]]
diff --git a/src/square/requests/cube_join.py b/src/square/requests/cube_join.py
new file mode 100644
index 00000000..d84a9587
--- /dev/null
+++ b/src/square/requests/cube_join.py
@@ -0,0 +1,8 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing_extensions
+
+
+class CubeJoinParams(typing_extensions.TypedDict):
+ name: str
+ relationship: str
diff --git a/src/square/requests/custom_numeric_format.py b/src/square/requests/custom_numeric_format.py
new file mode 100644
index 00000000..15b8123f
--- /dev/null
+++ b/src/square/requests/custom_numeric_format.py
@@ -0,0 +1,26 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+
+
+class CustomNumericFormatParams(typing_extensions.TypedDict):
+ """
+ Custom numeric format for numeric measures and dimensions
+ """
+
+ type: typing.Literal["custom-numeric"]
+ """
+ Type of the format (must be 'custom-numeric')
+ """
+
+ value: str
+ """
+ d3-format specifier string (e.g., '.2f', ',.0f', '$,.2f', '.0%', '.2s'). See https://d3js.org/d3-format
+ """
+
+ alias: typing_extensions.NotRequired[str]
+ """
+ Name of the predefined format (e.g., 'percent_2', 'currency_1'). Present only when a named format was used.
+ """
diff --git a/src/square/requests/custom_time_format.py b/src/square/requests/custom_time_format.py
new file mode 100644
index 00000000..bba2872f
--- /dev/null
+++ b/src/square/requests/custom_time_format.py
@@ -0,0 +1,21 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+
+
+class CustomTimeFormatParams(typing_extensions.TypedDict):
+ """
+ Custom time format for time dimensions
+ """
+
+ type: typing.Literal["custom-time"]
+ """
+ Type of the format (must be 'custom-time')
+ """
+
+ value: str
+ """
+ POSIX strftime format string (IEEE Std 1003.1 / POSIX.1) with d3-time-format extensions (e.g., '%Y-%m-%d', '%d/%m/%Y %H:%M:%S'). See https://pubs.opengroup.org/onlinepubs/009695399/functions/strptime.html and https://d3js.org/d3-time-format
+ """
diff --git a/src/square/requests/dimension.py b/src/square/requests/dimension.py
new file mode 100644
index 00000000..d9037136
--- /dev/null
+++ b/src/square/requests/dimension.py
@@ -0,0 +1,39 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+from ..types.dimension_order import DimensionOrder
+from .dimension_granularity import DimensionGranularityParams
+from .format import FormatParams
+from .format_description import FormatDescriptionParams
+
+
+class DimensionParams(typing_extensions.TypedDict):
+ name: str
+ title: typing_extensions.NotRequired[str]
+ short_title: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="shortTitle")]]
+ description: typing_extensions.NotRequired[str]
+ type: str
+ alias_member: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="aliasMember")]]
+ """
+ When dimension is defined in View, it keeps the original path: Cube.dimension
+ """
+
+ granularities: typing_extensions.NotRequired[typing.Sequence[DimensionGranularityParams]]
+ meta: typing_extensions.NotRequired[typing.Dict[str, typing.Any]]
+ format: typing_extensions.NotRequired[FormatParams]
+ format_description: typing_extensions.NotRequired[
+ typing_extensions.Annotated[FormatDescriptionParams, FieldMetadata(alias="formatDescription")]
+ ]
+ currency: typing_extensions.NotRequired[str]
+ """
+ ISO 4217 currency code in uppercase (3 characters, e.g. USD, EUR)
+ """
+
+ order: typing_extensions.NotRequired[DimensionOrder]
+ key: typing_extensions.NotRequired[str]
+ """
+ Key reference for the dimension
+ """
diff --git a/src/square/requests/dimension_granularity.py b/src/square/requests/dimension_granularity.py
new file mode 100644
index 00000000..f8c0b20a
--- /dev/null
+++ b/src/square/requests/dimension_granularity.py
@@ -0,0 +1,12 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing_extensions
+
+
+class DimensionGranularityParams(typing_extensions.TypedDict):
+ name: str
+ title: str
+ interval: typing_extensions.NotRequired[str]
+ sql: typing_extensions.NotRequired[str]
+ offset: typing_extensions.NotRequired[str]
+ origin: typing_extensions.NotRequired[str]
diff --git a/src/square/requests/folder.py b/src/square/requests/folder.py
new file mode 100644
index 00000000..3e6f4e75
--- /dev/null
+++ b/src/square/requests/folder.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+
+
+class FolderParams(typing_extensions.TypedDict):
+ name: str
+ members: typing.Sequence[str]
diff --git a/src/square/requests/format.py b/src/square/requests/format.py
new file mode 100644
index 00000000..972e38a0
--- /dev/null
+++ b/src/square/requests/format.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ..types.simple_format import SimpleFormat
+from .custom_numeric_format import CustomNumericFormatParams
+from .custom_time_format import CustomTimeFormatParams
+from .link_format import LinkFormatParams
+
+FormatParams = typing.Union[SimpleFormat, LinkFormatParams, CustomTimeFormatParams, CustomNumericFormatParams]
diff --git a/src/square/requests/format_description.py b/src/square/requests/format_description.py
new file mode 100644
index 00000000..6a91180d
--- /dev/null
+++ b/src/square/requests/format_description.py
@@ -0,0 +1,24 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing_extensions
+
+
+class FormatDescriptionParams(typing_extensions.TypedDict):
+ """
+ Resolved format description with the predefined name and d3-format specifier
+ """
+
+ name: str
+ """
+ Predefined format name (e.g., 'percent_2', 'currency_1') or a base name like 'number', or 'custom' for ad-hoc specifiers
+ """
+
+ specifier: str
+ """
+ d3-format specifier string (e.g., '.2f', ',.0f', '$,.2f'). See https://d3js.org/d3-format
+ """
+
+ currency: typing_extensions.NotRequired[str]
+ """
+ ISO 4217 currency code in uppercase (e.g. USD, EUR). Present when a currency format is used.
+ """
diff --git a/src/square/requests/hierarchy.py b/src/square/requests/hierarchy.py
new file mode 100644
index 00000000..4365b8de
--- /dev/null
+++ b/src/square/requests/hierarchy.py
@@ -0,0 +1,17 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+
+
+class HierarchyParams(typing_extensions.TypedDict):
+ name: str
+ alias_member: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="aliasMember")]]
+ """
+ When hierarchy is defined in Cube, it keeps the original path: Cube.hierarchy
+ """
+
+ title: typing_extensions.NotRequired[str]
+ levels: typing.Sequence[str]
diff --git a/src/square/requests/join_subquery.py b/src/square/requests/join_subquery.py
new file mode 100644
index 00000000..8d476d98
--- /dev/null
+++ b/src/square/requests/join_subquery.py
@@ -0,0 +1,11 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+
+
+class JoinSubqueryParams(typing_extensions.TypedDict):
+ sql: str
+ on: str
+ join_type: typing_extensions.Annotated[str, FieldMetadata(alias="joinType")]
+ alias: str
diff --git a/src/square/requests/link_format.py b/src/square/requests/link_format.py
new file mode 100644
index 00000000..61cc7f39
--- /dev/null
+++ b/src/square/requests/link_format.py
@@ -0,0 +1,21 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+
+
+class LinkFormatParams(typing_extensions.TypedDict):
+ """
+ Link format with label and type
+ """
+
+ label: str
+ """
+ Label for the link
+ """
+
+ type: typing.Literal["link"]
+ """
+ Type of the format (must be 'link')
+ """
diff --git a/src/square/requests/load_response.py b/src/square/requests/load_response.py
new file mode 100644
index 00000000..96eba7ee
--- /dev/null
+++ b/src/square/requests/load_response.py
@@ -0,0 +1,16 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+from .load_result import LoadResultParams
+
+
+class LoadResponseParams(typing_extensions.TypedDict):
+ pivot_query: typing_extensions.NotRequired[
+ typing_extensions.Annotated[typing.Dict[str, typing.Any], FieldMetadata(alias="pivotQuery")]
+ ]
+ slow_query: typing_extensions.NotRequired[typing_extensions.Annotated[bool, FieldMetadata(alias="slowQuery")]]
+ query_type: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="queryType")]]
+ results: typing.Sequence[LoadResultParams]
diff --git a/src/square/requests/load_result.py b/src/square/requests/load_result.py
new file mode 100644
index 00000000..eec1375a
--- /dev/null
+++ b/src/square/requests/load_result.py
@@ -0,0 +1,22 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+from .load_result_annotation import LoadResultAnnotationParams
+from .load_result_data import LoadResultDataParams
+
+
+class LoadResultParams(typing_extensions.TypedDict):
+ data_source: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="dataSource")]]
+ annotation: LoadResultAnnotationParams
+ data: LoadResultDataParams
+ refresh_key_values: typing_extensions.NotRequired[
+ typing_extensions.Annotated[
+ typing.Sequence[typing.Dict[str, typing.Any]], FieldMetadata(alias="refreshKeyValues")
+ ]
+ ]
+ last_refresh_time: typing_extensions.NotRequired[
+ typing_extensions.Annotated[str, FieldMetadata(alias="lastRefreshTime")]
+ ]
diff --git a/src/square/requests/load_result_annotation.py b/src/square/requests/load_result_annotation.py
new file mode 100644
index 00000000..5389dd86
--- /dev/null
+++ b/src/square/requests/load_result_annotation.py
@@ -0,0 +1,13 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+
+
+class LoadResultAnnotationParams(typing_extensions.TypedDict):
+ measures: typing.Dict[str, typing.Any]
+ dimensions: typing.Dict[str, typing.Any]
+ segments: typing.Dict[str, typing.Any]
+ time_dimensions: typing_extensions.Annotated[typing.Dict[str, typing.Any], FieldMetadata(alias="timeDimensions")]
diff --git a/src/square/requests/load_result_data.py b/src/square/requests/load_result_data.py
new file mode 100644
index 00000000..98b84717
--- /dev/null
+++ b/src/square/requests/load_result_data.py
@@ -0,0 +1,9 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from ..types.load_result_data_row import LoadResultDataRow
+from .load_result_data_columnar import LoadResultDataColumnarParams
+from .load_result_data_compact import LoadResultDataCompactParams
+
+LoadResultDataParams = typing.Union[LoadResultDataRow, LoadResultDataCompactParams, LoadResultDataColumnarParams]
diff --git a/src/square/requests/load_result_data_columnar.py b/src/square/requests/load_result_data_columnar.py
new file mode 100644
index 00000000..f5507f95
--- /dev/null
+++ b/src/square/requests/load_result_data_columnar.py
@@ -0,0 +1,21 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+
+
+class LoadResultDataColumnarParams(typing_extensions.TypedDict):
+ """
+ Columnar data format - members list paired with one primitive array per column. Returned when `responseFormat=columnar` is requested.
+ """
+
+ members: typing.Sequence[str]
+ """
+ Ordered list of member names. Element `i` of `columns` holds the values for `members[i]` across all rows.
+ """
+
+ columns: typing.Sequence[typing.Sequence[typing.Any]]
+ """
+ One array per member, in the same order as `members`. Each inner array contains the primitive value of that member for every row (null, boolean, number, string).
+ """
diff --git a/src/square/requests/load_result_data_compact.py b/src/square/requests/load_result_data_compact.py
new file mode 100644
index 00000000..1eec025e
--- /dev/null
+++ b/src/square/requests/load_result_data_compact.py
@@ -0,0 +1,21 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+
+
+class LoadResultDataCompactParams(typing_extensions.TypedDict):
+ """
+ Compact data format - a single object with the members list and a dataset of primitive arrays. Returned when `responseFormat=compact` is requested.
+ """
+
+ members: typing.Sequence[str]
+ """
+ Ordered list of member names that correspond to each cell position in `dataset` rows.
+ """
+
+ dataset: typing.Sequence[typing.Sequence[typing.Any]]
+ """
+ Array of rows, where each row is an array of primitive values (null, boolean, number, string) aligned with `members`.
+ """
diff --git a/src/square/requests/measure.py b/src/square/requests/measure.py
new file mode 100644
index 00000000..e1fb6c1f
--- /dev/null
+++ b/src/square/requests/measure.py
@@ -0,0 +1,31 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+from .format import FormatParams
+from .format_description import FormatDescriptionParams
+
+
+class MeasureParams(typing_extensions.TypedDict):
+ name: str
+ title: typing_extensions.NotRequired[str]
+ short_title: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="shortTitle")]]
+ description: typing_extensions.NotRequired[str]
+ type: str
+ agg_type: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="aggType")]]
+ meta: typing_extensions.NotRequired[typing.Dict[str, typing.Any]]
+ format: typing_extensions.NotRequired[FormatParams]
+ format_description: typing_extensions.NotRequired[
+ typing_extensions.Annotated[FormatDescriptionParams, FieldMetadata(alias="formatDescription")]
+ ]
+ currency: typing_extensions.NotRequired[str]
+ """
+ ISO 4217 currency code in uppercase (3 characters, e.g. USD, EUR)
+ """
+
+ alias_member: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="aliasMember")]]
+ """
+ When measure is defined in View, it keeps the original path: Cube.measure
+ """
diff --git a/src/square/requests/metadata_response.py b/src/square/requests/metadata_response.py
new file mode 100644
index 00000000..e72e9595
--- /dev/null
+++ b/src/square/requests/metadata_response.py
@@ -0,0 +1,12 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+from .cube import CubeParams
+
+
+class MetadataResponseParams(typing_extensions.TypedDict):
+ cubes: typing_extensions.NotRequired[typing.Sequence[CubeParams]]
+ compiler_id: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="compilerId")]]
diff --git a/src/square/requests/nested_folder.py b/src/square/requests/nested_folder.py
new file mode 100644
index 00000000..9d269c07
--- /dev/null
+++ b/src/square/requests/nested_folder.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+
+
+class NestedFolderParams(typing_extensions.TypedDict):
+ name: str
+ members: typing.Sequence[str]
diff --git a/src/square/requests/query.py b/src/square/requests/query.py
new file mode 100644
index 00000000..59219296
--- /dev/null
+++ b/src/square/requests/query.py
@@ -0,0 +1,35 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+from ..types.join_hint import JoinHint
+from ..types.response_format import ResponseFormat
+from .join_subquery import JoinSubqueryParams
+from .query_filter import QueryFilterParams
+from .time_dimension import TimeDimensionParams
+
+
+class QueryParams(typing_extensions.TypedDict):
+ measures: typing_extensions.NotRequired[typing.Sequence[str]]
+ dimensions: typing_extensions.NotRequired[typing.Sequence[str]]
+ segments: typing_extensions.NotRequired[typing.Sequence[str]]
+ time_dimensions: typing_extensions.NotRequired[
+ typing_extensions.Annotated[typing.Sequence[TimeDimensionParams], FieldMetadata(alias="timeDimensions")]
+ ]
+ order: typing_extensions.NotRequired[typing.Sequence[typing.Sequence[str]]]
+ limit: typing_extensions.NotRequired[int]
+ offset: typing_extensions.NotRequired[int]
+ filters: typing_extensions.NotRequired[typing.Sequence[QueryFilterParams]]
+ ungrouped: typing_extensions.NotRequired[bool]
+ subquery_joins: typing_extensions.NotRequired[
+ typing_extensions.Annotated[typing.Sequence[JoinSubqueryParams], FieldMetadata(alias="subqueryJoins")]
+ ]
+ join_hints: typing_extensions.NotRequired[
+ typing_extensions.Annotated[typing.Sequence[JoinHint], FieldMetadata(alias="joinHints")]
+ ]
+ timezone: typing_extensions.NotRequired[str]
+ response_format: typing_extensions.NotRequired[
+ typing_extensions.Annotated[ResponseFormat, FieldMetadata(alias="responseFormat")]
+ ]
diff --git a/src/square/requests/query_filter.py b/src/square/requests/query_filter.py
new file mode 100644
index 00000000..39f89f0f
--- /dev/null
+++ b/src/square/requests/query_filter.py
@@ -0,0 +1,9 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .query_filter_and import QueryFilterAndParams
+from .query_filter_condition import QueryFilterConditionParams
+from .query_filter_or import QueryFilterOrParams
+
+QueryFilterParams = typing.Union[QueryFilterConditionParams, QueryFilterOrParams, QueryFilterAndParams]
diff --git a/src/square/requests/query_filter_and.py b/src/square/requests/query_filter_and.py
new file mode 100644
index 00000000..0fec90ae
--- /dev/null
+++ b/src/square/requests/query_filter_and.py
@@ -0,0 +1,12 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+
+
+class QueryFilterAndParams(typing_extensions.TypedDict):
+ and_: typing_extensions.NotRequired[
+ typing_extensions.Annotated[typing.Sequence[typing.Dict[str, typing.Any]], FieldMetadata(alias="and")]
+ ]
diff --git a/src/square/requests/query_filter_condition.py b/src/square/requests/query_filter_condition.py
new file mode 100644
index 00000000..45f27907
--- /dev/null
+++ b/src/square/requests/query_filter_condition.py
@@ -0,0 +1,11 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+
+
+class QueryFilterConditionParams(typing_extensions.TypedDict):
+ member: typing_extensions.NotRequired[str]
+ operator: typing_extensions.NotRequired[str]
+ values: typing_extensions.NotRequired[typing.Sequence[str]]
diff --git a/src/square/requests/query_filter_or.py b/src/square/requests/query_filter_or.py
new file mode 100644
index 00000000..0cb5bfdc
--- /dev/null
+++ b/src/square/requests/query_filter_or.py
@@ -0,0 +1,12 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+
+
+class QueryFilterOrParams(typing_extensions.TypedDict):
+ or_: typing_extensions.NotRequired[
+ typing_extensions.Annotated[typing.Sequence[typing.Dict[str, typing.Any]], FieldMetadata(alias="or")]
+ ]
diff --git a/src/square/requests/reporting_error.py b/src/square/requests/reporting_error.py
new file mode 100644
index 00000000..bc987547
--- /dev/null
+++ b/src/square/requests/reporting_error.py
@@ -0,0 +1,11 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing_extensions
+
+
+class ReportingErrorParams(typing_extensions.TypedDict):
+ """
+ Error envelope returned by the Reporting API. Note: a 200 response whose body is `{ "error": "Continue wait" }` is not a failure — it signals that a long-running query is still processing and the request should be retried.
+ """
+
+ error: str
diff --git a/src/square/requests/segment.py b/src/square/requests/segment.py
new file mode 100644
index 00000000..a2f143f4
--- /dev/null
+++ b/src/square/requests/segment.py
@@ -0,0 +1,14 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+
+
+class SegmentParams(typing_extensions.TypedDict):
+ name: str
+ title: str
+ description: typing_extensions.NotRequired[str]
+ short_title: typing_extensions.Annotated[str, FieldMetadata(alias="shortTitle")]
+ meta: typing_extensions.NotRequired[typing.Dict[str, typing.Any]]
diff --git a/src/square/requests/time_dimension.py b/src/square/requests/time_dimension.py
new file mode 100644
index 00000000..71f03167
--- /dev/null
+++ b/src/square/requests/time_dimension.py
@@ -0,0 +1,14 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import typing_extensions
+from ..core.serialization import FieldMetadata
+
+
+class TimeDimensionParams(typing_extensions.TypedDict):
+ dimension: str
+ granularity: typing_extensions.NotRequired[str]
+ date_range: typing_extensions.NotRequired[
+ typing_extensions.Annotated[typing.Dict[str, typing.Any], FieldMetadata(alias="dateRange")]
+ ]
diff --git a/src/square/types/cache_mode.py b/src/square/types/cache_mode.py
new file mode 100644
index 00000000..76c99ce0
--- /dev/null
+++ b/src/square/types/cache_mode.py
@@ -0,0 +1,7 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+CacheMode = typing.Union[
+ typing.Literal["stale-if-slow", "stale-while-revalidate", "must-revalidate", "no-cache"], typing.Any
+]
diff --git a/src/square/types/cube.py b/src/square/types/cube.py
new file mode 100644
index 00000000..5b9c9f2b
--- /dev/null
+++ b/src/square/types/cube.py
@@ -0,0 +1,45 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+from .cube_join import CubeJoin
+from .cube_type import CubeType
+from .dimension import Dimension
+from .folder import Folder
+from .hierarchy import Hierarchy
+from .measure import Measure
+from .nested_folder import NestedFolder
+from .segment import Segment
+
+
+class Cube(UncheckedBaseModel):
+ name: str
+ title: typing.Optional[str] = None
+ type: CubeType
+ meta: typing.Optional[typing.Dict[str, typing.Any]] = None
+ description: typing.Optional[str] = None
+ measures: typing.List[Measure]
+ dimensions: typing.List[Dimension]
+ segments: typing.List[Segment]
+ joins: typing.Optional[typing.List[CubeJoin]] = None
+ folders: typing.Optional[typing.List[Folder]] = None
+ nested_folders: typing_extensions.Annotated[
+ typing.Optional[typing.List[NestedFolder]],
+ FieldMetadata(alias="nestedFolders"),
+ pydantic.Field(alias="nestedFolders"),
+ ] = None
+ hierarchies: typing.Optional[typing.List[Hierarchy]] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/cube_join.py b/src/square/types/cube_join.py
new file mode 100644
index 00000000..48196606
--- /dev/null
+++ b/src/square/types/cube_join.py
@@ -0,0 +1,21 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class CubeJoin(UncheckedBaseModel):
+ name: str
+ relationship: str
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/cube_type.py b/src/square/types/cube_type.py
new file mode 100644
index 00000000..e771d193
--- /dev/null
+++ b/src/square/types/cube_type.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+CubeType = typing.Union[typing.Literal["cube", "view"], typing.Any]
diff --git a/src/square/types/custom_numeric_format.py b/src/square/types/custom_numeric_format.py
new file mode 100644
index 00000000..e3d7a269
--- /dev/null
+++ b/src/square/types/custom_numeric_format.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class CustomNumericFormat(UncheckedBaseModel):
+ """
+ Custom numeric format for numeric measures and dimensions
+ """
+
+ type: typing.Literal["custom-numeric"] = pydantic.Field(default="custom-numeric")
+ """
+ Type of the format (must be 'custom-numeric')
+ """
+
+ value: str = pydantic.Field()
+ """
+ d3-format specifier string (e.g., '.2f', ',.0f', '$,.2f', '.0%', '.2s'). See https://d3js.org/d3-format
+ """
+
+ alias: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Name of the predefined format (e.g., 'percent_2', 'currency_1'). Present only when a named format was used.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/custom_time_format.py b/src/square/types/custom_time_format.py
new file mode 100644
index 00000000..409da8f7
--- /dev/null
+++ b/src/square/types/custom_time_format.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class CustomTimeFormat(UncheckedBaseModel):
+ """
+ Custom time format for time dimensions
+ """
+
+ type: typing.Literal["custom-time"] = pydantic.Field(default="custom-time")
+ """
+ Type of the format (must be 'custom-time')
+ """
+
+ value: str = pydantic.Field()
+ """
+ POSIX strftime format string (IEEE Std 1003.1 / POSIX.1) with d3-time-format extensions (e.g., '%Y-%m-%d', '%d/%m/%Y %H:%M:%S'). See https://pubs.opengroup.org/onlinepubs/009695399/functions/strptime.html and https://d3js.org/d3-time-format
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/dimension.py b/src/square/types/dimension.py
new file mode 100644
index 00000000..a4a6e999
--- /dev/null
+++ b/src/square/types/dimension.py
@@ -0,0 +1,58 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+from .dimension_granularity import DimensionGranularity
+from .dimension_order import DimensionOrder
+from .format import Format
+from .format_description import FormatDescription
+
+
+class Dimension(UncheckedBaseModel):
+ name: str
+ title: typing.Optional[str] = None
+ short_title: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="shortTitle"), pydantic.Field(alias="shortTitle")
+ ] = None
+ description: typing.Optional[str] = None
+ type: str
+ alias_member: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="aliasMember"),
+ pydantic.Field(
+ alias="aliasMember",
+ description="When dimension is defined in View, it keeps the original path: Cube.dimension",
+ ),
+ ] = None
+ granularities: typing.Optional[typing.List[DimensionGranularity]] = None
+ meta: typing.Optional[typing.Dict[str, typing.Any]] = None
+ format: typing.Optional[Format] = None
+ format_description: typing_extensions.Annotated[
+ typing.Optional[FormatDescription],
+ FieldMetadata(alias="formatDescription"),
+ pydantic.Field(alias="formatDescription"),
+ ] = None
+ currency: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ ISO 4217 currency code in uppercase (3 characters, e.g. USD, EUR)
+ """
+
+ order: typing.Optional[DimensionOrder] = None
+ key: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ Key reference for the dimension
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/dimension_granularity.py b/src/square/types/dimension_granularity.py
new file mode 100644
index 00000000..236d9a82
--- /dev/null
+++ b/src/square/types/dimension_granularity.py
@@ -0,0 +1,25 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class DimensionGranularity(UncheckedBaseModel):
+ name: str
+ title: str
+ interval: typing.Optional[str] = None
+ sql: typing.Optional[str] = None
+ offset: typing.Optional[str] = None
+ origin: typing.Optional[str] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/dimension_order.py b/src/square/types/dimension_order.py
new file mode 100644
index 00000000..4062fa7a
--- /dev/null
+++ b/src/square/types/dimension_order.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+DimensionOrder = typing.Union[typing.Literal["asc", "desc"], typing.Any]
diff --git a/src/square/types/folder.py b/src/square/types/folder.py
new file mode 100644
index 00000000..01d458a5
--- /dev/null
+++ b/src/square/types/folder.py
@@ -0,0 +1,21 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class Folder(UncheckedBaseModel):
+ name: str
+ members: typing.List[str]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/format.py b/src/square/types/format.py
new file mode 100644
index 00000000..7532e4b8
--- /dev/null
+++ b/src/square/types/format.py
@@ -0,0 +1,10 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .custom_numeric_format import CustomNumericFormat
+from .custom_time_format import CustomTimeFormat
+from .link_format import LinkFormat
+from .simple_format import SimpleFormat
+
+Format = typing.Union[SimpleFormat, LinkFormat, CustomTimeFormat, CustomNumericFormat]
diff --git a/src/square/types/format_description.py b/src/square/types/format_description.py
new file mode 100644
index 00000000..17fc5f1a
--- /dev/null
+++ b/src/square/types/format_description.py
@@ -0,0 +1,37 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class FormatDescription(UncheckedBaseModel):
+ """
+ Resolved format description with the predefined name and d3-format specifier
+ """
+
+ name: str = pydantic.Field()
+ """
+ Predefined format name (e.g., 'percent_2', 'currency_1') or a base name like 'number', or 'custom' for ad-hoc specifiers
+ """
+
+ specifier: str = pydantic.Field()
+ """
+ d3-format specifier string (e.g., '.2f', ',.0f', '$,.2f'). See https://d3js.org/d3-format
+ """
+
+ currency: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ ISO 4217 currency code in uppercase (e.g. USD, EUR). Present when a currency format is used.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/hierarchy.py b/src/square/types/hierarchy.py
new file mode 100644
index 00000000..ea084b6e
--- /dev/null
+++ b/src/square/types/hierarchy.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class Hierarchy(UncheckedBaseModel):
+ name: str
+ alias_member: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="aliasMember"),
+ pydantic.Field(
+ alias="aliasMember",
+ description="When hierarchy is defined in Cube, it keeps the original path: Cube.hierarchy",
+ ),
+ ] = None
+ title: typing.Optional[str] = None
+ levels: typing.List[str]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/join_hint.py b/src/square/types/join_hint.py
new file mode 100644
index 00000000..b9692d7d
--- /dev/null
+++ b/src/square/types/join_hint.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+JoinHint = typing.List[str]
diff --git a/src/square/types/join_subquery.py b/src/square/types/join_subquery.py
new file mode 100644
index 00000000..777cb11c
--- /dev/null
+++ b/src/square/types/join_subquery.py
@@ -0,0 +1,25 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class JoinSubquery(UncheckedBaseModel):
+ sql: str
+ on: str
+ join_type: typing_extensions.Annotated[str, FieldMetadata(alias="joinType"), pydantic.Field(alias="joinType")]
+ alias: str
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/link_format.py b/src/square/types/link_format.py
new file mode 100644
index 00000000..423bc8a8
--- /dev/null
+++ b/src/square/types/link_format.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class LinkFormat(UncheckedBaseModel):
+ """
+ Link format with label and type
+ """
+
+ label: str = pydantic.Field()
+ """
+ Label for the link
+ """
+
+ type: typing.Literal["link"] = pydantic.Field(default="link")
+ """
+ Type of the format (must be 'link')
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/load_response.py b/src/square/types/load_response.py
new file mode 100644
index 00000000..298735e5
--- /dev/null
+++ b/src/square/types/load_response.py
@@ -0,0 +1,34 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+from .load_result import LoadResult
+
+
+class LoadResponse(UncheckedBaseModel):
+ pivot_query: typing_extensions.Annotated[
+ typing.Optional[typing.Dict[str, typing.Any]],
+ FieldMetadata(alias="pivotQuery"),
+ pydantic.Field(alias="pivotQuery"),
+ ] = None
+ slow_query: typing_extensions.Annotated[
+ typing.Optional[bool], FieldMetadata(alias="slowQuery"), pydantic.Field(alias="slowQuery")
+ ] = None
+ query_type: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="queryType"), pydantic.Field(alias="queryType")
+ ] = None
+ results: typing.List[LoadResult]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/load_result.py b/src/square/types/load_result.py
new file mode 100644
index 00000000..b64bda48
--- /dev/null
+++ b/src/square/types/load_result.py
@@ -0,0 +1,36 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+from .load_result_annotation import LoadResultAnnotation
+from .load_result_data import LoadResultData
+
+
+class LoadResult(UncheckedBaseModel):
+ data_source: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="dataSource"), pydantic.Field(alias="dataSource")
+ ] = None
+ annotation: LoadResultAnnotation
+ data: LoadResultData
+ refresh_key_values: typing_extensions.Annotated[
+ typing.Optional[typing.List[typing.Dict[str, typing.Any]]],
+ FieldMetadata(alias="refreshKeyValues"),
+ pydantic.Field(alias="refreshKeyValues"),
+ ] = None
+ last_refresh_time: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="lastRefreshTime"), pydantic.Field(alias="lastRefreshTime")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/load_result_annotation.py b/src/square/types/load_result_annotation.py
new file mode 100644
index 00000000..39acef8c
--- /dev/null
+++ b/src/square/types/load_result_annotation.py
@@ -0,0 +1,27 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class LoadResultAnnotation(UncheckedBaseModel):
+ measures: typing.Dict[str, typing.Any]
+ dimensions: typing.Dict[str, typing.Any]
+ segments: typing.Dict[str, typing.Any]
+ time_dimensions: typing_extensions.Annotated[
+ typing.Dict[str, typing.Any], FieldMetadata(alias="timeDimensions"), pydantic.Field(alias="timeDimensions")
+ ]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/load_result_data.py b/src/square/types/load_result_data.py
new file mode 100644
index 00000000..40079c37
--- /dev/null
+++ b/src/square/types/load_result_data.py
@@ -0,0 +1,9 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .load_result_data_columnar import LoadResultDataColumnar
+from .load_result_data_compact import LoadResultDataCompact
+from .load_result_data_row import LoadResultDataRow
+
+LoadResultData = typing.Union[LoadResultDataRow, LoadResultDataCompact, LoadResultDataColumnar]
diff --git a/src/square/types/load_result_data_columnar.py b/src/square/types/load_result_data_columnar.py
new file mode 100644
index 00000000..5ed5a343
--- /dev/null
+++ b/src/square/types/load_result_data_columnar.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class LoadResultDataColumnar(UncheckedBaseModel):
+ """
+ Columnar data format - members list paired with one primitive array per column. Returned when `responseFormat=columnar` is requested.
+ """
+
+ members: typing.List[str] = pydantic.Field()
+ """
+ Ordered list of member names. Element `i` of `columns` holds the values for `members[i]` across all rows.
+ """
+
+ columns: typing.List[typing.List[typing.Any]] = pydantic.Field()
+ """
+ One array per member, in the same order as `members`. Each inner array contains the primitive value of that member for every row (null, boolean, number, string).
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/load_result_data_compact.py b/src/square/types/load_result_data_compact.py
new file mode 100644
index 00000000..734192af
--- /dev/null
+++ b/src/square/types/load_result_data_compact.py
@@ -0,0 +1,32 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class LoadResultDataCompact(UncheckedBaseModel):
+ """
+ Compact data format - a single object with the members list and a dataset of primitive arrays. Returned when `responseFormat=compact` is requested.
+ """
+
+ members: typing.List[str] = pydantic.Field()
+ """
+ Ordered list of member names that correspond to each cell position in `dataset` rows.
+ """
+
+ dataset: typing.List[typing.List[typing.Any]] = pydantic.Field()
+ """
+ Array of rows, where each row is an array of primitive values (null, boolean, number, string) aligned with `members`.
+ """
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/load_result_data_row.py b/src/square/types/load_result_data_row.py
new file mode 100644
index 00000000..ac37bac6
--- /dev/null
+++ b/src/square/types/load_result_data_row.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+LoadResultDataRow = typing.List[typing.Dict[str, typing.Any]]
diff --git a/src/square/types/measure.py b/src/square/types/measure.py
new file mode 100644
index 00000000..c65a1bf1
--- /dev/null
+++ b/src/square/types/measure.py
@@ -0,0 +1,52 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+from .format import Format
+from .format_description import FormatDescription
+
+
+class Measure(UncheckedBaseModel):
+ name: str
+ title: typing.Optional[str] = None
+ short_title: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="shortTitle"), pydantic.Field(alias="shortTitle")
+ ] = None
+ description: typing.Optional[str] = None
+ type: str
+ agg_type: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="aggType"), pydantic.Field(alias="aggType")
+ ] = None
+ meta: typing.Optional[typing.Dict[str, typing.Any]] = None
+ format: typing.Optional[Format] = None
+ format_description: typing_extensions.Annotated[
+ typing.Optional[FormatDescription],
+ FieldMetadata(alias="formatDescription"),
+ pydantic.Field(alias="formatDescription"),
+ ] = None
+ currency: typing.Optional[str] = pydantic.Field(default=None)
+ """
+ ISO 4217 currency code in uppercase (3 characters, e.g. USD, EUR)
+ """
+
+ alias_member: typing_extensions.Annotated[
+ typing.Optional[str],
+ FieldMetadata(alias="aliasMember"),
+ pydantic.Field(
+ alias="aliasMember", description="When measure is defined in View, it keeps the original path: Cube.measure"
+ ),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/metadata_response.py b/src/square/types/metadata_response.py
new file mode 100644
index 00000000..fe106b94
--- /dev/null
+++ b/src/square/types/metadata_response.py
@@ -0,0 +1,26 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+from .cube import Cube
+
+
+class MetadataResponse(UncheckedBaseModel):
+ cubes: typing.Optional[typing.List[Cube]] = None
+ compiler_id: typing_extensions.Annotated[
+ typing.Optional[str], FieldMetadata(alias="compilerId"), pydantic.Field(alias="compilerId")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/nested_folder.py b/src/square/types/nested_folder.py
new file mode 100644
index 00000000..f7bd8775
--- /dev/null
+++ b/src/square/types/nested_folder.py
@@ -0,0 +1,21 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class NestedFolder(UncheckedBaseModel):
+ name: str
+ members: typing.List[str]
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/query.py b/src/square/types/query.py
new file mode 100644
index 00000000..59e2a8d8
--- /dev/null
+++ b/src/square/types/query.py
@@ -0,0 +1,51 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+from .join_hint import JoinHint
+from .join_subquery import JoinSubquery
+from .query_filter import QueryFilter
+from .response_format import ResponseFormat
+from .time_dimension import TimeDimension
+
+
+class Query(UncheckedBaseModel):
+ measures: typing.Optional[typing.List[str]] = None
+ dimensions: typing.Optional[typing.List[str]] = None
+ segments: typing.Optional[typing.List[str]] = None
+ time_dimensions: typing_extensions.Annotated[
+ typing.Optional[typing.List[TimeDimension]],
+ FieldMetadata(alias="timeDimensions"),
+ pydantic.Field(alias="timeDimensions"),
+ ] = None
+ order: typing.Optional[typing.List[typing.List[str]]] = None
+ limit: typing.Optional[int] = None
+ offset: typing.Optional[int] = None
+ filters: typing.Optional[typing.List[QueryFilter]] = None
+ ungrouped: typing.Optional[bool] = None
+ subquery_joins: typing_extensions.Annotated[
+ typing.Optional[typing.List[JoinSubquery]],
+ FieldMetadata(alias="subqueryJoins"),
+ pydantic.Field(alias="subqueryJoins"),
+ ] = None
+ join_hints: typing_extensions.Annotated[
+ typing.Optional[typing.List[JoinHint]], FieldMetadata(alias="joinHints"), pydantic.Field(alias="joinHints")
+ ] = None
+ timezone: typing.Optional[str] = None
+ response_format: typing_extensions.Annotated[
+ typing.Optional[ResponseFormat], FieldMetadata(alias="responseFormat"), pydantic.Field(alias="responseFormat")
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/query_filter.py b/src/square/types/query_filter.py
new file mode 100644
index 00000000..9e37bf92
--- /dev/null
+++ b/src/square/types/query_filter.py
@@ -0,0 +1,9 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+from .query_filter_and import QueryFilterAnd
+from .query_filter_condition import QueryFilterCondition
+from .query_filter_or import QueryFilterOr
+
+QueryFilter = typing.Union[QueryFilterCondition, QueryFilterOr, QueryFilterAnd]
diff --git a/src/square/types/query_filter_and.py b/src/square/types/query_filter_and.py
new file mode 100644
index 00000000..cced013d
--- /dev/null
+++ b/src/square/types/query_filter_and.py
@@ -0,0 +1,26 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class QueryFilterAnd(UncheckedBaseModel):
+ and_: typing_extensions.Annotated[
+ typing.Optional[typing.List[typing.Dict[str, typing.Any]]],
+ FieldMetadata(alias="and"),
+ pydantic.Field(alias="and"),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/query_filter_condition.py b/src/square/types/query_filter_condition.py
new file mode 100644
index 00000000..9393d1dd
--- /dev/null
+++ b/src/square/types/query_filter_condition.py
@@ -0,0 +1,22 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class QueryFilterCondition(UncheckedBaseModel):
+ member: typing.Optional[str] = None
+ operator: typing.Optional[str] = None
+ values: typing.Optional[typing.List[str]] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/query_filter_or.py b/src/square/types/query_filter_or.py
new file mode 100644
index 00000000..238b99ec
--- /dev/null
+++ b/src/square/types/query_filter_or.py
@@ -0,0 +1,26 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class QueryFilterOr(UncheckedBaseModel):
+ or_: typing_extensions.Annotated[
+ typing.Optional[typing.List[typing.Dict[str, typing.Any]]],
+ FieldMetadata(alias="or"),
+ pydantic.Field(alias="or"),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/reporting_error.py b/src/square/types/reporting_error.py
new file mode 100644
index 00000000..e2165989
--- /dev/null
+++ b/src/square/types/reporting_error.py
@@ -0,0 +1,24 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class ReportingError(UncheckedBaseModel):
+ """
+ Error envelope returned by the Reporting API. Note: a 200 response whose body is `{ "error": "Continue wait" }` is not a failure — it signals that a long-running query is still processing and the request should be retried.
+ """
+
+ error: str
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/response_format.py b/src/square/types/response_format.py
new file mode 100644
index 00000000..28f7e80f
--- /dev/null
+++ b/src/square/types/response_format.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+ResponseFormat = typing.Union[typing.Literal["default", "compact", "columnar"], typing.Any]
diff --git a/src/square/types/segment.py b/src/square/types/segment.py
new file mode 100644
index 00000000..05342ee2
--- /dev/null
+++ b/src/square/types/segment.py
@@ -0,0 +1,26 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class Segment(UncheckedBaseModel):
+ name: str
+ title: str
+ description: typing.Optional[str] = None
+ short_title: typing_extensions.Annotated[str, FieldMetadata(alias="shortTitle"), pydantic.Field(alias="shortTitle")]
+ meta: typing.Optional[typing.Dict[str, typing.Any]] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
diff --git a/src/square/types/simple_format.py b/src/square/types/simple_format.py
new file mode 100644
index 00000000..7d2f83b6
--- /dev/null
+++ b/src/square/types/simple_format.py
@@ -0,0 +1,5 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+SimpleFormat = typing.Union[typing.Literal["percent", "currency", "number", "imageUrl", "id", "link"], typing.Any]
diff --git a/src/square/types/time_dimension.py b/src/square/types/time_dimension.py
new file mode 100644
index 00000000..3f44a097
--- /dev/null
+++ b/src/square/types/time_dimension.py
@@ -0,0 +1,28 @@
+# This file was auto-generated by Fern from our API Definition.
+
+import typing
+
+import pydantic
+import typing_extensions
+from ..core.pydantic_utilities import IS_PYDANTIC_V2
+from ..core.serialization import FieldMetadata
+from ..core.unchecked_base_model import UncheckedBaseModel
+
+
+class TimeDimension(UncheckedBaseModel):
+ dimension: str
+ granularity: typing.Optional[str] = None
+ date_range: typing_extensions.Annotated[
+ typing.Optional[typing.Dict[str, typing.Any]],
+ FieldMetadata(alias="dateRange"),
+ pydantic.Field(alias="dateRange"),
+ ] = None
+
+ if IS_PYDANTIC_V2:
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
+ else:
+
+ class Config:
+ frozen = True
+ smart_union = True
+ extra = pydantic.Extra.allow
From 358cfb05a4ac93a03337e788fe60c14205e3284c Mon Sep 17 00:00:00 2001
From: fern-support
Date: Sun, 14 Jun 2026 09:19:43 -0400
Subject: [PATCH 2/3] Add Reporting API polling helper, tests, and docs
The Reporting API's /v1/load is asynchronous: a still-processing query
returns HTTP 200 with {"error": "Continue wait"} instead of results, and
the client is expected to re-send the identical request with backoff until
results arrive.
- src/square/utils/reporting_helper.py: load_and_wait (sync) and
load_and_wait_async (AsyncSquare) own the Continue-wait retry loop
(exp. backoff 2s->20s, 20 attempts). Sync supports threading.Event
cancellation; async uses native asyncio cancellation.
- tests/integration/test_reporting_helper.py: offline unit tests for the
polling loop, timeout, cancellation, and a real-deserializer check that
the Continue-wait sentinel survives construct_type as a retry signal.
- tests/integration/test_reporting.py: live suite, defaults to production,
gated behind TEST_SQUARE_REPORTING so CI stays green.
- README.md: Reporting API section.
- .fernignore: protect reporting_helper.py.
---
.fernignore | 1 +
README.md | 65 +++++++
src/square/utils/reporting_helper.py | 187 ++++++++++++++++++++
tests/integration/test_reporting.py | 91 ++++++++++
tests/integration/test_reporting_helper.py | 190 +++++++++++++++++++++
5 files changed, 534 insertions(+)
create mode 100644 src/square/utils/reporting_helper.py
create mode 100644 tests/integration/test_reporting.py
create mode 100644 tests/integration/test_reporting_helper.py
diff --git a/.fernignore b/.fernignore
index 246906a5..2042ff79 100644
--- a/.fernignore
+++ b/.fernignore
@@ -9,6 +9,7 @@ examples
legacy
src/square/core/api_error.py
src/square/utils/webhooks_helper.py
+src/square/utils/reporting_helper.py
tests/integration
README.md
.fern/replay.lock
diff --git a/README.md b/README.md
index aa122514..b9111c2f 100644
--- a/README.md
+++ b/README.md
@@ -193,6 +193,71 @@ is_valid = verify_signature(
)
```
+## Reporting API
+
+The [Reporting API](https://developer.squareup.com/docs/reporting-api/overview) lets you query
+aggregated reporting data. Call `reporting.get_metadata` first to discover the available cubes,
+measures, and dimensions, then run a query with `reporting.load`.
+
+```python
+from square import Square
+
+client = Square(token="YOUR_TOKEN")
+
+# Discover what you can query.
+metadata = client.reporting.get_metadata()
+
+# Run a query against the discovered schema.
+response = client.reporting.load(query={"measures": ["Orders.count"]})
+```
+
+`load` is asynchronous: while a query is still being computed, the API returns an HTTP `200` whose
+body is `{"error": "Continue wait"}` instead of results, and the client is expected to re-send the
+identical request — with backoff — until the results are ready. The `load_and_wait` helper owns
+that polling loop for you and returns the resolved results (never the `"Continue wait"` sentinel):
+
+```python
+from square import Square
+from square.utils.reporting_helper import load_and_wait
+
+client = Square(token="YOUR_TOKEN")
+
+response = load_and_wait(client, query={"measures": ["Orders.count"]})
+
+print(response.results)
+```
+
+By default it polls up to 20 times with exponential backoff (2s → 20s). Tune the behavior — and
+pass a `threading.Event` to cancel — via the keyword arguments:
+
+```python
+import threading
+
+cancel_event = threading.Event()
+
+response = load_and_wait(
+ client,
+ query={"measures": ["Orders.count"]},
+ max_attempts=10, # default 20
+ initial_delay_s=1.0, # default 2.0
+ max_delay_s=20.0, # default 20.0
+ backoff_factor=2.0, # default 2.0
+ cancel_event=cancel_event,
+)
+```
+
+For the [async client](#async-client), use `load_and_wait_async` (cancel it the idiomatic asyncio
+way — e.g. `asyncio.wait_for` or `Task.cancel`):
+
+```python
+from square import AsyncSquare
+from square.utils.reporting_helper import load_and_wait_async
+
+client = AsyncSquare(token="YOUR_TOKEN")
+
+response = await load_and_wait_async(client, query={"measures": ["Orders.count"]})
+```
+
## Advanced
### Retries
diff --git a/src/square/utils/reporting_helper.py b/src/square/utils/reporting_helper.py
new file mode 100644
index 00000000..2ff6203b
--- /dev/null
+++ b/src/square/utils/reporting_helper.py
@@ -0,0 +1,187 @@
+import asyncio
+import threading
+import time
+import typing
+
+from ..core.request_options import RequestOptions
+from ..requests.query import QueryParams
+from ..types.cache_mode import CacheMode
+from ..types.load_response import LoadResponse
+
+if typing.TYPE_CHECKING:
+ from ..client import AsyncSquare, Square
+
+# Sentinel returned by the Reporting API on an HTTP 200 while a /v1/load query is
+# still processing. It is NOT an error -- the request should be retried. See the
+# Reporting API docs: https://developer.squareup.com/docs/reporting-api/overview
+CONTINUE_WAIT = "Continue wait"
+
+# Defaults for the polling loop: up to 20 attempts with exponential backoff
+# starting at 2s and capped at 20s.
+DEFAULT_MAX_ATTEMPTS = 20
+DEFAULT_INITIAL_DELAY_S = 2.0
+DEFAULT_MAX_DELAY_S = 20.0
+DEFAULT_BACKOFF_FACTOR = 2.0
+
+
+class ReportingPollTimeoutError(Exception):
+ """Raised when a reporting query does not resolve within the allotted attempts."""
+
+
+class ReportingPollCancelledError(Exception):
+ """Raised when a reporting poll loop is cancelled via its ``cancel_event``."""
+
+
+def _is_continue_wait(response: LoadResponse) -> bool:
+ # A "Continue wait" body parses into a LoadResponse (LoadResponse is an
+ # UncheckedBaseModel, so validation is skipped) with the extra ``error`` field
+ # preserved (the model is configured with extra="allow") and ``results`` left as
+ # None. That surviving ``error`` sentinel is the signal to retry.
+ return getattr(response, "error", None) == CONTINUE_WAIT
+
+
+def _build_load_kwargs(
+ *,
+ query_type: typing.Optional[str],
+ cache: typing.Optional[CacheMode],
+ query: typing.Optional[QueryParams],
+ request_options: typing.Optional[RequestOptions],
+) -> typing.Dict[str, typing.Any]:
+ # Forward only the inputs the caller actually set; the generated ``load`` omits
+ # anything we leave out (its params default to a sentinel), so a ``None`` here
+ # means "don't send it" rather than "send null".
+ load_kwargs: typing.Dict[str, typing.Any] = {"request_options": request_options}
+ if query_type is not None:
+ load_kwargs["query_type"] = query_type
+ if cache is not None:
+ load_kwargs["cache"] = cache
+ if query is not None:
+ load_kwargs["query"] = query
+ return load_kwargs
+
+
+def _next_delay(delay: float, backoff_factor: float, max_delay_s: float) -> float:
+ return min(delay * backoff_factor, max_delay_s)
+
+
+def _timeout_error(max_attempts: int) -> ReportingPollTimeoutError:
+ return ReportingPollTimeoutError(
+ f'Reporting query did not complete after {max_attempts} attempts ("{CONTINUE_WAIT}").'
+ )
+
+
+def load_and_wait(
+ client: "Square",
+ *,
+ query_type: typing.Optional[str] = None,
+ cache: typing.Optional[CacheMode] = None,
+ query: typing.Optional[QueryParams] = None,
+ max_attempts: int = DEFAULT_MAX_ATTEMPTS,
+ initial_delay_s: float = DEFAULT_INITIAL_DELAY_S,
+ max_delay_s: float = DEFAULT_MAX_DELAY_S,
+ backoff_factor: float = DEFAULT_BACKOFF_FACTOR,
+ cancel_event: typing.Optional[threading.Event] = None,
+ request_options: typing.Optional[RequestOptions] = None,
+) -> LoadResponse:
+ """
+ Runs a reporting query and transparently polls until it resolves, returning the
+ final ``LoadResponse``. Re-sends the identical request with exponential backoff
+ while the Reporting API answers "Continue wait".
+
+ Args:
+ client: A configured synchronous ``Square`` client.
+ query_type: Optional query type (passed through to ``client.reporting.load``).
+ cache: Optional cache strategy.
+ query: The reporting query (measures, dimensions, filters, ...).
+ max_attempts: Maximum poll attempts before giving up. Default 20.
+ initial_delay_s: Delay before the first retry, in seconds. Default 2.0.
+ max_delay_s: Upper bound on the backoff delay, in seconds. Default 20.0.
+ backoff_factor: Multiplier applied to the delay after each attempt. Default 2.0.
+ cancel_event: Optional ``threading.Event``; when set, the loop stops promptly
+ (interrupting any in-flight backoff sleep) and raises
+ ``ReportingPollCancelledError``.
+ request_options: Forwarded to each underlying ``client.reporting.load`` call.
+
+ Returns:
+ The resolved ``LoadResponse`` (never the "Continue wait" sentinel).
+
+ Raises:
+ ReportingPollTimeoutError: if the query does not resolve within ``max_attempts``.
+ ReportingPollCancelledError: if ``cancel_event`` is set before the query resolves.
+ """
+ load_kwargs = _build_load_kwargs(
+ query_type=query_type, cache=cache, query=query, request_options=request_options
+ )
+ delay = initial_delay_s
+ for attempt in range(1, max_attempts + 1):
+ if cancel_event is not None and cancel_event.is_set():
+ raise ReportingPollCancelledError("Reporting query polling was cancelled.")
+ response = client.reporting.load(**load_kwargs)
+ if not _is_continue_wait(response):
+ return response
+ if attempt == max_attempts:
+ break
+ # ``Event.wait`` doubles as an interruptible sleep: it returns True as soon as
+ # the event is set, so cancellation does not wait out the remaining backoff.
+ if cancel_event is not None:
+ if cancel_event.wait(delay):
+ raise ReportingPollCancelledError("Reporting query polling was cancelled.")
+ else:
+ time.sleep(delay)
+ delay = _next_delay(delay, backoff_factor, max_delay_s)
+
+ raise _timeout_error(max_attempts)
+
+
+async def load_and_wait_async(
+ client: "AsyncSquare",
+ *,
+ query_type: typing.Optional[str] = None,
+ cache: typing.Optional[CacheMode] = None,
+ query: typing.Optional[QueryParams] = None,
+ max_attempts: int = DEFAULT_MAX_ATTEMPTS,
+ initial_delay_s: float = DEFAULT_INITIAL_DELAY_S,
+ max_delay_s: float = DEFAULT_MAX_DELAY_S,
+ backoff_factor: float = DEFAULT_BACKOFF_FACTOR,
+ request_options: typing.Optional[RequestOptions] = None,
+) -> LoadResponse:
+ """
+ Async counterpart to :func:`load_and_wait` for the ``AsyncSquare`` client. Polls
+ ``client.reporting.load`` with exponential backoff while the Reporting API answers
+ "Continue wait", returning the resolved ``LoadResponse``.
+
+ Cancellation is handled the idiomatic asyncio way: cancel the awaiting task (e.g.
+ via ``asyncio.wait_for`` / ``Task.cancel``) and the in-flight ``asyncio.sleep``
+ raises ``asyncio.CancelledError``, which propagates out of this coroutine.
+
+ Args:
+ client: A configured ``AsyncSquare`` client.
+ query_type: Optional query type (passed through to ``client.reporting.load``).
+ cache: Optional cache strategy.
+ query: The reporting query (measures, dimensions, filters, ...).
+ max_attempts: Maximum poll attempts before giving up. Default 20.
+ initial_delay_s: Delay before the first retry, in seconds. Default 2.0.
+ max_delay_s: Upper bound on the backoff delay, in seconds. Default 20.0.
+ backoff_factor: Multiplier applied to the delay after each attempt. Default 2.0.
+ request_options: Forwarded to each underlying ``client.reporting.load`` call.
+
+ Returns:
+ The resolved ``LoadResponse`` (never the "Continue wait" sentinel).
+
+ Raises:
+ ReportingPollTimeoutError: if the query does not resolve within ``max_attempts``.
+ """
+ load_kwargs = _build_load_kwargs(
+ query_type=query_type, cache=cache, query=query, request_options=request_options
+ )
+ delay = initial_delay_s
+ for attempt in range(1, max_attempts + 1):
+ response = await client.reporting.load(**load_kwargs)
+ if not _is_continue_wait(response):
+ return response
+ if attempt == max_attempts:
+ break
+ await asyncio.sleep(delay)
+ delay = _next_delay(delay, backoff_factor, max_delay_s)
+
+ raise _timeout_error(max_attempts)
diff --git a/tests/integration/test_reporting.py b/tests/integration/test_reporting.py
new file mode 100644
index 00000000..7ac0bbfa
--- /dev/null
+++ b/tests/integration/test_reporting.py
@@ -0,0 +1,91 @@
+"""Live, end-to-end tests for the Reporting API.
+
+The Reporting API is a beta, bespoke offering served ONLY from production
+(connect.squareup.com/reporting) -- it is not routed on sandbox (which 404s),
+and a sandbox token 401s against prod. Validating it live therefore needs a
+production, reporting-provisioned ``TEST_SQUARE_TOKEN``. CI's token is
+sandbox-only, so this suite is gated behind ``TEST_SQUARE_REPORTING`` and skips
+by default, keeping CI green. The endpoints exercised are read-only (schema
+discovery + queries). The polling *logic* is covered without a live account in
+``test_reporting_helper.py``.
+
+Run it against a real prod account:
+
+ TEST_SQUARE_REPORTING=1 TEST_SQUARE_TOKEN= \
+ poetry run pytest tests/integration/test_reporting.py
+ # Override the host with TEST_SQUARE_BASE_URL= if reporting moves.
+"""
+
+import os
+
+import pytest
+
+from square import Square
+from square.environment import SquareEnvironment
+from square.utils.reporting_helper import load_and_wait
+
+pytestmark = pytest.mark.skipif(
+ not os.getenv("TEST_SQUARE_REPORTING"),
+ reason="Set TEST_SQUARE_REPORTING to run the live Reporting API suite (needs a prod, reporting-provisioned token).",
+)
+
+
+def reporting_client() -> Square:
+ token = os.getenv("TEST_SQUARE_TOKEN")
+ if not token:
+ raise RuntimeError("TEST_SQUARE_TOKEN must be set to run the reporting integration suite.")
+ # Reporting only exists on production; allow overriding the host via TEST_SQUARE_BASE_URL.
+ base_url = os.getenv("TEST_SQUARE_BASE_URL")
+ if base_url:
+ return Square(token=token, base_url=base_url)
+ return Square(token=token, environment=SquareEnvironment.PRODUCTION)
+
+
+def first_measure_name(client: Square) -> str:
+ metadata = client.reporting.get_metadata()
+ cubes = metadata.cubes or []
+ for cube in cubes:
+ for measure in cube.measures or []:
+ if measure.name:
+ return measure.name
+ raise RuntimeError("No cubes/measures are available on the reporting schema for this account.")
+
+
+def test_get_metadata_returns_queryable_schema() -> None:
+ client = reporting_client()
+ metadata = client.reporting.get_metadata()
+
+ assert metadata.cubes is not None
+ assert len(metadata.cubes) > 0
+
+
+def test_load_returns_results_or_continue_wait_sentinel() -> None:
+ client = reporting_client()
+ measure = first_measure_name(client)
+
+ response = client.reporting.load(query={"measures": [measure]})
+
+ sentinel = getattr(response, "error", None)
+ if sentinel is not None:
+ # Documented async behavior: a still-processing query comes back as HTTP 200
+ # with {"error": "Continue wait"} instead of results.
+ assert sentinel == "Continue wait"
+ else:
+ assert response.results is not None
+
+
+def test_load_and_wait_resolves_without_continue_wait() -> None:
+ client = reporting_client()
+ measure = first_measure_name(client)
+
+ response = load_and_wait(
+ client,
+ query={"measures": [measure]},
+ max_attempts=20,
+ initial_delay_s=2.0,
+ max_delay_s=20.0,
+ )
+
+ # The polling helper must never hand back the raw "Continue wait" sentinel.
+ assert getattr(response, "error", None) is None
+ assert response.results is not None
diff --git a/tests/integration/test_reporting_helper.py b/tests/integration/test_reporting_helper.py
new file mode 100644
index 00000000..90067c1c
--- /dev/null
+++ b/tests/integration/test_reporting_helper.py
@@ -0,0 +1,190 @@
+"""Offline unit tests for the Reporting API polling helper.
+
+The Reporting API answers a still-processing ``/v1/load`` query with an HTTP 200
+whose body is ``{"error": "Continue wait"}``. ``load_and_wait`` /
+``load_and_wait_async`` own the retry loop around that sentinel. These tests
+exercise that loop without a network by scripting ``client.reporting.load``, plus
+one test that proves the sentinel actually survives the generated client's
+deserialization. They run offline, so they stay green in CI.
+
+The live, end-to-end suite lives in ``test_reporting.py`` (gated behind
+``TEST_SQUARE_REPORTING``).
+"""
+
+import threading
+import typing
+
+import pytest
+
+from square.core.unchecked_base_model import construct_type
+from square.types.load_response import LoadResponse
+from square.utils.reporting_helper import (
+ CONTINUE_WAIT,
+ ReportingPollCancelledError,
+ ReportingPollTimeoutError,
+ load_and_wait,
+ load_and_wait_async,
+)
+
+if typing.TYPE_CHECKING:
+ from square.client import AsyncSquare, Square
+
+
+def _continue_wait() -> LoadResponse:
+ # Build the sentinel the same way the generated client does, so the tests
+ # exercise the real type rather than a hand-rolled stand-in.
+ return typing.cast(LoadResponse, construct_type(type_=LoadResponse, object_={"error": CONTINUE_WAIT}))
+
+
+def _resolved() -> LoadResponse:
+ return typing.cast(
+ LoadResponse,
+ construct_type(type_=LoadResponse, object_={"results": [{"data": {"Orders.count": "128"}}]}),
+ )
+
+
+class _FakeReporting:
+ """A ``reporting`` stub whose ``load`` returns a scripted sequence of responses."""
+
+ def __init__(
+ self,
+ sequence: typing.List[LoadResponse],
+ on_call: typing.Optional[typing.Callable[[int], None]] = None,
+ ) -> None:
+ self._sequence = sequence
+ self._on_call = on_call
+ self.calls = 0
+
+ def load(self, **_kwargs: typing.Any) -> LoadResponse:
+ response = self._sequence[min(self.calls, len(self._sequence) - 1)]
+ self.calls += 1
+ if self._on_call is not None:
+ self._on_call(self.calls)
+ return response
+
+
+class _FakeAsyncReporting:
+ def __init__(self, sequence: typing.List[LoadResponse]) -> None:
+ self._sequence = sequence
+ self.calls = 0
+
+ async def load(self, **_kwargs: typing.Any) -> LoadResponse:
+ response = self._sequence[min(self.calls, len(self._sequence) - 1)]
+ self.calls += 1
+ return response
+
+
+def _fake_client(reporting: typing.Any) -> "Square":
+ class _FakeClient:
+ pass
+
+ client = _FakeClient()
+ client.reporting = reporting # type: ignore[attr-defined]
+ return typing.cast("Square", client)
+
+
+def test_polls_past_continue_wait_and_returns_resolved_result() -> None:
+ reporting = _FakeReporting([_continue_wait(), _continue_wait(), _resolved()])
+ client = _fake_client(reporting)
+
+ response = load_and_wait(
+ client,
+ query={"measures": ["Orders.count"]},
+ initial_delay_s=0.001,
+ max_delay_s=0.001,
+ max_attempts=5,
+ )
+
+ # The helper must never hand back the raw sentinel.
+ assert getattr(response, "error", None) is None
+ assert response.results is not None
+ assert reporting.calls == 3
+
+
+def test_returns_immediately_when_first_response_has_results() -> None:
+ reporting = _FakeReporting([_resolved()])
+ client = _fake_client(reporting)
+
+ response = load_and_wait(client, initial_delay_s=0.001)
+
+ assert response.results is not None
+ assert reporting.calls == 1
+
+
+def test_raises_timeout_once_max_attempts_exhausted() -> None:
+ reporting = _FakeReporting([_continue_wait()]) # never resolves
+ client = _fake_client(reporting)
+
+ with pytest.raises(ReportingPollTimeoutError, match="did not complete after 3 attempts"):
+ load_and_wait(client, initial_delay_s=0.001, max_delay_s=0.001, max_attempts=3)
+
+ assert reporting.calls == 3
+
+
+def test_cancel_event_set_before_start_aborts_without_calling() -> None:
+ reporting = _FakeReporting([_continue_wait()])
+ client = _fake_client(reporting)
+ cancel_event = threading.Event()
+ cancel_event.set()
+
+ with pytest.raises(ReportingPollCancelledError):
+ load_and_wait(client, initial_delay_s=10, max_attempts=10, cancel_event=cancel_event)
+
+ assert reporting.calls == 0
+
+
+def test_cancel_event_interrupts_backoff_sleep() -> None:
+ cancel_event = threading.Event()
+
+ # Trip the event after the first poll; the helper's interruptible backoff sleep
+ # must then return promptly and raise rather than waiting out the delay.
+ def on_call(_count: int) -> None:
+ cancel_event.set()
+
+ reporting = _FakeReporting([_continue_wait()], on_call=on_call)
+ client = _fake_client(reporting)
+
+ with pytest.raises(ReportingPollCancelledError):
+ load_and_wait(client, initial_delay_s=30, max_attempts=10, cancel_event=cancel_event)
+
+ assert reporting.calls == 1
+
+
+def test_continue_wait_body_survives_real_deserialization_as_sentinel() -> None:
+ # The crux of the design: the generated ``reporting.load`` parses the body with
+ # ``construct_type`` (skip-validation + extra="allow"), so the ``error`` sentinel
+ # survives onto a LoadResponse-shaped object while ``results`` stays None. If this
+ # ever stops being true, load_and_wait would mistake "Continue wait" for a result.
+ parsed = typing.cast(
+ typing.Any, construct_type(type_=LoadResponse, object_={"error": CONTINUE_WAIT})
+ )
+
+ assert parsed.error == CONTINUE_WAIT
+ assert parsed.results is None
+
+
+async def test_async_polls_past_continue_wait_and_resolves() -> None:
+ reporting = _FakeAsyncReporting([_continue_wait(), _continue_wait(), _resolved()])
+ client = typing.cast("AsyncSquare", _fake_client(reporting))
+
+ response = await load_and_wait_async(
+ client,
+ query={"measures": ["Orders.count"]},
+ initial_delay_s=0.001,
+ max_delay_s=0.001,
+ max_attempts=5,
+ )
+
+ assert getattr(response, "error", None) is None
+ assert response.results is not None
+ assert reporting.calls == 3
+
+
+async def test_async_raises_timeout_once_max_attempts_exhausted() -> None:
+ reporting = _FakeAsyncReporting([_continue_wait()]) # never resolves
+ client = typing.cast("AsyncSquare", _fake_client(reporting))
+
+ with pytest.raises(ReportingPollTimeoutError, match="did not complete after 3 attempts"):
+ await load_and_wait_async(client, initial_delay_s=0.001, max_delay_s=0.001, max_attempts=3)
+
+ assert reporting.calls == 3
From 469bd498d7b69aa85708edfc52737f29cc77bfd9 Mon Sep 17 00:00:00 2001
From: fern-support
Date: Mon, 15 Jun 2026 19:49:21 -0400
Subject: [PATCH 3/3] test(reporting): source live token from
TEST_SQUARE_REPORTING (prod)
---
.github/workflows/ci.yml | 1 +
tests/integration/test_reporting.py | 15 ++++++++-------
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 62bfdf61..0327a3e7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -32,6 +32,7 @@ jobs:
runs-on: ubuntu-latest
env:
TEST_SQUARE_TOKEN: ${{ secrets.TEST_SQUARE_TOKEN }}
+ TEST_SQUARE_REPORTING: ${{ secrets.TEST_SQUARE_REPORTING }}
steps:
- name: Checkout repo
uses: actions/checkout@v3
diff --git a/tests/integration/test_reporting.py b/tests/integration/test_reporting.py
index 7ac0bbfa..680a1abf 100644
--- a/tests/integration/test_reporting.py
+++ b/tests/integration/test_reporting.py
@@ -3,15 +3,16 @@
The Reporting API is a beta, bespoke offering served ONLY from production
(connect.squareup.com/reporting) -- it is not routed on sandbox (which 404s),
and a sandbox token 401s against prod. Validating it live therefore needs a
-production, reporting-provisioned ``TEST_SQUARE_TOKEN``. CI's token is
-sandbox-only, so this suite is gated behind ``TEST_SQUARE_REPORTING`` and skips
-by default, keeping CI green. The endpoints exercised are read-only (schema
+production, reporting-provisioned access token. CI's regular ``TEST_SQUARE_TOKEN``
+is sandbox-only, so this suite is gated behind ``TEST_SQUARE_REPORTING`` -- which
+is itself the prod, reporting-provisioned token -- and skips by default when it is
+unset, keeping CI green. The endpoints exercised are read-only (schema
discovery + queries). The polling *logic* is covered without a live account in
``test_reporting_helper.py``.
Run it against a real prod account:
- TEST_SQUARE_REPORTING=1 TEST_SQUARE_TOKEN= \
+ TEST_SQUARE_REPORTING= \
poetry run pytest tests/integration/test_reporting.py
# Override the host with TEST_SQUARE_BASE_URL= if reporting moves.
"""
@@ -26,14 +27,14 @@
pytestmark = pytest.mark.skipif(
not os.getenv("TEST_SQUARE_REPORTING"),
- reason="Set TEST_SQUARE_REPORTING to run the live Reporting API suite (needs a prod, reporting-provisioned token).",
+ reason="Set TEST_SQUARE_REPORTING to a prod, reporting-provisioned token to run the live Reporting API suite.",
)
def reporting_client() -> Square:
- token = os.getenv("TEST_SQUARE_TOKEN")
+ token = os.getenv("TEST_SQUARE_REPORTING")
if not token:
- raise RuntimeError("TEST_SQUARE_TOKEN must be set to run the reporting integration suite.")
+ raise RuntimeError("TEST_SQUARE_REPORTING must be set to a prod, reporting-provisioned token to run the reporting integration suite.")
# Reporting only exists on production; allow overriding the host via TEST_SQUARE_BASE_URL.
base_url = os.getenv("TEST_SQUARE_BASE_URL")
if base_url: