Skip to content

Commit 068a292

Browse files
authored
[APP-5754] Python GetFragmentHistory SDK changes (#701)
1 parent df753cb commit 068a292

File tree

3 files changed

+106
-1
lines changed

3 files changed

+106
-1
lines changed

src/viam/app/app_client.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
AddRoleRequest,
1313
APIKeyWithAuthorizations,
1414
AppServiceStub,
15+
AuthenticatorInfo,
1516
Authorization,
1617
AuthorizedPermissions,
1718
ChangeRoleRequest,
@@ -49,8 +50,11 @@
4950
DeleteRobotRequest,
5051
)
5152
from viam.proto.app import Fragment as FragmentPB
53+
from viam.proto.app import FragmentHistoryEntry as FragmentHistoryEntryPB
5254
from viam.proto.app import FragmentVisibility as FragmentVisibilityPB
5355
from viam.proto.app import (
56+
GetFragmentHistoryRequest,
57+
GetFragmentHistoryResponse,
5458
GetFragmentRequest,
5559
GetFragmentResponse,
5660
GetLocationRequest,
@@ -383,6 +387,44 @@ def proto(self) -> FragmentPB:
383387
)
384388

385389

390+
class FragmentHistoryEntry:
391+
"""A class that mirrors the `FragmentHistoryEntry` proto message.
392+
393+
Use this class to make the attributes of a `viam.proto.app.FragmentHistoryEntry` more accessible and easier to read/interpret.
394+
"""
395+
396+
@classmethod
397+
def from_proto(cls, fragment_history_entry: FragmentHistoryEntryPB) -> Self:
398+
"""Create a `FragmentHistoryEntry` from the .proto defined `FragmentHistoryEntry`.
399+
400+
Args:
401+
fragment_history_entry (viam.proto.app.FragmentHistoryEntry): The object to copy from.
402+
403+
Returns:
404+
FragmentHistoryEntry: The `FragmentHistoryEntry`.
405+
"""
406+
self = cls()
407+
self.fragment = fragment_history_entry.fragment
408+
self.edited_on = fragment_history_entry.edited_on.ToDatetime()
409+
self.old = Fragment.from_proto(fragment_history_entry.old)
410+
self.edited_by = fragment_history_entry.edited_by
411+
return self
412+
413+
fragment: str
414+
edited_on: datetime
415+
old: Fragment
416+
edited_by: AuthenticatorInfo
417+
418+
@property
419+
def proto(self) -> FragmentHistoryEntryPB:
420+
return FragmentHistoryEntryPB(
421+
fragment=self.fragment,
422+
edited_on=datetime_to_timestamp(self.edited_on),
423+
edited_by=self.edited_by,
424+
old=self.old.proto if self.old else None,
425+
)
426+
427+
386428
class RobotPartHistoryEntry:
387429
"""A class that mirrors the `RobotPartHistoryEntry` proto message.
388430
@@ -1781,6 +1823,38 @@ async def delete_fragment(self, fragment_id: str) -> None:
17811823
request = DeleteFragmentRequest(id=fragment_id)
17821824
await self._app_client.DeleteFragment(request, metadata=self._metadata)
17831825

1826+
async def get_fragment_history(
1827+
self, id: str, page_token: Optional[str] = "", page_limit: Optional[int] = 10
1828+
) -> List[FragmentHistoryEntry]:
1829+
"""Get fragment history.
1830+
1831+
::
1832+
1833+
fragment_history = await cloud.get_fragment_history(
1834+
id = "12a12ab1-1234-5678-abcd-abcd01234567",
1835+
page_token = "pg-token",
1836+
page_limit = 10
1837+
)
1838+
1839+
Args:
1840+
id (str): ID of the fragment to fetch history for.
1841+
page_token (Optional[str]): the page token for the fragment history collection
1842+
page_limit (Optional[int]): the number of fragment history documents to return in the result.
1843+
The default page limit is 10.
1844+
1845+
Raises:
1846+
GRPCError: if an invalid fragment id, page token or page limit is passed.
1847+
1848+
Returns:
1849+
viam.app.app_client.FragmentHistoryResponse: The fragment history document(s).
1850+
1851+
For more information, see `Fleet Management API <https://docs.viam.com/appendix/apis/fleet/>`_.
1852+
"""
1853+
1854+
request = GetFragmentHistoryRequest(id=id, page_token=page_token, page_limit=page_limit)
1855+
response: GetFragmentHistoryResponse = await self._app_client.GetFragmentHistory(request, metadata=self._metadata)
1856+
return [FragmentHistoryEntry.from_proto(fragment_history) for fragment_history in response.history]
1857+
17841858
async def add_role(
17851859
self,
17861860
org_id: str,

tests/mocks/services.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from numpy.typing import NDArray
66

77
from viam.app.data_client import DataClient
8+
from viam.gen.app.v1.app_pb2 import FragmentHistoryEntry, GetFragmentHistoryRequest, GetFragmentHistoryResponse
89
from viam.media.video import ViamImage
910
from viam.proto.app import (
1011
AddRoleRequest,
@@ -1173,6 +1174,7 @@ def __init__(
11731174
available: bool,
11741175
location_auth: LocationAuth,
11751176
robot_part_history: List[RobotPartHistoryEntry],
1177+
fragment_history: List[FragmentHistoryEntry],
11761178
authorizations: List[Authorization],
11771179
url: str,
11781180
module: Module,
@@ -1195,6 +1197,7 @@ def __init__(
11951197
self.available = available
11961198
self.location_auth = location_auth
11971199
self.robot_part_history = robot_part_history
1200+
self.fragment_history = fragment_history
11981201
self.authorizations = authorizations
11991202
self.url = url
12001203
self.module = module
@@ -1490,6 +1493,14 @@ async def GetFragment(self, stream: Stream[GetFragmentRequest, GetFragmentRespon
14901493
self.fragment_id = request.id
14911494
await stream.send_message(GetFragmentResponse(fragment=self.fragment))
14921495

1496+
async def GetFragmentHistory(self, stream: Stream[GetFragmentHistoryRequest, GetFragmentHistoryResponse]) -> None:
1497+
request = await stream.recv_message()
1498+
assert request is not None
1499+
self.id = request.id
1500+
self.page_token = request.page_token
1501+
self.page_limit = request.page_limit
1502+
await stream.send_message(GetFragmentHistoryResponse(history=self.fragment_history))
1503+
14931504
async def CreateFragment(self, stream: Stream[CreateFragmentRequest, CreateFragmentResponse]) -> None:
14941505
request = await stream.recv_message()
14951506
assert request is not None

tests/test_app_client.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
from grpclib.testing import ChannelFor
55

66
from viam.app.app_client import APIKeyAuthorization, AppClient, Fragment, FragmentVisibilityPB
7-
from viam.proto.app import APIKey, APIKeyWithAuthorizations, Authorization, AuthorizationDetails, AuthorizedPermissions
7+
from viam.proto.app import APIKey, APIKeyWithAuthorizations, AuthenticatorInfo, Authorization, AuthorizationDetails, AuthorizedPermissions
88
from viam.proto.app import Fragment as FragmentPB
99
from viam.proto.app import (
10+
FragmentHistoryEntry,
1011
Location,
1112
LocationAuth,
1213
Model,
@@ -36,6 +37,8 @@
3637
IDS = [ID]
3738
NAME = "name"
3839
CID = "cid"
40+
PAGE_TOKEN = "123"
41+
PAGE_LIMIT = 20
3942
TIME = datetime_to_timestamp(datetime.now())
4043
PUBLIC_NAMESPACE = "public_namespace"
4144
DEFAULT_REGION = "default_region"
@@ -122,6 +125,9 @@
122125
PART = "part"
123126
ROBOT_PART_HISTORY_ENTRY = RobotPartHistoryEntry(part=PART, robot=ID, when=TIME, old=None)
124127
ROBOT_PART_HISTORY = [ROBOT_PART_HISTORY_ENTRY]
128+
AUTHENTICATOR_INFO = AuthenticatorInfo(value="value", is_deactivated=True, type=1)
129+
FRAGMENT_HISTORY_ENTRY = FragmentHistoryEntry(fragment=ID, edited_by=AUTHENTICATOR_INFO, old=FRAGMENT, edited_on=TIME)
130+
FRAGMENT_HISTORY = [FRAGMENT_HISTORY_ENTRY]
125131
TYPE = "robot"
126132
ROLE = "operator"
127133
API_KEY = "key"
@@ -210,6 +216,7 @@ def service() -> MockApp:
210216
available=AVAILABLE,
211217
location_auth=LOCATION_AUTH,
212218
robot_part_history=ROBOT_PART_HISTORY,
219+
fragment_history=FRAGMENT_HISTORY,
213220
authorizations=AUTHORIZATIONS,
214221
url=URL,
215222
module=MODULE,
@@ -630,6 +637,19 @@ async def test_delete_fragment(self, service: MockApp):
630637
await client.delete_fragment(fragment_id=ID)
631638
assert service.id == ID
632639

640+
@pytest.mark.asyncio
641+
async def test_get_fragment_history(self, service: MockApp):
642+
async with ChannelFor([service]) as channel:
643+
client = AppClient(channel, METADATA, ID)
644+
fragment_history = await client.get_fragment_history(id=ID, page_token=PAGE_TOKEN, page_limit=PAGE_LIMIT)
645+
assert service.fragment.id == ID
646+
assert len(fragment_history) == len(FRAGMENT_HISTORY)
647+
assert service.id == ID
648+
assert service.page_token == PAGE_TOKEN
649+
assert service.page_limit == PAGE_LIMIT
650+
for i in range(len(FRAGMENT_HISTORY)):
651+
assert fragment_history[i].proto == FRAGMENT_HISTORY[i]
652+
633653
@pytest.mark.asyncio
634654
async def test_add_role(self, service: MockApp):
635655
async with ChannelFor([service]) as channel:

0 commit comments

Comments
 (0)