Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions robosystems_client/clients/ledger_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,12 @@
from ..graphql.generated.list_ledger_blocked_source_graphs import (
ListLedgerBlockedSourceGraphsBlockedSourceGraphs as BlockedSourceGraphsPage,
)
from ..graphql.generated.list_ledger_report_shares import (
ListLedgerReportShares,
)
from ..graphql.generated.list_ledger_report_shares import (
ListLedgerReportSharesReportShares as ReportSharesPage,
)
from ..graphql.generated.list_ledger_agents import (
ListLedgerAgents,
ListLedgerAgentsAgents,
Expand Down Expand Up @@ -528,6 +534,7 @@
LIST_INFORMATION_BLOCKS_GQL,
LIST_LEDGER_ACCOUNTS_GQL,
LIST_LEDGER_BLOCKED_SOURCE_GRAPHS_GQL,
LIST_LEDGER_REPORT_SHARES_GQL,
LIST_LEDGER_AGENTS_GQL,
LIST_LEDGER_DIMENSIONS_GQL,
LIST_LEDGER_ELEMENTS_GQL,
Expand Down Expand Up @@ -3535,6 +3542,34 @@ def revoke_report_share(
"Revoke report share", envelope, RevokeReportShareResponse
)

def list_report_shares(
self,
graph_id: str,
report_id: str | None = None,
include_revoked: bool = False,
limit: int = 100,
offset: int = 0,
) -> ReportSharesPage | None:
"""Where this graph's reports have been shared — the sender's record
behind `share_report` and `revoke_report_share`.

`report_id` narrows to one report; omitted, the list is the graph's whole
distribution log. Revoked shares stay out unless `include_revoked` is set:
a recipient shared to, revoked, and shared to again has one revoked row
and one live.
"""
data = self._query(
graph_id,
LIST_LEDGER_REPORT_SHARES_GQL,
{
"reportId": report_id,
"includeRevoked": include_revoked,
"limit": limit,
"offset": offset,
},
)
return ListLedgerReportShares.model_validate(data).report_shares

# ── Blocked source graphs ────────────────────────────────────────────
#
# Sharing is authorized capability-style: whoever holds this graph's id
Expand Down
12 changes: 12 additions & 0 deletions robosystems_client/graphql/generated/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,12 @@
ListLedgerReconciliationsReconciliationsReconciliationsDifferencesRollForward,
ListLedgerReconciliationsReconciliationsReconciliationsRollForward,
)
from .list_ledger_report_shares import (
ListLedgerReportShares,
ListLedgerReportSharesReportShares,
ListLedgerReportSharesReportSharesPagination,
ListLedgerReportSharesReportSharesReportShares,
)
from .list_ledger_reports import (
ListLedgerReports,
ListLedgerReportsReports,
Expand Down Expand Up @@ -456,6 +462,7 @@
LIST_LEDGER_MAPPINGS_GQL,
LIST_LEDGER_PUBLISH_LISTS_GQL,
LIST_LEDGER_RECONCILIATIONS_GQL,
LIST_LEDGER_REPORT_SHARES_GQL,
LIST_LEDGER_REPORTS_GQL,
LIST_LEDGER_STRUCTURES_GQL,
LIST_LEDGER_TAXONOMIES_GQL,
Expand Down Expand Up @@ -716,6 +723,7 @@
"LIST_LEDGER_PUBLISH_LISTS_GQL",
"LIST_LEDGER_RECONCILIATIONS_GQL",
"LIST_LEDGER_REPORTS_GQL",
"LIST_LEDGER_REPORT_SHARES_GQL",
"LIST_LEDGER_STRUCTURES_GQL",
"LIST_LEDGER_TAXONOMIES_GQL",
"LIST_LEDGER_TRANSACTIONS_GQL",
Expand Down Expand Up @@ -806,6 +814,10 @@
"ListLedgerReconciliationsReconciliationsReconciliationsDifferencesComponents",
"ListLedgerReconciliationsReconciliationsReconciliationsDifferencesRollForward",
"ListLedgerReconciliationsReconciliationsReconciliationsRollForward",
"ListLedgerReportShares",
"ListLedgerReportSharesReportShares",
"ListLedgerReportSharesReportSharesPagination",
"ListLedgerReportSharesReportSharesReportShares",
"ListLedgerReports",
"ListLedgerReportsReports",
"ListLedgerReportsReportsReports",
Expand Down
25 changes: 25 additions & 0 deletions robosystems_client/graphql/generated/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
from .list_ledger_mappings import ListLedgerMappings
from .list_ledger_publish_lists import ListLedgerPublishLists
from .list_ledger_reconciliations import ListLedgerReconciliations
from .list_ledger_report_shares import ListLedgerReportShares
from .list_ledger_reports import ListLedgerReports
from .list_ledger_structures import ListLedgerStructures
from .list_ledger_taxonomies import ListLedgerTaxonomies
Expand Down Expand Up @@ -125,6 +126,7 @@
LIST_LEDGER_MAPPINGS_GQL,
LIST_LEDGER_PUBLISH_LISTS_GQL,
LIST_LEDGER_RECONCILIATIONS_GQL,
LIST_LEDGER_REPORT_SHARES_GQL,
LIST_LEDGER_REPORTS_GQL,
LIST_LEDGER_STRUCTURES_GQL,
LIST_LEDGER_TAXONOMIES_GQL,
Expand Down Expand Up @@ -1016,6 +1018,29 @@ def list_ledger_reconciliations(
data = self.get_data(response)
return ListLedgerReconciliations.model_validate(data)

def list_ledger_report_shares(
self,
include_revoked: bool,
limit: int,
offset: int,
report_id: Union[Optional[str], UnsetType] = UNSET,
**kwargs: Any,
) -> ListLedgerReportShares:
variables: dict[str, object] = {
"reportId": report_id,
"includeRevoked": include_revoked,
"limit": limit,
"offset": offset,
}
response = self.execute(
query=LIST_LEDGER_REPORT_SHARES_GQL,
operation_name="ListLedgerReportShares",
variables=variables,
**kwargs,
)
data = self.get_data(response)
return ListLedgerReportShares.model_validate(data)

def list_ledger_reports(
self,
lifecycle: Union[Optional[ReportLifecycle], UnsetType] = UNSET,
Expand Down
40 changes: 40 additions & 0 deletions robosystems_client/graphql/generated/list_ledger_report_shares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from typing import Optional

from pydantic import Field

from .base_model import BaseModel


class ListLedgerReportShares(BaseModel):
report_shares: Optional["ListLedgerReportSharesReportShares"] = Field(
alias="reportShares"
)


class ListLedgerReportSharesReportShares(BaseModel):
report_shares: list["ListLedgerReportSharesReportSharesReportShares"] = Field(
alias="reportShares"
)
pagination: "ListLedgerReportSharesReportSharesPagination"


class ListLedgerReportSharesReportSharesReportShares(BaseModel):
id: str
report_id: str = Field(alias="reportId")
target_graph_id: str = Field(alias="targetGraphId")
target_graph_name: Optional[str] = Field(alias="targetGraphName")
shared_by: str = Field(alias="sharedBy")
shared_at: str = Field(alias="sharedAt")
revoked_at: Optional[str] = Field(alias="revokedAt")
fact_count: int = Field(alias="factCount")


class ListLedgerReportSharesReportSharesPagination(BaseModel):
total: int
limit: int
offset: int
has_more: bool = Field(alias="hasMore")


ListLedgerReportShares.model_rebuild()
ListLedgerReportSharesReportShares.model_rebuild()
29 changes: 29 additions & 0 deletions robosystems_client/graphql/generated/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"LIST_LEDGER_PUBLISH_LISTS_GQL",
"LIST_LEDGER_RECONCILIATIONS_GQL",
"LIST_LEDGER_REPORTS_GQL",
"LIST_LEDGER_REPORT_SHARES_GQL",
"LIST_LEDGER_STRUCTURES_GQL",
"LIST_LEDGER_TAXONOMIES_GQL",
"LIST_LEDGER_TRANSACTIONS_GQL",
Expand Down Expand Up @@ -2199,6 +2200,34 @@
}
"""

LIST_LEDGER_REPORT_SHARES_GQL = """
query ListLedgerReportShares($reportId: String, $includeRevoked: Boolean! = false, $limit: Int! = 100, $offset: Int! = 0) {
reportShares(
reportId: $reportId
includeRevoked: $includeRevoked
limit: $limit
offset: $offset
) {
reportShares {
id
reportId
targetGraphId
targetGraphName
sharedBy
sharedAt
revokedAt
factCount
}
pagination {
total
limit
offset
hasMore
}
}
}
"""

LIST_LEDGER_REPORTS_GQL = """
query ListLedgerReports($lifecycle: ReportLifecycle, $entityId: String) {
reports(lifecycle: $lifecycle, entityId: $entityId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
query ListLedgerReportShares(
$reportId: String
$includeRevoked: Boolean! = false
$limit: Int! = 100
$offset: Int! = 0
) {
reportShares(
reportId: $reportId
includeRevoked: $includeRevoked
limit: $limit
offset: $offset
) {
reportShares {
id reportId targetGraphId targetGraphName
sharedBy sharedAt revokedAt factCount
}
pagination { total limit offset hasMore }
}
}
66 changes: 66 additions & 0 deletions robosystems_client/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,34 @@ type Query {
listId: String!
): PublishListDetail

"""
Where this graph's reports have been shared: each recipient graph, when,
by whom, how many facts it received, and whether the share was revoked.

The sender's record behind `share-report` and `revoke-report-share`.
Revoked shares are history: a recipient shared to, revoked, and shared to
again has two rows, one revoked and one live.
"""
reportShares(
"""One report's shares. Omit for the graph's whole distribution log."""
reportId: String = null

"""
Also list revoked shares. Off by default, so the list is the copies recipients still hold.
"""
includeRevoked: Boolean! = false

"""
Maximum rows to return, 1-1000. Omit to use this field's own default; out of range raises `INVALID_PAGINATION`.
"""
limit: Int = null

"""
Rows to skip before returning, 0 or greater. Out of range raises `INVALID_PAGINATION`.
"""
offset: Int = null
): ReportShareList

"""
Source graphs barred from sharing reports into this graph.

Expand Down Expand Up @@ -4626,6 +4654,44 @@ type PublishListMember {
addedAt: DateTime!
}

"""Paginated list of a report's shares, newest first."""
type ReportShareList {
"""Shares, newest first."""
reportShares: [ReportShare!]!

"""Pagination metadata."""
pagination: PaginationInfo!
}

"""One delivery of a report to a recipient graph."""
type ReportShare {
"""Share row identifier (ULID)."""
id: String!

"""The shared report's ID in this graph."""
reportId: String!

"""The recipient's graph ID."""
targetGraphId: String!

"""Display name of the recipient graph (if known)."""
targetGraphName: String

"""User ID that shared the report."""
sharedBy: String!

"""When the report was shared, or last re-shared."""
sharedAt: DateTime!

"""
When the share was revoked. Null while the recipient holds the copy; a re-share after a revoke is a new row, so a revoked row stays revoked.
"""
revokedAt: DateTime

"""Number of facts delivered to the recipient on this share."""
factCount: Int!
}

"""Paginated list of blocked source graphs."""
type BlockedSourceGraphList {
"""Blocked source graphs."""
Expand Down
61 changes: 61 additions & 0 deletions tests/test_ledger_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3599,6 +3599,67 @@ def test_list_blocked_source_graphs(self, mock_query, mock_config, graph_id):
assert page.blocked_source_graphs[0].source_graph_id == "kg_sender"
assert page.pagination.total == 1

@patch("robosystems_client.clients.ledger_client.LedgerClient._query")
def test_list_report_shares(self, mock_query, mock_config, graph_id):
mock_query.return_value = {
"reportShares": {
"reportShares": [
{
"id": "share_2",
"reportId": "rpt_1",
"targetGraphId": "kg_recipient",
"targetGraphName": "Harbor Fund",
"sharedBy": "usr_1",
"sharedAt": "2026-10-01T12:00:00Z",
"revokedAt": None,
"factCount": 47,
},
{
"id": "share_1",
"reportId": "rpt_1",
"targetGraphId": "kg_recipient",
"targetGraphName": "Harbor Fund",
"sharedBy": "usr_1",
"sharedAt": "2026-09-01T12:00:00Z",
"revokedAt": "2026-09-15T12:00:00Z",
"factCount": 40,
},
],
"pagination": {"total": 2, "limit": 100, "offset": 0, "hasMore": False},
}
}
client = LedgerClient(mock_config)
page = client.list_report_shares(graph_id, report_id="rpt_1", include_revoked=True)
assert page is not None
assert [s.id for s in page.report_shares] == ["share_2", "share_1"]
assert page.report_shares[0].revoked_at is None
assert page.report_shares[1].revoked_at is not None
assert page.report_shares[0].fact_count == 47
assert page.pagination.total == 2
assert mock_query.call_args.args[2] == {
"reportId": "rpt_1",
"includeRevoked": True,
"limit": 100,
"offset": 0,
}

@patch("robosystems_client.clients.ledger_client.LedgerClient._query")
def test_list_report_shares_defaults_to_live_shares_of_every_report(
self, mock_query, mock_config, graph_id
):
mock_query.return_value = {
"reportShares": {
"reportShares": [],
"pagination": {"total": 0, "limit": 100, "offset": 0, "hasMore": False},
}
}
client = LedgerClient(mock_config)
page = client.list_report_shares(graph_id)
assert page is not None
assert page.report_shares == []
assert mock_query.call_args.args[2]["reportId"] is None
assert mock_query.call_args.args[2]["includeRevoked"] is False


# ── Chart of accounts ─────────────────────────────────────────────────

Expand Down
Loading