From 30c74e6b94fa0d51831aefa11aaeba32ae78fe71 Mon Sep 17 00:00:00 2001 From: "Joseph T. French" Date: Mon, 28 Sep 2026 10:35:58 -0500 Subject: [PATCH] chore: regenerate for the graph mutation audit Adds api.graph_audit.list_graph_mutations and its models (MutationAuditEntry, MutationAuditListResponse and their enums), and picks up additive changes already on the API: the optional allow_unposted_source_events on close and backfill, the optional unposted-source-event fields on the fiscal calendar and in the GraphQL schema, and clearer operation descriptions. Claude-Session: https://claude.ai/code/session_01P2UGTUdaKBHMeEtP6myv4H --- .../api/content_operations/ingest_file.py | 12 +- .../api/graph_audit/__init__.py | 1 + .../api/graph_audit/list_graph_mutations.py | 381 ++++++++++++++++++ .../api/operations/cancel_operation.py | 12 +- .../create_mapping_association.py | 12 +- robosystems_client/graphql/schema.graphql | 12 +- robosystems_client/models/__init__.py | 10 + .../models/backfill_plan_history_operation.py | 11 + .../models/close_period_operation.py | 13 + robosystems_client/models/fact_set_lite.py | 7 +- .../models/fiscal_calendar_response.py | 25 ++ robosystems_client/models/ingest_file_op.py | 3 +- .../list_graph_mutations_surface_type_0.py | 10 + robosystems_client/models/materialize_op.py | 3 +- .../models/mutation_audit_entry.py | 283 +++++++++++++ .../models/mutation_audit_entry_status.py | 9 + .../models/mutation_audit_entry_surface.py | 10 + .../models/mutation_audit_list_response.py | 106 +++++ 18 files changed, 902 insertions(+), 18 deletions(-) create mode 100644 robosystems_client/api/graph_audit/__init__.py create mode 100644 robosystems_client/api/graph_audit/list_graph_mutations.py create mode 100644 robosystems_client/models/list_graph_mutations_surface_type_0.py create mode 100644 robosystems_client/models/mutation_audit_entry.py create mode 100644 robosystems_client/models/mutation_audit_entry_status.py create mode 100644 robosystems_client/models/mutation_audit_entry_surface.py create mode 100644 robosystems_client/models/mutation_audit_list_response.py diff --git a/robosystems_client/api/content_operations/ingest_file.py b/robosystems_client/api/content_operations/ingest_file.py index 6bb4580..910eff7 100644 --- a/robosystems_client/api/content_operations/ingest_file.py +++ b/robosystems_client/api/content_operations/ingest_file.py @@ -113,7 +113,8 @@ def sync_detailed( Mark an uploaded file ready and stage it into DuckDB. Small files stage directly (sync); large files stage via a background job (returns a pending envelope with an `operation_id` to monitor). Set - `ingest_to_graph` to auto-materialize into the graph after staging. + `ingest_to_graph` to auto-materialize into the graph after staging; that write takes the graph's + materialize lock, so it returns 409 while another materialization of the graph is running. **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. @@ -158,7 +159,8 @@ def sync( Mark an uploaded file ready and stage it into DuckDB. Small files stage directly (sync); large files stage via a background job (returns a pending envelope with an `operation_id` to monitor). Set - `ingest_to_graph` to auto-materialize into the graph after staging. + `ingest_to_graph` to auto-materialize into the graph after staging; that write takes the graph's + materialize lock, so it returns 409 while another materialization of the graph is running. **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. @@ -198,7 +200,8 @@ async def asyncio_detailed( Mark an uploaded file ready and stage it into DuckDB. Small files stage directly (sync); large files stage via a background job (returns a pending envelope with an `operation_id` to monitor). Set - `ingest_to_graph` to auto-materialize into the graph after staging. + `ingest_to_graph` to auto-materialize into the graph after staging; that write takes the graph's + materialize lock, so it returns 409 while another materialization of the graph is running. **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. @@ -241,7 +244,8 @@ async def asyncio( Mark an uploaded file ready and stage it into DuckDB. Small files stage directly (sync); large files stage via a background job (returns a pending envelope with an `operation_id` to monitor). Set - `ingest_to_graph` to auto-materialize into the graph after staging. + `ingest_to_graph` to auto-materialize into the graph after staging; that write takes the graph's + materialize lock, so it returns 409 while another materialization of the graph is running. **Idempotency**: supply an `Idempotency-Key` header to make safe retries; replays within 24 hours return the same envelope. Reusing the key with a different body returns HTTP 409 Conflict. diff --git a/robosystems_client/api/graph_audit/__init__.py b/robosystems_client/api/graph_audit/__init__.py new file mode 100644 index 0000000..2d7c0b2 --- /dev/null +++ b/robosystems_client/api/graph_audit/__init__.py @@ -0,0 +1 @@ +"""Contains endpoint functions for accessing the API""" diff --git a/robosystems_client/api/graph_audit/list_graph_mutations.py b/robosystems_client/api/graph_audit/list_graph_mutations.py new file mode 100644 index 0000000..4ccb2bb --- /dev/null +++ b/robosystems_client/api/graph_audit/list_graph_mutations.py @@ -0,0 +1,381 @@ +import datetime +from http import HTTPStatus +from typing import Any +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.error_response import ErrorResponse +from ...models.http_validation_error import HTTPValidationError +from ...models.list_graph_mutations_surface_type_0 import ListGraphMutationsSurfaceType0 +from ...models.mutation_audit_list_response import MutationAuditListResponse +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + graph_id: str, + *, + surface: ListGraphMutationsSurfaceType0 | None | Unset = UNSET, + operation_name: None | str | Unset = UNSET, + user_id: None | str | Unset = UNSET, + operation_id: None | str | Unset = UNSET, + since: datetime.datetime | None | Unset = UNSET, + until: datetime.datetime | None | Unset = UNSET, + cursor: None | str | Unset = UNSET, + limit: int | Unset = 50, +) -> dict[str, Any]: + + params: dict[str, Any] = {} + + json_surface: None | str | Unset + if isinstance(surface, Unset): + json_surface = UNSET + elif isinstance(surface, ListGraphMutationsSurfaceType0): + json_surface = surface.value + else: + json_surface = surface + params["surface"] = json_surface + + json_operation_name: None | str | Unset + if isinstance(operation_name, Unset): + json_operation_name = UNSET + else: + json_operation_name = operation_name + params["operation_name"] = json_operation_name + + json_user_id: None | str | Unset + if isinstance(user_id, Unset): + json_user_id = UNSET + else: + json_user_id = user_id + params["user_id"] = json_user_id + + json_operation_id: None | str | Unset + if isinstance(operation_id, Unset): + json_operation_id = UNSET + else: + json_operation_id = operation_id + params["operation_id"] = json_operation_id + + json_since: None | str | Unset + if isinstance(since, Unset): + json_since = UNSET + elif isinstance(since, datetime.datetime): + json_since = since.isoformat() + else: + json_since = since + params["since"] = json_since + + json_until: None | str | Unset + if isinstance(until, Unset): + json_until = UNSET + elif isinstance(until, datetime.datetime): + json_until = until.isoformat() + else: + json_until = until + params["until"] = json_until + + json_cursor: None | str | Unset + if isinstance(cursor, Unset): + json_cursor = UNSET + else: + json_cursor = cursor + params["cursor"] = json_cursor + + params["limit"] = limit + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/v1/graphs/{graph_id}/audit/mutations".format( + graph_id=quote(str(graph_id), safe=""), + ), + "params": params, + } + + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> ErrorResponse | HTTPValidationError | MutationAuditListResponse | None: + if response.status_code == 200: + response_200 = MutationAuditListResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 400: + response_400 = ErrorResponse.from_dict(response.json()) + + return response_400 + + if response.status_code == 401: + response_401 = ErrorResponse.from_dict(response.json()) + + return response_401 + + if response.status_code == 403: + response_403 = ErrorResponse.from_dict(response.json()) + + return response_403 + + if response.status_code == 404: + response_404 = ErrorResponse.from_dict(response.json()) + + return response_404 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if response.status_code == 429: + response_429 = ErrorResponse.from_dict(response.json()) + + return response_429 + + if response.status_code == 500: + response_500 = ErrorResponse.from_dict(response.json()) + + return response_500 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[ErrorResponse | HTTPValidationError | MutationAuditListResponse]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + graph_id: str, + *, + client: AuthenticatedClient, + surface: ListGraphMutationsSurfaceType0 | None | Unset = UNSET, + operation_name: None | str | Unset = UNSET, + user_id: None | str | Unset = UNSET, + operation_id: None | str | Unset = UNSET, + since: datetime.datetime | None | Unset = UNSET, + until: datetime.datetime | None | Unset = UNSET, + cursor: None | str | Unset = UNSET, + limit: int | Unset = 50, +) -> Response[ErrorResponse | HTTPValidationError | MutationAuditListResponse]: + """List Graph Mutations + + Every call that changed this graph, newest first: REST operations, external MCP clients, and in-app + AI operator runs. Each entry names the operation, the outcome, who made it and with which + credential, and the objects it touched. Arguments are not stored, only their SHA-256 fingerprint. + Requires graph admin. + + Args: + graph_id (str): Graph identifier + surface (ListGraphMutationsSurfaceType0 | None | Unset): Only calls from this surface + operation_name (None | str | Unset): Only this operation or MCP tool + user_id (None | str | Unset): Only calls made as this user + operation_id (None | str | Unset): Only calls from this REST operation or operator run + since (datetime.datetime | None | Unset): Only calls at or after this time + until (datetime.datetime | None | Unset): Only calls before this time + cursor (None | str | Unset): The `next_cursor` of the previous page + limit (int | Unset): Entries per page Default: 50. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ErrorResponse | HTTPValidationError | MutationAuditListResponse] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + surface=surface, + operation_name=operation_name, + user_id=user_id, + operation_id=operation_id, + since=since, + until=until, + cursor=cursor, + limit=limit, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + graph_id: str, + *, + client: AuthenticatedClient, + surface: ListGraphMutationsSurfaceType0 | None | Unset = UNSET, + operation_name: None | str | Unset = UNSET, + user_id: None | str | Unset = UNSET, + operation_id: None | str | Unset = UNSET, + since: datetime.datetime | None | Unset = UNSET, + until: datetime.datetime | None | Unset = UNSET, + cursor: None | str | Unset = UNSET, + limit: int | Unset = 50, +) -> ErrorResponse | HTTPValidationError | MutationAuditListResponse | None: + """List Graph Mutations + + Every call that changed this graph, newest first: REST operations, external MCP clients, and in-app + AI operator runs. Each entry names the operation, the outcome, who made it and with which + credential, and the objects it touched. Arguments are not stored, only their SHA-256 fingerprint. + Requires graph admin. + + Args: + graph_id (str): Graph identifier + surface (ListGraphMutationsSurfaceType0 | None | Unset): Only calls from this surface + operation_name (None | str | Unset): Only this operation or MCP tool + user_id (None | str | Unset): Only calls made as this user + operation_id (None | str | Unset): Only calls from this REST operation or operator run + since (datetime.datetime | None | Unset): Only calls at or after this time + until (datetime.datetime | None | Unset): Only calls before this time + cursor (None | str | Unset): The `next_cursor` of the previous page + limit (int | Unset): Entries per page Default: 50. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ErrorResponse | HTTPValidationError | MutationAuditListResponse + """ + + return sync_detailed( + graph_id=graph_id, + client=client, + surface=surface, + operation_name=operation_name, + user_id=user_id, + operation_id=operation_id, + since=since, + until=until, + cursor=cursor, + limit=limit, + ).parsed + + +async def asyncio_detailed( + graph_id: str, + *, + client: AuthenticatedClient, + surface: ListGraphMutationsSurfaceType0 | None | Unset = UNSET, + operation_name: None | str | Unset = UNSET, + user_id: None | str | Unset = UNSET, + operation_id: None | str | Unset = UNSET, + since: datetime.datetime | None | Unset = UNSET, + until: datetime.datetime | None | Unset = UNSET, + cursor: None | str | Unset = UNSET, + limit: int | Unset = 50, +) -> Response[ErrorResponse | HTTPValidationError | MutationAuditListResponse]: + """List Graph Mutations + + Every call that changed this graph, newest first: REST operations, external MCP clients, and in-app + AI operator runs. Each entry names the operation, the outcome, who made it and with which + credential, and the objects it touched. Arguments are not stored, only their SHA-256 fingerprint. + Requires graph admin. + + Args: + graph_id (str): Graph identifier + surface (ListGraphMutationsSurfaceType0 | None | Unset): Only calls from this surface + operation_name (None | str | Unset): Only this operation or MCP tool + user_id (None | str | Unset): Only calls made as this user + operation_id (None | str | Unset): Only calls from this REST operation or operator run + since (datetime.datetime | None | Unset): Only calls at or after this time + until (datetime.datetime | None | Unset): Only calls before this time + cursor (None | str | Unset): The `next_cursor` of the previous page + limit (int | Unset): Entries per page Default: 50. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[ErrorResponse | HTTPValidationError | MutationAuditListResponse] + """ + + kwargs = _get_kwargs( + graph_id=graph_id, + surface=surface, + operation_name=operation_name, + user_id=user_id, + operation_id=operation_id, + since=since, + until=until, + cursor=cursor, + limit=limit, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + graph_id: str, + *, + client: AuthenticatedClient, + surface: ListGraphMutationsSurfaceType0 | None | Unset = UNSET, + operation_name: None | str | Unset = UNSET, + user_id: None | str | Unset = UNSET, + operation_id: None | str | Unset = UNSET, + since: datetime.datetime | None | Unset = UNSET, + until: datetime.datetime | None | Unset = UNSET, + cursor: None | str | Unset = UNSET, + limit: int | Unset = 50, +) -> ErrorResponse | HTTPValidationError | MutationAuditListResponse | None: + """List Graph Mutations + + Every call that changed this graph, newest first: REST operations, external MCP clients, and in-app + AI operator runs. Each entry names the operation, the outcome, who made it and with which + credential, and the objects it touched. Arguments are not stored, only their SHA-256 fingerprint. + Requires graph admin. + + Args: + graph_id (str): Graph identifier + surface (ListGraphMutationsSurfaceType0 | None | Unset): Only calls from this surface + operation_name (None | str | Unset): Only this operation or MCP tool + user_id (None | str | Unset): Only calls made as this user + operation_id (None | str | Unset): Only calls from this REST operation or operator run + since (datetime.datetime | None | Unset): Only calls at or after this time + until (datetime.datetime | None | Unset): Only calls before this time + cursor (None | str | Unset): The `next_cursor` of the previous page + limit (int | Unset): Entries per page Default: 50. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + ErrorResponse | HTTPValidationError | MutationAuditListResponse + """ + + return ( + await asyncio_detailed( + graph_id=graph_id, + client=client, + surface=surface, + operation_name=operation_name, + user_id=user_id, + operation_id=operation_id, + since=since, + until=until, + cursor=cursor, + limit=limit, + ) + ).parsed diff --git a/robosystems_client/api/operations/cancel_operation.py b/robosystems_client/api/operations/cancel_operation.py index e9df82b..2a2cf6f 100644 --- a/robosystems_client/api/operations/cancel_operation.py +++ b/robosystems_client/api/operations/cancel_operation.py @@ -110,7 +110,8 @@ def sync_detailed( """Cancel Operation Cancels a pending or running operation. Emits a cancellation event to any active SSE connections. - Cannot cancel completed or failed operations. + Cannot cancel completed or failed operations, or a running operation that cannot be stopped (graph + creation, tier changes, materialization, period close), which returns 409. Args: operation_id (str): Operation identifier @@ -148,7 +149,8 @@ def sync( """Cancel Operation Cancels a pending or running operation. Emits a cancellation event to any active SSE connections. - Cannot cancel completed or failed operations. + Cannot cancel completed or failed operations, or a running operation that cannot be stopped (graph + creation, tier changes, materialization, period close), which returns 409. Args: operation_id (str): Operation identifier @@ -177,7 +179,8 @@ async def asyncio_detailed( """Cancel Operation Cancels a pending or running operation. Emits a cancellation event to any active SSE connections. - Cannot cancel completed or failed operations. + Cannot cancel completed or failed operations, or a running operation that cannot be stopped (graph + creation, tier changes, materialization, period close), which returns 409. Args: operation_id (str): Operation identifier @@ -213,7 +216,8 @@ async def asyncio( """Cancel Operation Cancels a pending or running operation. Emits a cancellation event to any active SSE connections. - Cannot cancel completed or failed operations. + Cannot cancel completed or failed operations, or a running operation that cannot be stopped (graph + creation, tier changes, materialization, period close), which returns 409. Args: operation_id (str): Operation identifier diff --git a/robosystems_client/api/robo_ledger_taxonomy_mapping/create_mapping_association.py b/robosystems_client/api/robo_ledger_taxonomy_mapping/create_mapping_association.py index 3e3cf44..5fe5d35 100644 --- a/robosystems_client/api/robo_ledger_taxonomy_mapping/create_mapping_association.py +++ b/robosystems_client/api/robo_ledger_taxonomy_mapping/create_mapping_association.py @@ -116,7 +116,8 @@ def sync_detailed( """Create Mapping Association Link a chart-of-accounts element to a US GAAP reporting concept. One mapping edge per call — use - `auto-map-elements` for bulk AI-assisted mapping. Duplicate (from, to, type) tuples return 409. Map + `auto-map-elements` for bulk AI-assisted mapping. Duplicate (from, to, type) tuples return 409. The + target must be a leaf concept: a subtotal the statement sums from its children is refused (422). Map before you close: an account with landed history in a closed month cannot be re-mapped (422) — the closed month's stamped statements were computed through the old arcs. Reopen latest-first down to the earliest month named, map, then close forward. @@ -165,7 +166,8 @@ def sync( """Create Mapping Association Link a chart-of-accounts element to a US GAAP reporting concept. One mapping edge per call — use - `auto-map-elements` for bulk AI-assisted mapping. Duplicate (from, to, type) tuples return 409. Map + `auto-map-elements` for bulk AI-assisted mapping. Duplicate (from, to, type) tuples return 409. The + target must be a leaf concept: a subtotal the statement sums from its children is refused (422). Map before you close: an account with landed history in a closed month cannot be re-mapped (422) — the closed month's stamped statements were computed through the old arcs. Reopen latest-first down to the earliest month named, map, then close forward. @@ -209,7 +211,8 @@ async def asyncio_detailed( """Create Mapping Association Link a chart-of-accounts element to a US GAAP reporting concept. One mapping edge per call — use - `auto-map-elements` for bulk AI-assisted mapping. Duplicate (from, to, type) tuples return 409. Map + `auto-map-elements` for bulk AI-assisted mapping. Duplicate (from, to, type) tuples return 409. The + target must be a leaf concept: a subtotal the statement sums from its children is refused (422). Map before you close: an account with landed history in a closed month cannot be re-mapped (422) — the closed month's stamped statements were computed through the old arcs. Reopen latest-first down to the earliest month named, map, then close forward. @@ -256,7 +259,8 @@ async def asyncio( """Create Mapping Association Link a chart-of-accounts element to a US GAAP reporting concept. One mapping edge per call — use - `auto-map-elements` for bulk AI-assisted mapping. Duplicate (from, to, type) tuples return 409. Map + `auto-map-elements` for bulk AI-assisted mapping. Duplicate (from, to, type) tuples return 409. The + target must be a leaf concept: a subtotal the statement sums from its children is refused (422). Map before you close: an account with landed history in a closed month cannot be re-mapped (422) — the closed month's stamped statements were computed through the old arcs. Reopen latest-first down to the earliest month named, map, then close forward. diff --git a/robosystems_client/graphql/schema.graphql b/robosystems_client/graphql/schema.graphql index d6a95d7..30e5212 100644 --- a/robosystems_client/graphql/schema.graphql +++ b/robosystems_client/graphql/schema.graphql @@ -2139,6 +2139,16 @@ type FiscalCalendar { Source identifiers (or event ids) of up to 5 unresolved reconciling items, so the blocker names what is holding the close. """ reconcilingItemSample: [String!]! + + """ + Source events dated in this period that were never committed: bank-feed lines still captured or classified, and QuickBooks transactions whose automatic posting failed. Commit or void each, or close over them knowingly with allow_unposted_source_events. + """ + unpostedSourceEventCount: Int! + + """ + Source identifiers (or event ids) of up to 5 unposted source events, so the blocker names what is holding the close. + """ + unpostedSourceEventSample: [String!]! lastCloseAt: DateTime initializedAt: DateTime @@ -2766,7 +2776,7 @@ type InformationBlockFactSet { scenarioId: String """ - Typed `FactProvenance` descriptor (discriminated on `origin`: pivot | schedule | derived | asserted | document | forecast | filed) recording how this FactSet's facts were constructed. Surfaced as JSON, mirroring how mechanics is exposed. Null when the FactSet carries no descriptor. + Typed `FactProvenance` descriptor (discriminated on `origin`: pivot | schedule | derived | asserted | document | forecast | filed) recording how this FactSet was constructed. It describes the set, not each fact: a `pivot` set's mapped leaves aggregate posted line items, while its subtotals and derivations are computed from those leaves. Surfaced as JSON, mirroring how mechanics is exposed. Null when the FactSet carries no descriptor. """ provenance: JSON } diff --git a/robosystems_client/models/__init__.py b/robosystems_client/models/__init__.py index 4e77ce7..8c5f64e 100644 --- a/robosystems_client/models/__init__.py +++ b/robosystems_client/models/__init__.py @@ -375,6 +375,7 @@ from .link_entity_taxonomy_request import LinkEntityTaxonomyRequest from .link_entity_taxonomy_request_basis import LinkEntityTaxonomyRequestBasis from .list_connections_provider_type_0 import ListConnectionsProviderType0 +from .list_graph_mutations_surface_type_0 import ListGraphMutationsSurfaceType0 from .list_org_graphs_response_200_item import ListOrgGraphsResponse200Item from .list_subgraphs_response import ListSubgraphsResponse from .list_table_files_response import ListTableFilesResponse @@ -395,6 +396,10 @@ from .mfa_status_response import MfaStatusResponse from .mfa_verify_request import MfaVerifyRequest from .mfa_verify_request_assertion_type_0 import MfaVerifyRequestAssertionType0 +from .mutation_audit_entry import MutationAuditEntry +from .mutation_audit_entry_status import MutationAuditEntryStatus +from .mutation_audit_entry_surface import MutationAuditEntrySurface +from .mutation_audit_list_response import MutationAuditListResponse from .o_auth_callback_request import OAuthCallbackRequest from .o_auth_callback_response import OAuthCallbackResponse from .o_auth_grant_info import OAuthGrantInfo @@ -1311,6 +1316,7 @@ "LinkEntityTaxonomyRequest", "LinkEntityTaxonomyRequestBasis", "ListConnectionsProviderType0", + "ListGraphMutationsSurfaceType0", "ListOrgGraphsResponse200Item", "ListSubgraphsResponse", "ListTableFilesResponse", @@ -1331,6 +1337,10 @@ "MfaStatusResponse", "MfaVerifyRequest", "MfaVerifyRequestAssertionType0", + "MutationAuditEntry", + "MutationAuditEntryStatus", + "MutationAuditEntrySurface", + "MutationAuditListResponse", "OAuthCallbackRequest", "OAuthCallbackResponse", "OAuthGrantInfo", diff --git a/robosystems_client/models/backfill_plan_history_operation.py b/robosystems_client/models/backfill_plan_history_operation.py index 695c78e..898c5fe 100644 --- a/robosystems_client/models/backfill_plan_history_operation.py +++ b/robosystems_client/models/backfill_plan_history_operation.py @@ -28,6 +28,9 @@ class BackfillPlanHistoryOperation: allow_reconciling_items (bool | Unset): Override the reconciling-item gate on each reclose. Only needed when an event inside the backfill window is still flagged as changed upstream and you have decided not to resolve it first. Default: False. + allow_unposted_source_events (bool | Unset): Override the unposted-source-event gate on each reclose. Only + needed when a source event inside the backfill window was never committed and you have decided not to commit or + void it first. Default: False. restamp (bool | Unset): Also re-derive months that ALREADY have canonical statement sets (default: skip them). Use after an engine improvement changes what a stamp produces — each month reruns the full reopen → reclose cycle and replaces its sets. A restamp run is not self-resuming (every month in range stays a candidate); @@ -40,6 +43,7 @@ class BackfillPlanHistoryOperation: allow_stale_sync: bool | Unset = False allow_stranded_obligations: bool | Unset = False allow_reconciling_items: bool | Unset = False + allow_unposted_source_events: bool | Unset = False restamp: bool | Unset = False note: None | str | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) @@ -59,6 +63,8 @@ def to_dict(self) -> dict[str, Any]: allow_reconciling_items = self.allow_reconciling_items + allow_unposted_source_events = self.allow_unposted_source_events + restamp = self.restamp note: None | str | Unset @@ -80,6 +86,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["allow_stranded_obligations"] = allow_stranded_obligations if allow_reconciling_items is not UNSET: field_dict["allow_reconciling_items"] = allow_reconciling_items + if allow_unposted_source_events is not UNSET: + field_dict["allow_unposted_source_events"] = allow_unposted_source_events if restamp is not UNSET: field_dict["restamp"] = restamp if note is not UNSET: @@ -108,6 +116,8 @@ def _parse_start_period(data: object) -> None | str | Unset: allow_reconciling_items = d.pop("allow_reconciling_items", UNSET) + allow_unposted_source_events = d.pop("allow_unposted_source_events", UNSET) + restamp = d.pop("restamp", UNSET) def _parse_note(data: object) -> None | str | Unset: @@ -125,6 +135,7 @@ def _parse_note(data: object) -> None | str | Unset: allow_stale_sync=allow_stale_sync, allow_stranded_obligations=allow_stranded_obligations, allow_reconciling_items=allow_reconciling_items, + allow_unposted_source_events=allow_unposted_source_events, restamp=restamp, note=note, ) diff --git a/robosystems_client/models/close_period_operation.py b/robosystems_client/models/close_period_operation.py index 8ef4ebc..df32eed 100644 --- a/robosystems_client/models/close_period_operation.py +++ b/robosystems_client/models/close_period_operation.py @@ -30,6 +30,11 @@ class ClosePeriodOperation: the period are still flagged as changed in the source system, leaving those differences undecided. The next sync will still report them, and the statements stamped by this close may disagree with the source. Prefer resolve- reconciling-item on each first. The override is recorded in the close audit note. Default: False. + allow_unposted_source_events (bool | Unset): Override the unposted-source-event gate — close even though source + events dated in the period (bank-feed lines, QuickBooks transactions whose automatic posting failed) were never + committed. Once the period closes they cannot post into it, so the stamped statements leave them out. Prefer + classifying and committing, or voiding, each first. The override is recorded in the close audit note. Default: + False. """ period: str @@ -37,6 +42,7 @@ class ClosePeriodOperation: allow_stale_sync: bool | Unset = False allow_stranded_obligations: bool | Unset = False allow_reconciling_items: bool | Unset = False + allow_unposted_source_events: bool | Unset = False additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -54,6 +60,8 @@ def to_dict(self) -> dict[str, Any]: allow_reconciling_items = self.allow_reconciling_items + allow_unposted_source_events = self.allow_unposted_source_events + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -69,6 +77,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["allow_stranded_obligations"] = allow_stranded_obligations if allow_reconciling_items is not UNSET: field_dict["allow_reconciling_items"] = allow_reconciling_items + if allow_unposted_source_events is not UNSET: + field_dict["allow_unposted_source_events"] = allow_unposted_source_events return field_dict @@ -92,12 +102,15 @@ def _parse_note(data: object) -> None | str | Unset: allow_reconciling_items = d.pop("allow_reconciling_items", UNSET) + allow_unposted_source_events = d.pop("allow_unposted_source_events", UNSET) + close_period_operation = cls( period=period, note=note, allow_stale_sync=allow_stale_sync, allow_stranded_obligations=allow_stranded_obligations, allow_reconciling_items=allow_reconciling_items, + allow_unposted_source_events=allow_unposted_source_events, ) close_period_operation.additional_properties = d diff --git a/robosystems_client/models/fact_set_lite.py b/robosystems_client/models/fact_set_lite.py index 803f675..38738a3 100644 --- a/robosystems_client/models/fact_set_lite.py +++ b/robosystems_client/models/fact_set_lite.py @@ -36,9 +36,10 @@ class FactSetLite: scenario_id (None | str | Unset): Scenario axis (the forecast engine). NULL = actuals; non-NULL names the owning forecast block whose parallel universe this set belongs to. provenance (FactSetLiteProvenanceType0 | None | Unset): Typed `FactProvenance` descriptor (discriminated on - `origin`: pivot | schedule | derived | asserted | document | forecast | filed) recording how this FactSet's - facts were constructed. Surfaced as JSON, mirroring how mechanics is exposed. Null when the FactSet carries no - descriptor. + `origin`: pivot | schedule | derived | asserted | document | forecast | filed) recording how this FactSet was + constructed. It describes the set, not each fact: a `pivot` set's mapped leaves aggregate posted line items, + while its subtotals and derivations are computed from those leaves. Surfaced as JSON, mirroring how mechanics is + exposed. Null when the FactSet carries no descriptor. """ id: str diff --git a/robosystems_client/models/fiscal_calendar_response.py b/robosystems_client/models/fiscal_calendar_response.py index a797c1a..c94a7a9 100644 --- a/robosystems_client/models/fiscal_calendar_response.py +++ b/robosystems_client/models/fiscal_calendar_response.py @@ -57,6 +57,11 @@ class FiscalCalendarResponse: with resolve-reconciling-item, or close over them knowingly with allow_reconciling_items. Default: 0. reconciling_item_sample (list[str] | Unset): Source identifiers (or event ids) of up to 5 unresolved reconciling items, so the blocker names what is holding the close. + unposted_source_event_count (int | Unset): Source events dated in this period that were never committed: bank- + feed lines still captured or classified, and QuickBooks transactions whose automatic posting failed. Commit or + void each, or close over them knowingly with allow_unposted_source_events. Default: 0. + unposted_source_event_sample (list[str] | Unset): Source identifiers (or event ids) of up to 5 unposted source + events, so the blocker names what is holding the close. last_close_at (datetime.datetime | None | Unset): initialized_at (datetime.datetime | None | Unset): last_sync_at (datetime.datetime | None | Unset): Most recent QB sync timestamp (if connected) @@ -79,6 +84,8 @@ class FiscalCalendarResponse: stranded_obligation_sample: list[PendingObligationDetailResponse] | Unset = UNSET reconciling_item_count: int | Unset = 0 reconciling_item_sample: list[str] | Unset = UNSET + unposted_source_event_count: int | Unset = 0 + unposted_source_event_sample: list[str] | Unset = UNSET last_close_at: datetime.datetime | None | Unset = UNSET initialized_at: datetime.datetime | None | Unset = UNSET last_sync_at: datetime.datetime | None | Unset = UNSET @@ -150,6 +157,12 @@ def to_dict(self) -> dict[str, Any]: if not isinstance(self.reconciling_item_sample, Unset): reconciling_item_sample = self.reconciling_item_sample + unposted_source_event_count = self.unposted_source_event_count + + unposted_source_event_sample: list[str] | Unset = UNSET + if not isinstance(self.unposted_source_event_sample, Unset): + unposted_source_event_sample = self.unposted_source_event_sample + last_close_at: None | str | Unset if isinstance(self.last_close_at, Unset): last_close_at = UNSET @@ -217,6 +230,10 @@ def to_dict(self) -> dict[str, Any]: field_dict["reconciling_item_count"] = reconciling_item_count if reconciling_item_sample is not UNSET: field_dict["reconciling_item_sample"] = reconciling_item_sample + if unposted_source_event_count is not UNSET: + field_dict["unposted_source_event_count"] = unposted_source_event_count + if unposted_source_event_sample is not UNSET: + field_dict["unposted_source_event_sample"] = unposted_source_event_sample if last_close_at is not UNSET: field_dict["last_close_at"] = last_close_at if initialized_at is not UNSET: @@ -316,6 +333,12 @@ def _parse_sync_stale_days(data: object) -> int | None | Unset: reconciling_item_sample = cast(list[str], d.pop("reconciling_item_sample", UNSET)) + unposted_source_event_count = d.pop("unposted_source_event_count", UNSET) + + unposted_source_event_sample = cast( + list[str], d.pop("unposted_source_event_sample", UNSET) + ) + def _parse_last_close_at(data: object) -> datetime.datetime | None | Unset: if data is None: return data @@ -393,6 +416,8 @@ def _parse_last_sync_at(data: object) -> datetime.datetime | None | Unset: stranded_obligation_sample=stranded_obligation_sample, reconciling_item_count=reconciling_item_count, reconciling_item_sample=reconciling_item_sample, + unposted_source_event_count=unposted_source_event_count, + unposted_source_event_sample=unposted_source_event_sample, last_close_at=last_close_at, initialized_at=initialized_at, last_sync_at=last_sync_at, diff --git a/robosystems_client/models/ingest_file_op.py b/robosystems_client/models/ingest_file_op.py index b2066ed..2df0538 100644 --- a/robosystems_client/models/ingest_file_op.py +++ b/robosystems_client/models/ingest_file_op.py @@ -20,7 +20,8 @@ class IngestFileOp: Attributes: file_id (str): Uploaded file id to ingest - ingest_to_graph (bool | Unset): Auto-materialize into the graph after DuckDB staging Default: False. + ingest_to_graph (bool | Unset): Auto-materialize into the graph after DuckDB staging. Refused with 409 while + another materialization of the graph is running. Default: False. """ file_id: str diff --git a/robosystems_client/models/list_graph_mutations_surface_type_0.py b/robosystems_client/models/list_graph_mutations_surface_type_0.py new file mode 100644 index 0000000..fe511a6 --- /dev/null +++ b/robosystems_client/models/list_graph_mutations_surface_type_0.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class ListGraphMutationsSurfaceType0(str, Enum): + API = "api" + MCP = "mcp" + OPERATOR = "operator" + + def __str__(self) -> str: + return str(self.value) diff --git a/robosystems_client/models/materialize_op.py b/robosystems_client/models/materialize_op.py index f1c13d0..14cbe89 100644 --- a/robosystems_client/models/materialize_op.py +++ b/robosystems_client/models/materialize_op.py @@ -19,7 +19,8 @@ class MaterializeOp: force (bool | Unset): Force materialization even if already up to date Default: False. rebuild (bool | Unset): Rebuild the graph from scratch, dropping existing data. Required (staged source) when materializing new uploads into a graph that already contains materialized data — staging replays all uploaded - files, so a non-rebuild pass would re-copy ingested rows (409). Default: False. + files, so a non-rebuild pass would re-copy ingested rows (409). An extensions source always rebuilds a graph + that already exists: the new copy is built alongside and swapped in, regardless of this flag. Default: False. dry_run (bool | Unset): Validate tables without writing to the graph Default: False. source (None | str | Unset): Materialization source: 'extensions' for OLTP, omit for DuckDB staging tables materialize_embeddings (bool | Unset): Generate vector embeddings during materialization Default: False. diff --git a/robosystems_client/models/mutation_audit_entry.py b/robosystems_client/models/mutation_audit_entry.py new file mode 100644 index 0000000..b966dbf --- /dev/null +++ b/robosystems_client/models/mutation_audit_entry.py @@ -0,0 +1,283 @@ +from __future__ import annotations + +import datetime +from collections.abc import Mapping +from typing import Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..models.mutation_audit_entry_status import MutationAuditEntryStatus +from ..models.mutation_audit_entry_surface import MutationAuditEntrySurface +from ..types import UNSET, Unset + +T = TypeVar("T", bound="MutationAuditEntry") + + +@_attrs_define +class MutationAuditEntry: + """One mutating call on the graph. + + Attributes: + id (str): Audit entry identifier + occurred_at (datetime.datetime): When the call finished + surface (MutationAuditEntrySurface): Where the call came from: 'api' for a REST operation, 'mcp' for an external + MCP client, 'operator' for an in-app AI operator run such as the console's /do + operation_name (str): The operation or MCP tool that ran + status (MutationAuditEntryStatus): Whether the call succeeded; a failed call changed nothing it reports + duration_ms (float): How long the call took + error_code (None | str | Unset): Why a failed call failed + user_id (None | str | Unset): The user the call ran as + auth_method (None | str | Unset): How the caller authenticated, for example 'api_key' or 'oauth' + api_key_prefix (None | str | Unset): The first characters of the API key used, when one was + request_id (None | str | Unset): The HTTP request that made the call + operation_id (None | str | Unset): The REST operation's envelope id, or the operator run's operation id: every + write an operator run makes shares it + operator_type (None | str | Unset): The operator that made the call, for surface 'operator' + arguments_fingerprint (None | str | Unset): SHA-256 of the call's arguments. The arguments themselves are not + stored + object_ids (list[str] | Unset): Identifiers of the objects the call touched + """ + + id: str + occurred_at: datetime.datetime + surface: MutationAuditEntrySurface + operation_name: str + status: MutationAuditEntryStatus + duration_ms: float + error_code: None | str | Unset = UNSET + user_id: None | str | Unset = UNSET + auth_method: None | str | Unset = UNSET + api_key_prefix: None | str | Unset = UNSET + request_id: None | str | Unset = UNSET + operation_id: None | str | Unset = UNSET + operator_type: None | str | Unset = UNSET + arguments_fingerprint: None | str | Unset = UNSET + object_ids: list[str] | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + id = self.id + + occurred_at = self.occurred_at.isoformat() + + surface = self.surface.value + + operation_name = self.operation_name + + status = self.status.value + + duration_ms = self.duration_ms + + error_code: None | str | Unset + if isinstance(self.error_code, Unset): + error_code = UNSET + else: + error_code = self.error_code + + user_id: None | str | Unset + if isinstance(self.user_id, Unset): + user_id = UNSET + else: + user_id = self.user_id + + auth_method: None | str | Unset + if isinstance(self.auth_method, Unset): + auth_method = UNSET + else: + auth_method = self.auth_method + + api_key_prefix: None | str | Unset + if isinstance(self.api_key_prefix, Unset): + api_key_prefix = UNSET + else: + api_key_prefix = self.api_key_prefix + + request_id: None | str | Unset + if isinstance(self.request_id, Unset): + request_id = UNSET + else: + request_id = self.request_id + + operation_id: None | str | Unset + if isinstance(self.operation_id, Unset): + operation_id = UNSET + else: + operation_id = self.operation_id + + operator_type: None | str | Unset + if isinstance(self.operator_type, Unset): + operator_type = UNSET + else: + operator_type = self.operator_type + + arguments_fingerprint: None | str | Unset + if isinstance(self.arguments_fingerprint, Unset): + arguments_fingerprint = UNSET + else: + arguments_fingerprint = self.arguments_fingerprint + + object_ids: list[str] | Unset = UNSET + if not isinstance(self.object_ids, Unset): + object_ids = self.object_ids + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "id": id, + "occurred_at": occurred_at, + "surface": surface, + "operation_name": operation_name, + "status": status, + "duration_ms": duration_ms, + } + ) + if error_code is not UNSET: + field_dict["error_code"] = error_code + if user_id is not UNSET: + field_dict["user_id"] = user_id + if auth_method is not UNSET: + field_dict["auth_method"] = auth_method + if api_key_prefix is not UNSET: + field_dict["api_key_prefix"] = api_key_prefix + if request_id is not UNSET: + field_dict["request_id"] = request_id + if operation_id is not UNSET: + field_dict["operation_id"] = operation_id + if operator_type is not UNSET: + field_dict["operator_type"] = operator_type + if arguments_fingerprint is not UNSET: + field_dict["arguments_fingerprint"] = arguments_fingerprint + if object_ids is not UNSET: + field_dict["object_ids"] = object_ids + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + id = d.pop("id") + + occurred_at = datetime.datetime.fromisoformat(d.pop("occurred_at")) + + surface = MutationAuditEntrySurface(d.pop("surface")) + + operation_name = d.pop("operation_name") + + status = MutationAuditEntryStatus(d.pop("status")) + + duration_ms = d.pop("duration_ms") + + def _parse_error_code(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + error_code = _parse_error_code(d.pop("error_code", UNSET)) + + def _parse_user_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + user_id = _parse_user_id(d.pop("user_id", UNSET)) + + def _parse_auth_method(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + auth_method = _parse_auth_method(d.pop("auth_method", UNSET)) + + def _parse_api_key_prefix(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + api_key_prefix = _parse_api_key_prefix(d.pop("api_key_prefix", UNSET)) + + def _parse_request_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + request_id = _parse_request_id(d.pop("request_id", UNSET)) + + def _parse_operation_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + operation_id = _parse_operation_id(d.pop("operation_id", UNSET)) + + def _parse_operator_type(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + operator_type = _parse_operator_type(d.pop("operator_type", UNSET)) + + def _parse_arguments_fingerprint(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + arguments_fingerprint = _parse_arguments_fingerprint( + d.pop("arguments_fingerprint", UNSET) + ) + + object_ids = cast(list[str], d.pop("object_ids", UNSET)) + + mutation_audit_entry = cls( + id=id, + occurred_at=occurred_at, + surface=surface, + operation_name=operation_name, + status=status, + duration_ms=duration_ms, + error_code=error_code, + user_id=user_id, + auth_method=auth_method, + api_key_prefix=api_key_prefix, + request_id=request_id, + operation_id=operation_id, + operator_type=operator_type, + arguments_fingerprint=arguments_fingerprint, + object_ids=object_ids, + ) + + mutation_audit_entry.additional_properties = d + return mutation_audit_entry + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/robosystems_client/models/mutation_audit_entry_status.py b/robosystems_client/models/mutation_audit_entry_status.py new file mode 100644 index 0000000..f40a730 --- /dev/null +++ b/robosystems_client/models/mutation_audit_entry_status.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class MutationAuditEntryStatus(str, Enum): + COMPLETED = "completed" + FAILED = "failed" + + def __str__(self) -> str: + return str(self.value) diff --git a/robosystems_client/models/mutation_audit_entry_surface.py b/robosystems_client/models/mutation_audit_entry_surface.py new file mode 100644 index 0000000..23587d7 --- /dev/null +++ b/robosystems_client/models/mutation_audit_entry_surface.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class MutationAuditEntrySurface(str, Enum): + API = "api" + MCP = "mcp" + OPERATOR = "operator" + + def __str__(self) -> str: + return str(self.value) diff --git a/robosystems_client/models/mutation_audit_list_response.py b/robosystems_client/models/mutation_audit_list_response.py new file mode 100644 index 0000000..d45cd18 --- /dev/null +++ b/robosystems_client/models/mutation_audit_list_response.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.mutation_audit_entry import MutationAuditEntry + + +T = TypeVar("T", bound="MutationAuditListResponse") + + +@_attrs_define +class MutationAuditListResponse: + """A page of the graph's mutation audit, newest first. + + Attributes: + graph_id (str): + entries (list[MutationAuditEntry]): + next_cursor (None | str | Unset): Pass as `cursor` for the next, older page; null on the last page + """ + + graph_id: str + entries: list[MutationAuditEntry] + next_cursor: None | str | Unset = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + graph_id = self.graph_id + + entries = [] + for entries_item_data in self.entries: + entries_item = entries_item_data.to_dict() + entries.append(entries_item) + + next_cursor: None | str | Unset + if isinstance(self.next_cursor, Unset): + next_cursor = UNSET + else: + next_cursor = self.next_cursor + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "graph_id": graph_id, + "entries": entries, + } + ) + if next_cursor is not UNSET: + field_dict["next_cursor"] = next_cursor + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.mutation_audit_entry import MutationAuditEntry + + d = dict(src_dict) + graph_id = d.pop("graph_id") + + entries = [] + _entries = d.pop("entries") + for entries_item_data in _entries: + entries_item = MutationAuditEntry.from_dict(entries_item_data) + + entries.append(entries_item) + + def _parse_next_cursor(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + next_cursor = _parse_next_cursor(d.pop("next_cursor", UNSET)) + + mutation_audit_list_response = cls( + graph_id=graph_id, + entries=entries, + next_cursor=next_cursor, + ) + + mutation_audit_list_response.additional_properties = d + return mutation_audit_list_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties