Skip to content

Commit a722ea7

Browse files
jfrench9claude
andauthored
Add Report Builder API, models, and extension clients (#74)
## Summary Introduces full report builder functionality to the `robosystems_client` SDK, including new ledger API endpoints, data models, and high-level extension clients for report lifecycle management. ## Key Accomplishments ### New API Endpoints (7) Added seven new ledger API modules supporting the complete report lifecycle: - **create_report** – Create new reports from a ledger - **get_report** – Retrieve a specific report by ID - **list_reports** – List all available reports - **delete_report** – Remove an existing report - **regenerate_report** – Trigger regeneration of a report - **share_report** – Share a report with other users/entities - **get_statement** – Retrieve financial statement data associated with a report ### New Data Models (10) Introduced strongly-typed request/response models to support the new API surface: - `CreateReportRequest`, `RegenerateReportRequest`, `ShareReportRequest` – request payloads - `ReportResponse`, `ReportListResponse` – report retrieval responses - `ShareReportResponse`, `ShareResultItem` – sharing workflow responses - `StatementResponse`, `FactRowResponse` – financial statement data models - `StructureSummary`, `ValidationCheckResponse` – supporting metadata models ### Extension Clients (2) - **LedgerClient** – High-level client wrapping ledger API operations, including a new `create_mapping_structure` method for building report mapping structures - **ReportClient** – Dedicated client providing a clean interface for report CRUD, regeneration, sharing, and statement retrieval ### Updated Module Exports - `extensions/__init__.py` and `extensions/extensions.py` updated to register and expose the new `LedgerClient` and `ReportClient` - `models/__init__.py` updated to export all new model classes ## Breaking Changes None. This is a purely additive feature with no modifications to existing APIs or models. ## Testing Notes - Verify all new API endpoint modules handle both `sync` and `async` invocation patterns consistently with existing endpoints - Test the `LedgerClient.create_mapping_structure` method with various ledger configurations - Validate `ReportClient` operations end-to-end: create → get → list → regenerate → share → delete - Confirm `StatementResponse` and `FactRowResponse` correctly deserialize nested financial data structures - Ensure new model serialization/deserialization round-trips cleanly (especially `ReportResponse` at 330 lines, the largest new model) ## Infrastructure Considerations - No new dependencies introduced; all new code builds on the existing client framework and patterns - The 10 new models and 7 new API modules add ~3,200 lines; ensure documentation generation pipelines pick up the new modules - Extension client registration should be validated in any integration or environment bootstrap tests --- 🤖 Generated with [Claude Code](https://claude.ai/code) **Branch Info:** - Source: `feature/report-builder` - Target: `main` - Type: feature Co-Authored-By: Claude <noreply@anthropic.com>
2 parents f3f8368 + ae3b1ef commit a722ea7

23 files changed

+3220
-0
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
from http import HTTPStatus
2+
from typing import Any
3+
from urllib.parse import quote
4+
5+
import httpx
6+
7+
from ... import errors
8+
from ...client import AuthenticatedClient, Client
9+
from ...models.create_report_request import CreateReportRequest
10+
from ...models.http_validation_error import HTTPValidationError
11+
from ...models.report_response import ReportResponse
12+
from ...types import Response
13+
14+
15+
def _get_kwargs(
16+
graph_id: str,
17+
*,
18+
body: CreateReportRequest,
19+
) -> dict[str, Any]:
20+
headers: dict[str, Any] = {}
21+
22+
_kwargs: dict[str, Any] = {
23+
"method": "post",
24+
"url": "/v1/ledger/{graph_id}/reports".format(
25+
graph_id=quote(str(graph_id), safe=""),
26+
),
27+
}
28+
29+
_kwargs["json"] = body.to_dict()
30+
31+
headers["Content-Type"] = "application/json"
32+
33+
_kwargs["headers"] = headers
34+
return _kwargs
35+
36+
37+
def _parse_response(
38+
*, client: AuthenticatedClient | Client, response: httpx.Response
39+
) -> HTTPValidationError | ReportResponse | None:
40+
if response.status_code == 201:
41+
response_201 = ReportResponse.from_dict(response.json())
42+
43+
return response_201
44+
45+
if response.status_code == 422:
46+
response_422 = HTTPValidationError.from_dict(response.json())
47+
48+
return response_422
49+
50+
if client.raise_on_unexpected_status:
51+
raise errors.UnexpectedStatus(response.status_code, response.content)
52+
else:
53+
return None
54+
55+
56+
def _build_response(
57+
*, client: AuthenticatedClient | Client, response: httpx.Response
58+
) -> Response[HTTPValidationError | ReportResponse]:
59+
return Response(
60+
status_code=HTTPStatus(response.status_code),
61+
content=response.content,
62+
headers=response.headers,
63+
parsed=_parse_response(client=client, response=response),
64+
)
65+
66+
67+
def sync_detailed(
68+
graph_id: str,
69+
*,
70+
client: AuthenticatedClient,
71+
body: CreateReportRequest,
72+
) -> Response[HTTPValidationError | ReportResponse]:
73+
"""Create Report
74+
75+
Create a report definition, generate facts for all mapped elements.
76+
77+
Args:
78+
graph_id (str):
79+
body (CreateReportRequest):
80+
81+
Raises:
82+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
83+
httpx.TimeoutException: If the request takes longer than Client.timeout.
84+
85+
Returns:
86+
Response[HTTPValidationError | ReportResponse]
87+
"""
88+
89+
kwargs = _get_kwargs(
90+
graph_id=graph_id,
91+
body=body,
92+
)
93+
94+
response = client.get_httpx_client().request(
95+
**kwargs,
96+
)
97+
98+
return _build_response(client=client, response=response)
99+
100+
101+
def sync(
102+
graph_id: str,
103+
*,
104+
client: AuthenticatedClient,
105+
body: CreateReportRequest,
106+
) -> HTTPValidationError | ReportResponse | None:
107+
"""Create Report
108+
109+
Create a report definition, generate facts for all mapped elements.
110+
111+
Args:
112+
graph_id (str):
113+
body (CreateReportRequest):
114+
115+
Raises:
116+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
117+
httpx.TimeoutException: If the request takes longer than Client.timeout.
118+
119+
Returns:
120+
HTTPValidationError | ReportResponse
121+
"""
122+
123+
return sync_detailed(
124+
graph_id=graph_id,
125+
client=client,
126+
body=body,
127+
).parsed
128+
129+
130+
async def asyncio_detailed(
131+
graph_id: str,
132+
*,
133+
client: AuthenticatedClient,
134+
body: CreateReportRequest,
135+
) -> Response[HTTPValidationError | ReportResponse]:
136+
"""Create Report
137+
138+
Create a report definition, generate facts for all mapped elements.
139+
140+
Args:
141+
graph_id (str):
142+
body (CreateReportRequest):
143+
144+
Raises:
145+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
146+
httpx.TimeoutException: If the request takes longer than Client.timeout.
147+
148+
Returns:
149+
Response[HTTPValidationError | ReportResponse]
150+
"""
151+
152+
kwargs = _get_kwargs(
153+
graph_id=graph_id,
154+
body=body,
155+
)
156+
157+
response = await client.get_async_httpx_client().request(**kwargs)
158+
159+
return _build_response(client=client, response=response)
160+
161+
162+
async def asyncio(
163+
graph_id: str,
164+
*,
165+
client: AuthenticatedClient,
166+
body: CreateReportRequest,
167+
) -> HTTPValidationError | ReportResponse | None:
168+
"""Create Report
169+
170+
Create a report definition, generate facts for all mapped elements.
171+
172+
Args:
173+
graph_id (str):
174+
body (CreateReportRequest):
175+
176+
Raises:
177+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
178+
httpx.TimeoutException: If the request takes longer than Client.timeout.
179+
180+
Returns:
181+
HTTPValidationError | ReportResponse
182+
"""
183+
184+
return (
185+
await asyncio_detailed(
186+
graph_id=graph_id,
187+
client=client,
188+
body=body,
189+
)
190+
).parsed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
from http import HTTPStatus
2+
from typing import Any, cast
3+
from urllib.parse import quote
4+
5+
import httpx
6+
7+
from ... import errors
8+
from ...client import AuthenticatedClient, Client
9+
from ...models.http_validation_error import HTTPValidationError
10+
from ...types import Response
11+
12+
13+
def _get_kwargs(
14+
graph_id: str,
15+
report_id: str,
16+
) -> dict[str, Any]:
17+
_kwargs: dict[str, Any] = {
18+
"method": "delete",
19+
"url": "/v1/ledger/{graph_id}/reports/{report_id}".format(
20+
graph_id=quote(str(graph_id), safe=""),
21+
report_id=quote(str(report_id), safe=""),
22+
),
23+
}
24+
25+
return _kwargs
26+
27+
28+
def _parse_response(
29+
*, client: AuthenticatedClient | Client, response: httpx.Response
30+
) -> Any | HTTPValidationError | None:
31+
if response.status_code == 204:
32+
response_204 = cast(Any, None)
33+
return response_204
34+
35+
if response.status_code == 422:
36+
response_422 = HTTPValidationError.from_dict(response.json())
37+
38+
return response_422
39+
40+
if client.raise_on_unexpected_status:
41+
raise errors.UnexpectedStatus(response.status_code, response.content)
42+
else:
43+
return None
44+
45+
46+
def _build_response(
47+
*, client: AuthenticatedClient | Client, response: httpx.Response
48+
) -> Response[Any | HTTPValidationError]:
49+
return Response(
50+
status_code=HTTPStatus(response.status_code),
51+
content=response.content,
52+
headers=response.headers,
53+
parsed=_parse_response(client=client, response=response),
54+
)
55+
56+
57+
def sync_detailed(
58+
graph_id: str,
59+
report_id: str,
60+
*,
61+
client: AuthenticatedClient,
62+
) -> Response[Any | HTTPValidationError]:
63+
"""Delete Report
64+
65+
Delete a report definition and its generated facts.
66+
67+
Args:
68+
graph_id (str):
69+
report_id (str): Report definition ID
70+
71+
Raises:
72+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
73+
httpx.TimeoutException: If the request takes longer than Client.timeout.
74+
75+
Returns:
76+
Response[Any | HTTPValidationError]
77+
"""
78+
79+
kwargs = _get_kwargs(
80+
graph_id=graph_id,
81+
report_id=report_id,
82+
)
83+
84+
response = client.get_httpx_client().request(
85+
**kwargs,
86+
)
87+
88+
return _build_response(client=client, response=response)
89+
90+
91+
def sync(
92+
graph_id: str,
93+
report_id: str,
94+
*,
95+
client: AuthenticatedClient,
96+
) -> Any | HTTPValidationError | None:
97+
"""Delete Report
98+
99+
Delete a report definition and its generated facts.
100+
101+
Args:
102+
graph_id (str):
103+
report_id (str): Report definition ID
104+
105+
Raises:
106+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
107+
httpx.TimeoutException: If the request takes longer than Client.timeout.
108+
109+
Returns:
110+
Any | HTTPValidationError
111+
"""
112+
113+
return sync_detailed(
114+
graph_id=graph_id,
115+
report_id=report_id,
116+
client=client,
117+
).parsed
118+
119+
120+
async def asyncio_detailed(
121+
graph_id: str,
122+
report_id: str,
123+
*,
124+
client: AuthenticatedClient,
125+
) -> Response[Any | HTTPValidationError]:
126+
"""Delete Report
127+
128+
Delete a report definition and its generated facts.
129+
130+
Args:
131+
graph_id (str):
132+
report_id (str): Report definition ID
133+
134+
Raises:
135+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
136+
httpx.TimeoutException: If the request takes longer than Client.timeout.
137+
138+
Returns:
139+
Response[Any | HTTPValidationError]
140+
"""
141+
142+
kwargs = _get_kwargs(
143+
graph_id=graph_id,
144+
report_id=report_id,
145+
)
146+
147+
response = await client.get_async_httpx_client().request(**kwargs)
148+
149+
return _build_response(client=client, response=response)
150+
151+
152+
async def asyncio(
153+
graph_id: str,
154+
report_id: str,
155+
*,
156+
client: AuthenticatedClient,
157+
) -> Any | HTTPValidationError | None:
158+
"""Delete Report
159+
160+
Delete a report definition and its generated facts.
161+
162+
Args:
163+
graph_id (str):
164+
report_id (str): Report definition ID
165+
166+
Raises:
167+
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
168+
httpx.TimeoutException: If the request takes longer than Client.timeout.
169+
170+
Returns:
171+
Any | HTTPValidationError
172+
"""
173+
174+
return (
175+
await asyncio_detailed(
176+
graph_id=graph_id,
177+
report_id=report_id,
178+
client=client,
179+
)
180+
).parsed

0 commit comments

Comments
 (0)