From 660c2afafda882a3d89d8144f1aa5722ec019c42 Mon Sep 17 00:00:00 2001 From: Katherine Pioro Date: Fri, 2 Aug 2024 15:17:20 -0400 Subject: [PATCH 1/7] GetFragmentHistory initial add --- src/viam/app/app_client.py | 328 ++++++++++++++++++++++++------------- tests/mocks/services.py | 9 + tests/test_app_client.py | 18 ++ 3 files changed, 245 insertions(+), 110 deletions(-) diff --git a/src/viam/app/app_client.py b/src/viam/app/app_client.py index 2e2e86276..573747a5b 100644 --- a/src/viam/app/app_client.py +++ b/src/viam/app/app_client.py @@ -8,6 +8,7 @@ from viam import logging from viam.app._logs import _LogsStream, _LogsStreamWithIterator +from viam.gen.app.v1.app_pb2 import FragmentHistoryEntry from viam.proto.app import ( AddRoleRequest, APIKeyWithAuthorizations, @@ -53,6 +54,8 @@ from viam.proto.app import ( GetFragmentRequest, GetFragmentResponse, + GetFragmentHistoryRequest, + GetFragmentHistoryResponse, GetLocationRequest, GetLocationResponse, GetModuleRequest, @@ -128,6 +131,7 @@ ) from viam.proto.app import RobotPart as RobotPartPB from viam.proto.app import RobotPartHistoryEntry as RobotPartHistoryEntryPB +from viam.proto.app import FragmentHistoryEntry as FragmentHistoryEntryPB from viam.proto.app import ( RotateKeyRequest, RotateKeyResponse, @@ -187,7 +191,8 @@ def from_proto(cls, robot_part: RobotPartPB) -> Self: self.location_id = robot_part.location_id self.robot_config = struct_to_dict(robot_part.robot_config) if robot_part.HasField("robot_config") else None self.last_access = robot_part.last_access.ToDatetime() if robot_part.HasField("last_access") else None - self.user_supplied_info = struct_to_dict(robot_part.user_supplied_info) if robot_part.HasField("user_supplied_info") else None + self.user_supplied_info = struct_to_dict(robot_part.user_supplied_info) if robot_part.HasField( + "user_supplied_info") else None self.main_part = robot_part.main_part self.fqdn = robot_part.fqdn self.local_fqdn = robot_part.local_fqdn @@ -383,6 +388,45 @@ def proto(self) -> FragmentPB: ) +class FragmentHistoryEntry: + """A class that mirror the `FragmentHistoryEntry` proto message. + + Use this class to make the attributes of a `viam.proto.app.FragmentHistoryEntry` more accessible and easier to read/interpret. + """ + + @classmethod + def from_proto(cls, fragment_history_entry: FragmentHistoryEntryPB) -> Self: + """Create a `FragmentHistoryEntry` from the .proto defined `FragmentHistoryEntry`. + + Args: + fragment_history_entry (viam.proto.app.FragmentHistoryEntry): The object to copy from. + + Returns: + FragmentHistoryEntry: The `FragmentHistoryEntry`. + """ + self = cls() + self.fragment = fragment_history_entry.fragment + self.edited_on = fragment_history_entry.edited_on.ToDatetime() if fragment_history_entry.HasField( + "edited_on") else None + self.old = Fragment.from_proto(fragment_history_entry.old) if fragment_history_entry.HasField("old") else None + self.edited_by = fragment_history_entry.edited_by if fragment_history_entry.HasField("edited_by") else None + return self + + fragment: str + edited_on: Optional[datetime] + old: Optional[Fragment] + edited_by: Optional[str] + + @property + def proto(self) -> FragmentHistoryEntryPB: + return FragmentHistoryEntryPB( + fragment=self.fragment, + edited_on=datetime_to_timestamp(self.edited_on), + edited_by=self.edited_by, + old=self.old.proto if self.old else None + ) + + class RobotPartHistoryEntry: """A class that mirrors the `RobotPartHistoryEntry` proto message. @@ -403,7 +447,8 @@ def from_proto(cls, robot_part_history_entry: RobotPartHistoryEntryPB) -> Self: self.part = robot_part_history_entry.part self.robot = robot_part_history_entry.robot self.when = robot_part_history_entry.when.ToDatetime() if robot_part_history_entry.HasField("when") else None - self.old = RobotPart.from_proto(robot_part_history_entry.old) if robot_part_history_entry.HasField("old") else None + self.old = RobotPart.from_proto(robot_part_history_entry.old) if robot_part_history_entry.HasField( + "old") else None return self part: str @@ -428,10 +473,10 @@ class APIKeyAuthorization: """ def __init__( - self, - role: Union[Literal["owner"], Literal["operator"]], - resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], - resource_id: str, + self, + role: Union[Literal["owner"], Literal["operator"]], + resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], + resource_id: str, ): """role (Union[Literal["owner"], Literal["operator"]]): The role to add. resource_type (Union[Literal["organization"], Literal["location"], Literal["robot"]]): Type of the resource to add role to. @@ -502,13 +547,13 @@ def __init__(self, channel: Channel, metadata: Mapping[str, str], location_id: O _organization_id: Optional[str] = None async def _create_authorization( - self, - organization_id: str, - identity_id: str, - identity_type: str, - role: Union[Literal["owner"], Literal["operator"]], - resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], - resource_id: str, + self, + organization_id: str, + identity_id: str, + identity_type: str, + role: Union[Literal["owner"], Literal["operator"]], + resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], + resource_id: str, ) -> Authorization: return Authorization( authorization_type="role", @@ -526,8 +571,10 @@ async def _create_authorization_for_new_api_key(self, org_id: str, auth: APIKeyA organization_id=org_id, identity_id="", # setting `identity_id` when creating an API key results in an error identity_type="api-key", - role=auth._role, # type: ignore -- Ignoring because this is technically a `string` - resource_type=auth._resource_type, # type: ignore -- Ignoring because this is technically a `string` + # type ignoring because it's technically a string + role=auth._role, + # type ignoring because this is technically a string + resource_type=auth._resource_type, resource_id=auth._resource_id, ) @@ -566,7 +613,8 @@ async def create_organization(self, name: str) -> Organization: For more information, see `Fleet Management API `_. """ request = CreateOrganizationRequest(name=name) - response: CreateOrganizationResponse = await self._app_client.CreateOrganization(request, metadata=self._metadata) + response: CreateOrganizationResponse = await self._app_client.CreateOrganization(request, + metadata=self._metadata) return response.organization async def list_organizations(self) -> List[Organization]: @@ -622,7 +670,8 @@ async def list_organizations_by_user(self, user_id: str) -> List[OrgDetails]: For more information, see `Fleet Management API `_. """ request = ListOrganizationsByUserRequest(user_id=user_id) - response: ListOrganizationsByUserResponse = await self._app_client.ListOrganizationsByUser(request, metadata=self._metadata) + response: ListOrganizationsByUserResponse = await self._app_client.ListOrganizationsByUser(request, + metadata=self._metadata) return list(response.orgs) async def get_organization(self, org_id: str) -> Organization: @@ -670,12 +719,12 @@ async def get_organization_namespace_availability(self, public_namespace: str) - return response.available async def update_organization( - self, - org_id: str, - name: Optional[str] = None, - public_namespace: Optional[str] = None, - region: Optional[str] = None, - cid: Optional[str] = None, + self, + org_id: str, + name: Optional[str] = None, + public_namespace: Optional[str] = None, + region: Optional[str] = None, + cid: Optional[str] = None, ) -> Organization: """Updates organization details. @@ -701,7 +750,8 @@ async def update_organization( cid=cid, name=name, ) - response: UpdateOrganizationResponse = await self._app_client.UpdateOrganization(request, metadata=self._metadata) + response: UpdateOrganizationResponse = await self._app_client.UpdateOrganization(request, + metadata=self._metadata) return response.organization async def delete_organization(self, org_id: str) -> None: @@ -737,15 +787,16 @@ async def list_organization_members(self, org_id: str) -> Tuple[List[Organizatio For more information, see `Fleet Management API `_. """ request = ListOrganizationMembersRequest(organization_id=org_id) - response: ListOrganizationMembersResponse = await self._app_client.ListOrganizationMembers(request, metadata=self._metadata) + response: ListOrganizationMembersResponse = await self._app_client.ListOrganizationMembers(request, + metadata=self._metadata) return list(response.members), list(response.invites) async def create_organization_invite( - self, - org_id: str, - email: str, - authorizations: Optional[List[Authorization]] = None, - send_email_invite: bool = True, + self, + org_id: str, + email: str, + authorizations: Optional[List[Authorization]] = None, + send_email_invite: bool = True, ) -> OrganizationInvite: """Creates an organization invite and sends it via email. @@ -776,15 +827,16 @@ async def create_organization_invite( request = CreateOrganizationInviteRequest( organization_id=org_id, email=email, authorizations=authorizations, send_email_invite=send_email_invite ) - response: CreateOrganizationInviteResponse = await self._app_client.CreateOrganizationInvite(request, metadata=self._metadata) + response: CreateOrganizationInviteResponse = await self._app_client.CreateOrganizationInvite(request, + metadata=self._metadata) return response.invite async def update_organization_invite_authorizations( - self, - org_id: str, - email: str, - add_authorizations: Optional[List[Authorization]] = None, - remove_authorizations: Optional[List[Authorization]] = None, + self, + org_id: str, + email: str, + add_authorizations: Optional[List[Authorization]] = None, + remove_authorizations: Optional[List[Authorization]] = None, ) -> OrganizationInvite: """Update the authorizations attached to an organization invite that has already been created. @@ -827,7 +879,8 @@ async def update_organization_invite_authorizations( For more information, see `Fleet Management API `_. """ request = UpdateOrganizationInviteAuthorizationsRequest( - organization_id=org_id, email=email, add_authorizations=add_authorizations, remove_authorizations=remove_authorizations + organization_id=org_id, email=email, add_authorizations=add_authorizations, + remove_authorizations=remove_authorizations ) response: UpdateOrganizationInviteAuthorizationsResponse = await self._app_client.UpdateOrganizationInviteAuthorizations( request, metadata=self._metadata @@ -895,7 +948,8 @@ async def resend_organization_invite(self, org_id: str, email: str) -> Organizat For more information, see `Fleet Management API `_. """ request = ResendOrganizationInviteRequest(organization_id=org_id, email=email) - response: ResendOrganizationInviteResponse = await self._app_client.ResendOrganizationInvite(request, metadata=self._metadata) + response: ResendOrganizationInviteResponse = await self._app_client.ResendOrganizationInvite(request, + metadata=self._metadata) return response.invite async def create_location(self, org_id: str, name: str, parent_location_id: Optional[str] = None) -> Location: @@ -943,11 +997,13 @@ async def get_location(self, location_id: Optional[str] = None) -> Location: For more information, see `Fleet Management API `_. """ - request = GetLocationRequest(location_id=location_id if location_id else self._location_id if self._location_id else "") + request = GetLocationRequest( + location_id=location_id if location_id else self._location_id if self._location_id else "") response: GetLocationResponse = await self._app_client.GetLocation(request, metadata=self._metadata) return response.location - async def update_location(self, location_id: str, name: Optional[str] = None, parent_location_id: Optional[str] = None) -> Location: + async def update_location(self, location_id: str, name: Optional[str] = None, + parent_location_id: Optional[str] = None) -> Location: """Change the name of a location and/or assign it a new parent location. :: @@ -1082,7 +1138,8 @@ async def location_auth(self, location_id: Optional[str] = None) -> LocationAuth For more information, see `Fleet Management API `_. """ - request = LocationAuthRequest(location_id=location_id if location_id else self._location_id if self._location_id else "") + request = LocationAuthRequest( + location_id=location_id if location_id else self._location_id if self._location_id else "") response: LocationAuthResponse = await self._app_client.LocationAuth(request, metadata=self._metadata) return response.auth @@ -1106,8 +1163,10 @@ async def create_location_secret(self, location_id: Optional[str] = None) -> Loc For more information, see `Fleet Management API `_. """ - request = CreateLocationSecretRequest(location_id=location_id if location_id else self._location_id if self._location_id else "") - response: CreateLocationSecretResponse = await self._app_client.CreateLocationSecret(request, metadata=self._metadata) + request = CreateLocationSecretRequest( + location_id=location_id if location_id else self._location_id if self._location_id else "") + response: CreateLocationSecretResponse = await self._app_client.CreateLocationSecret(request, + metadata=self._metadata) return response.auth async def delete_location_secret(self, secret_id: str, location_id: Optional[str] = None) -> None: @@ -1129,7 +1188,8 @@ async def delete_location_secret(self, secret_id: str, location_id: Optional[str For more information, see `Fleet Management API `_. """ request = DeleteLocationSecretRequest( - location_id=location_id if location_id else self._location_id if self._location_id else "", secret_id=secret_id + location_id=location_id if location_id else self._location_id if self._location_id else "", + secret_id=secret_id ) await self._app_client.DeleteLocationSecret(request, metadata=self._metadata) @@ -1172,7 +1232,8 @@ async def get_rover_rental_robots(self, org_id: str) -> List[RoverRentalRobot]: For more information, see `Fleet Management API `_. """ request = GetRoverRentalRobotsRequest(org_id=org_id) - response: GetRoverRentalRobotsResponse = await self._app_client.GetRoverRentalRobots(request, metadata=self._metadata) + response: GetRoverRentalRobotsResponse = await self._app_client.GetRoverRentalRobots(request, + metadata=self._metadata) return list(response.robots) async def get_robot_parts(self, robot_id: str) -> List[RobotPart]: @@ -1233,12 +1294,12 @@ async def get_robot_part(self, robot_part_id: str, dest: Optional[str] = None, i return RobotPart.from_proto(robot_part=response.part) async def get_robot_part_logs( - self, - robot_part_id: str, - filter: Optional[str] = None, - dest: Optional[str] = None, - log_levels: List[str] = [], - num_log_entries: int = 100, + self, + robot_part_id: str, + filter: Optional[str] = None, + dest: Optional[str] = None, + log_levels: List[str] = [], + num_log_entries: int = 100, ) -> List[LogEntry]: """Get the logs associated with a robot part. @@ -1272,7 +1333,8 @@ async def get_robot_part_logs( while True: new_logs, next_page_token = await self._get_robot_part_logs( - robot_part_id=robot_part_id, filter=filter if filter else "", page_token=page_token, log_levels=log_levels + robot_part_id=robot_part_id, filter=filter if filter else "", page_token=page_token, + log_levels=log_levels ) if num_log_entries != 0 and len(new_logs) > logs_left: logs += new_logs[0:logs_left] @@ -1295,19 +1357,20 @@ async def get_robot_part_logs( file.write(f"{time}\t{level}\t{logger_name}\t{file_name:<64}{message}\n") file.flush() except Exception as e: - LOGGER.error(f"Failed to write robot part from robot part with ID [{robot_part_id}]logs to file {dest}", exc_info=e) + LOGGER.error(f"Failed to write robot part from robot part with ID [{robot_part_id}]logs to file {dest}", + exc_info=e) return logs async def _get_robot_part_logs( - self, robot_part_id: str, filter: str, page_token: str, log_levels: List[str] + self, robot_part_id: str, filter: str, page_token: str, log_levels: List[str] ) -> Tuple[List[LogEntry], str]: request = GetRobotPartLogsRequest(id=robot_part_id, filter=filter, page_token=page_token, levels=log_levels) response: GetRobotPartLogsResponse = await self._app_client.GetRobotPartLogs(request, metadata=self._metadata) return [LogEntry.from_proto(log) for log in response.logs], response.next_page_token async def tail_robot_part_logs( - self, robot_part_id: str, errors_only: bool = True, filter: Optional[str] = None + self, robot_part_id: str, errors_only: bool = True, filter: Optional[str] = None ) -> _LogsStream[List[LogEntry]]: """Get an asynchronous iterator that receives live robot part logs. @@ -1361,10 +1424,12 @@ async def get_robot_part_history(self, robot_part_id: str) -> List[RobotPartHist For more information, see `Fleet Management API `_. """ request = GetRobotPartHistoryRequest(id=robot_part_id) - response: GetRobotPartHistoryResponse = await self._app_client.GetRobotPartHistory(request, metadata=self._metadata) + response: GetRobotPartHistoryResponse = await self._app_client.GetRobotPartHistory(request, + metadata=self._metadata) return [RobotPartHistoryEntry.from_proto(part_history) for part_history in response.history] - async def update_robot_part(self, robot_part_id: str, name: str, robot_config: Optional[Mapping[str, Any]] = None) -> RobotPart: + async def update_robot_part(self, robot_part_id: str, name: str, + robot_config: Optional[Mapping[str, Any]] = None) -> RobotPart: """Change the name and assign an optional new configuration to a robot part. :: @@ -1386,7 +1451,8 @@ async def update_robot_part(self, robot_part_id: str, name: str, robot_config: O For more information, see `Fleet Management API `_. """ - request = UpdateRobotPartRequest(id=robot_part_id, name=name, robot_config=dict_to_struct(robot_config) if robot_config else None) + request = UpdateRobotPartRequest(id=robot_part_id, name=name, + robot_config=dict_to_struct(robot_config) if robot_config else None) response: UpdateRobotPartResponse = await self._app_client.UpdateRobotPart(request, metadata=self._metadata) return RobotPart.from_proto(robot_part=response.part) @@ -1510,7 +1576,8 @@ async def create_robot_part_secret(self, robot_part_id: str) -> RobotPart: For more information, see `Fleet Management API `_. """ request = CreateRobotPartSecretRequest(part_id=robot_part_id) - response: CreateRobotPartSecretResponse = await self._app_client.CreateRobotPartSecret(request, metadata=self._metadata) + response: CreateRobotPartSecretResponse = await self._app_client.CreateRobotPartSecret(request, + metadata=self._metadata) return RobotPart.from_proto(response.part) async def delete_robot_part_secret(self, robot_part_id: str, secret_id: str) -> None: @@ -1554,7 +1621,8 @@ async def list_robots(self, location_id: Optional[str] = None) -> List[Robot]: For more information, see `Fleet Management API `_. """ - request = ListRobotsRequest(location_id=location_id if location_id else self._location_id if self._location_id else "") + request = ListRobotsRequest( + location_id=location_id if location_id else self._location_id if self._location_id else "") response: ListRobotsResponse = await self._app_client.ListRobots(request, metadata=self._metadata) return list(response.robots) @@ -1578,7 +1646,8 @@ async def new_robot(self, name: str, location_id: Optional[str] = None) -> str: For more information, see `Fleet Management API `_. """ - request = NewRobotRequest(location=location_id if location_id else self._location_id if self._location_id else "", name=name) + request = NewRobotRequest( + location=location_id if location_id else self._location_id if self._location_id else "", name=name) response: NewRobotResponse = await self._app_client.NewRobot(request, metadata=self._metadata) return response.id @@ -1607,7 +1676,8 @@ async def update_robot(self, robot_id: str, name: str, location_id: Optional[str For more information, see `Fleet Management API `_. """ request = UpdateRobotRequest( - id=robot_id, name=name, location=location_id if location_id else self._location_id if self._location_id else "" + id=robot_id, name=name, + location=location_id if location_id else self._location_id if self._location_id else "" ) response: UpdateRobotResponse = await self._app_client.UpdateRobot(request, metadata=self._metadata) return response.robot @@ -1631,7 +1701,7 @@ async def delete_robot(self, robot_id: str) -> None: await self._app_client.DeleteRobot(request, metadata=self._metadata) async def list_fragments( - self, org_id: str, show_public: bool = True, visibilities: Optional[List[Fragment.Visibility]] = None + self, org_id: str, show_public: bool = True, visibilities: Optional[List[Fragment.Visibility]] = None ) -> List[Fragment]: """Get a list of fragments under the currently authed-to organization. @@ -1710,17 +1780,18 @@ async def create_fragment(self, org_id: str, name: str, config: Optional[Mapping For more information, see `Fleet Management API `_. """ - request = CreateFragmentRequest(name=name, config=dict_to_struct(config) if config else None, organization_id=org_id) + request = CreateFragmentRequest(name=name, config=dict_to_struct(config) if config else None, + organization_id=org_id) response: CreateFragmentResponse = await self._app_client.CreateFragment(request, metadata=self._metadata) return Fragment.from_proto(response.fragment) async def update_fragment( - self, - fragment_id: str, - name: str, - config: Optional[Mapping[str, Any]] = None, - public: Optional[bool] = None, - visibility: Optional[Fragment.Visibility] = None, + self, + fragment_id: str, + name: str, + config: Optional[Mapping[str, Any]] = None, + public: Optional[bool] = None, + visibility: Optional[Fragment.Visibility] = None, ) -> Fragment: """Update a fragment name AND its config and/or visibility. @@ -1781,13 +1852,48 @@ async def delete_fragment(self, fragment_id: str) -> None: request = DeleteFragmentRequest(id=fragment_id) await self._app_client.DeleteFragment(request, metadata=self._metadata) + async def get_fragment_history(self, + id: str, + page_token: Optional[str] = "", + page_limit: Optional[int] = 10) -> list[FragmentHistoryEntry]: + """Get fragment history. + + :: + + await cloud.get_fragment_history( + id = "12a12ab1-1234-5678-abcd-abcd01234567", + page_token = "pg-token", + page_limit = 10 + ) + + Args: + id (str): ID of the fragment to fetch history for. + page_token (Optional[str]): the page token for the fragment history collection + page_limit (Optional[int]): the number of fragment history documents to return in the result. + The default page limit is 10. + + Raises: + GRPCError: if an invalid fragment id, page token or page limit is passed. + + Returns: + viam.app.app_client.FragmentHistoryResponse: The fragment history document(s). + + For more information, see `Fleet Management API `_. + """ + + request: GetFragmentHistoryRequest = GetFragmentHistoryRequest(id=id, page_token=page_token, + page_limit=page_limit) + response: GetFragmentHistoryResponse = await self._app_client.GetFragmentHistory(request, + metadata=self._metadata) + return [FragmentHistoryEntry.from_proto(fragment_history) for fragment_history in response.history] + async def add_role( - self, - org_id: str, - identity_id: str, - role: Union[Literal["owner"], Literal["operator"]], - resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], - resource_id: str, + self, + org_id: str, + identity_id: str, + role: Union[Literal["owner"], Literal["operator"]], + resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], + resource_id: str, ) -> None: """Add a role under the currently authed-to organization. @@ -1826,12 +1932,12 @@ async def add_role( await self._app_client.AddRole(request, metadata=self._metadata) async def remove_role( - self, - org_id: str, - identity_id: str, - role: Union[Literal["owner"], Literal["operator"]], - resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], - resource_id: str, + self, + org_id: str, + identity_id: str, + role: Union[Literal["owner"], Literal["operator"]], + resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], + resource_id: str, ) -> None: """Remove a role under the currently authed-to organization. @@ -1870,16 +1976,16 @@ async def remove_role( await self._app_client.RemoveRole(request, metadata=self._metadata) async def change_role( - self, - organization_id: str, - old_identity_id: str, - old_role: Union[Literal["owner"], Literal["operator"]], - old_resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], - old_resource_id: str, - new_identity_id: str, - new_role: Union[Literal["owner"], Literal["operator"]], - new_resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], - new_resource_id: str, + self, + organization_id: str, + old_identity_id: str, + old_role: Union[Literal["owner"], Literal["operator"]], + old_resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], + old_resource_id: str, + new_identity_id: str, + new_role: Union[Literal["owner"], Literal["operator"]], + new_resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], + new_resource_id: str, ) -> None: """Changes a role to a new role. @@ -1955,7 +2061,8 @@ async def list_authorizations(self, org_id: str, resource_ids: Optional[List[str For more information, see `Fleet Management API `_. """ request = ListAuthorizationsRequest(organization_id=org_id, resource_ids=resource_ids) - response: ListAuthorizationsResponse = await self._app_client.ListAuthorizations(request, metadata=self._metadata) + response: ListAuthorizationsResponse = await self._app_client.ListAuthorizations(request, + metadata=self._metadata) return list(response.authorizations) async def check_permissions(self, permissions: List[AuthorizedPermissions]) -> List[AuthorizedPermissions]: @@ -2027,7 +2134,7 @@ async def create_registry_item(self, organization_id: str, name: str, type: Pack await self._app_client.CreateRegistryItem(request, metadata=self._metadata) async def update_registry_item( - self, item_id: str, type: PackageType.ValueType, description: str, visibility: Visibility.ValueType + self, item_id: str, type: PackageType.ValueType, description: str, visibility: Visibility.ValueType ) -> None: """Update a registry item. @@ -2046,14 +2153,14 @@ async def update_registry_item( await self._app_client.UpdateRegistryItem(request, metadata=self._metadata) async def list_registry_items( - self, - organization_id: str, - types: List[PackageType.ValueType], - visibilities: List[Visibility.ValueType], - platforms: List[str], - statuses: List[RegistryItemStatus.ValueType], - search_term: Optional[str] = None, - page_token: Optional[str] = None, + self, + organization_id: str, + types: List[PackageType.ValueType], + visibilities: List[Visibility.ValueType], + platforms: List[str], + statuses: List[RegistryItemStatus.ValueType], + search_term: Optional[str] = None, + page_token: Optional[str] = None, ) -> List[RegistryItem]: """List the registry items in an organization. @@ -2124,13 +2231,13 @@ async def create_module(self, org_id: str, name: str) -> Tuple[str, str]: return response.module_id, response.url async def update_module( - self, - module_id: str, - url: str, - description: str, - models: Optional[List[Model]], - entrypoint: str, - public: bool = False, + self, + module_id: str, + url: str, + description: str, + models: Optional[List[Model]], + entrypoint: str, + public: bool = False, ) -> str: """Update the documentation URL, description, models, entrypoint, and/or the visibility of a module. @@ -2241,7 +2348,8 @@ async def list_modules(self, org_id: str) -> List[Module]: # TODO(RSDK-5569): when user-based auth exists, make `name` default to `None` and let # app deal with setting a default. - async def create_key(self, org_id: str, authorizations: List[APIKeyAuthorization], name: Optional[str] = None) -> Tuple[str, str]: + async def create_key(self, org_id: str, authorizations: List[APIKeyAuthorization], name: Optional[str] = None) -> \ + Tuple[str, str]: """Creates a new API key. :: diff --git a/tests/mocks/services.py b/tests/mocks/services.py index f5d40e3aa..9161979d1 100644 --- a/tests/mocks/services.py +++ b/tests/mocks/services.py @@ -5,6 +5,7 @@ from numpy.typing import NDArray from viam.app.data_client import DataClient +from viam.gen.app.v1.app_pb2 import FragmentHistoryEntry, GetFragmentHistoryRequest, GetFragmentHistoryResponse from viam.media.video import ViamImage from viam.proto.app import ( AddRoleRequest, @@ -1173,6 +1174,7 @@ def __init__( available: bool, location_auth: LocationAuth, robot_part_history: List[RobotPartHistoryEntry], + fragment_history: List[FragmentHistoryEntry], authorizations: List[Authorization], url: str, module: Module, @@ -1195,6 +1197,7 @@ def __init__( self.available = available self.location_auth = location_auth self.robot_part_history = robot_part_history + self.fragment_history = fragment_history self.authorizations = authorizations self.url = url self.module = module @@ -1490,6 +1493,12 @@ async def GetFragment(self, stream: Stream[GetFragmentRequest, GetFragmentRespon self.fragment_id = request.id await stream.send_message(GetFragmentResponse(fragment=self.fragment)) + async def GetFragmentHistory(self, stream: Stream[GetFragmentHistoryRequest, GetFragmentHistoryResponse]) -> None: + request = await stream.recv_message() + assert request is not None + self.id = request.id + await stream.send_message(GetFragmentHistoryResponse(history=self.fragment_history)) + async def CreateFragment(self, stream: Stream[CreateFragmentRequest, CreateFragmentResponse]) -> None: request = await stream.recv_message() assert request is not None diff --git a/tests/test_app_client.py b/tests/test_app_client.py index 39a1d0cf7..84b750528 100644 --- a/tests/test_app_client.py +++ b/tests/test_app_client.py @@ -7,6 +7,8 @@ from viam.proto.app import APIKey, APIKeyWithAuthorizations, Authorization, AuthorizationDetails, AuthorizedPermissions from viam.proto.app import Fragment as FragmentPB from viam.proto.app import ( + AuthenticatorInfo, + FragmentHistoryEntry, Location, LocationAuth, Model, @@ -36,6 +38,8 @@ IDS = [ID] NAME = "name" CID = "cid" +PAGE_TOKEN="" +PAGE_LIMIT=10 TIME = datetime_to_timestamp(datetime.now()) PUBLIC_NAMESPACE = "public_namespace" DEFAULT_REGION = "default_region" @@ -122,6 +126,9 @@ PART = "part" ROBOT_PART_HISTORY_ENTRY = RobotPartHistoryEntry(part=PART, robot=ID, when=TIME, old=None) ROBOT_PART_HISTORY = [ROBOT_PART_HISTORY_ENTRY] +AUTHENTICATOR_INFO = AuthenticatorInfo(value="value", is_deactivated=True, type=1) +FRAGMENT_HISTORY_ENTRY = FragmentHistoryEntry(fragment=ID, edited_by=AUTHENTICATOR_INFO, old=FRAGMENT, edited_on=TIME) +FRAGMENT_HISTORY = [FRAGMENT_HISTORY_ENTRY] TYPE = "robot" ROLE = "operator" API_KEY = "key" @@ -210,6 +217,7 @@ def service() -> MockApp: available=AVAILABLE, location_auth=LOCATION_AUTH, robot_part_history=ROBOT_PART_HISTORY, + fragment_history=FRAGMENT_HISTORY, authorizations=AUTHORIZATIONS, url=URL, module=MODULE, @@ -630,6 +638,16 @@ async def test_delete_fragment(self, service: MockApp): await client.delete_fragment(fragment_id=ID) assert service.id == ID + @pytest.mark.asyncio + async def test_get_fragment_history(self, service: MockApp): + async with ChannelFor([service]) as channel: + client = AppClient(channel, METADATA, ID) + fragment_history = await client.get_fragment_history(id=ID, page_token="", page_limit=20) + assert service.fragment.id == ID + assert len(fragment_history) == len(FRAGMENT_HISTORY) + for i in range(len(FRAGMENT_HISTORY)): + assert fragment_history[i].proto == FRAGMENT_HISTORY[i] + @pytest.mark.asyncio async def test_add_role(self, service: MockApp): async with ChannelFor([service]) as channel: From 40a03a3ad49663af0669d315652a7498412ca65e Mon Sep 17 00:00:00 2001 From: Katherine Pioro Date: Tue, 6 Aug 2024 10:11:19 -0400 Subject: [PATCH 2/7] make typecheck --- .idea/.gitignore | 8 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/viam-python-sdk.iml | 18 ++ src/viam/app/app_client.py | 368 ++++++++++++++++++++----------------- tests/package.json | 6 + tests/test_app_client.py | 7 +- 7 files changed, 253 insertions(+), 168 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/viam-python-sdk.iml create mode 100644 tests/package.json diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..13566b81b --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..93d1fb3ab --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/viam-python-sdk.iml b/.idea/viam-python-sdk.iml new file mode 100644 index 000000000..daf0933a2 --- /dev/null +++ b/.idea/viam-python-sdk.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/viam/app/app_client.py b/src/viam/app/app_client.py index 573747a5b..71776a2b4 100644 --- a/src/viam/app/app_client.py +++ b/src/viam/app/app_client.py @@ -8,11 +8,9 @@ from viam import logging from viam.app._logs import _LogsStream, _LogsStreamWithIterator -from viam.gen.app.v1.app_pb2 import FragmentHistoryEntry +from viam.proto.app import AddRoleRequest, APIKeyWithAuthorizations, AppServiceStub from viam.proto.app import ( - AddRoleRequest, - APIKeyWithAuthorizations, - AppServiceStub, + AuthenticatorInfo, Authorization, AuthorizedPermissions, ChangeRoleRequest, @@ -50,12 +48,13 @@ DeleteRobotRequest, ) from viam.proto.app import Fragment as FragmentPB +from viam.proto.app import FragmentHistoryEntry as FragmentHistoryEntryPB from viam.proto.app import FragmentVisibility as FragmentVisibilityPB from viam.proto.app import ( - GetFragmentRequest, - GetFragmentResponse, GetFragmentHistoryRequest, GetFragmentHistoryResponse, + GetFragmentRequest, + GetFragmentResponse, GetLocationRequest, GetLocationResponse, GetModuleRequest, @@ -131,7 +130,6 @@ ) from viam.proto.app import RobotPart as RobotPartPB from viam.proto.app import RobotPartHistoryEntry as RobotPartHistoryEntryPB -from viam.proto.app import FragmentHistoryEntry as FragmentHistoryEntryPB from viam.proto.app import ( RotateKeyRequest, RotateKeyResponse, @@ -191,8 +189,7 @@ def from_proto(cls, robot_part: RobotPartPB) -> Self: self.location_id = robot_part.location_id self.robot_config = struct_to_dict(robot_part.robot_config) if robot_part.HasField("robot_config") else None self.last_access = robot_part.last_access.ToDatetime() if robot_part.HasField("last_access") else None - self.user_supplied_info = struct_to_dict(robot_part.user_supplied_info) if robot_part.HasField( - "user_supplied_info") else None + self.user_supplied_info = struct_to_dict(robot_part.user_supplied_info) if robot_part.HasField("user_supplied_info") else None self.main_part = robot_part.main_part self.fqdn = robot_part.fqdn self.local_fqdn = robot_part.local_fqdn @@ -388,6 +385,82 @@ def proto(self) -> FragmentPB: ) +# class AuthenticatorInfo: +# class AuthenticationType(str, Enum): +# """ +# AuthenticationType specifies the authentication method used by the caller in editing the fragment. +# """ +# +# WEB_OAUTH = "web_oauth" +# """ +# Caller authenticated via oauth, presumably via Viam app frontend. +# """ +# +# API_KEY = "api_key" +# """ +# Caller authenticated via api key. +# """ +# +# ROBOT_PART_SECRET = "robot_part_secret" +# """ +# Caller authenticated via robot part secret. +# """ +# +# LOCATION_SECRET = "location_secret" +# """ +# Caller authenticated via location secret. +# """ +# +# UNSPECIFIED = "unspecified" +# +# """ +# Unspecified authentication type. +# """ +# +# @classmethod +# def from_proto(cls, authentication_type: AuthenticationTypePB.ValueType): +# if authentication_type == AuthenticationTypePB.AUTHENTICATION_TYPE_WEB_OAUTH: +# return AuthenticatorInfo.AuthenticationType.WEB_OAUTH +# if authentication_type == AuthenticationTypePB.AUTHENTICATION_TYPE_API_KEY: +# return AuthenticatorInfo.AuthenticationType.API_KEY +# if authentication_type == AuthenticationTypePB.AUTHENTICATION_TYPE_LOCATION_SECRET: +# return AuthenticatorInfo.AuthenticationType.LOCATION_SECRET +# if authentication_type == AuthenticationTypePB.AUTHENTICATION_TYPE_ROBOT_PART_SECRET: +# return AuthenticatorInfo.AuthenticationType.ROBOT_PART_SECRET +# return AuthenticatorInfo.AuthenticationType.UNSPECIFIED +# +# def to_proto(self) -> AuthenticationTypePB.ValueType: +# if self == self.WEB_OAUTH: +# return AuthenticationTypePB.AUTHENTICATION_TYPE_WEB_OAUTH +# if self == self.API_KEY: +# return AuthenticationTypePB.AUTHENTICATION_TYPE_API_KEY +# if self == self.LOCATION_SECRET: +# return AuthenticationTypePB.AUTHENTICATION_TYPE_LOCATION_SECRET +# if self == self.ROBOT_PART_SECRET: +# return AuthenticationTypePB.AUTHENTICATION_TYPE_ROBOT_PART_SECRET +# return AuthenticationTypePB.AUTHENTICATION_TYPE_UNSPECIFIED +# +# @classmethod +# def from_proto(cls, auth_info: AuthenticatorInfoPB) -> Self: +# self = cls() +# self.type = AuthenticatorInfo.AuthenticationType.from_proto(auth_info.type) +# self.value = auth_info.value +# self.is_deactivated = auth_info.is_deactivated +# return self +# +# @classmethod +# def to_proto(self) -> AuthenticatorInfoPB: +# return AuthenticatorInfoPB( +# is_deactivated=self.is_deactivated, +# value=self.value, +# type=AuthenticatorInfo.AuthenticationType.to_proto(self.type) +# ) +# +# type: AuthenticationType +# value: str +# is_deactivated: bool + + class FragmentHistoryEntry: """A class that mirror the `FragmentHistoryEntry` proto message. @@ -406,16 +479,15 @@ def from_proto(cls, fragment_history_entry: FragmentHistoryEntryPB) -> Self: """ self = cls() self.fragment = fragment_history_entry.fragment - self.edited_on = fragment_history_entry.edited_on.ToDatetime() if fragment_history_entry.HasField( - "edited_on") else None - self.old = Fragment.from_proto(fragment_history_entry.old) if fragment_history_entry.HasField("old") else None - self.edited_by = fragment_history_entry.edited_by if fragment_history_entry.HasField("edited_by") else None + self.edited_on = fragment_history_entry.edited_on.ToDatetime() + self.old = Fragment.from_proto(fragment_history_entry.old) + self.edited_by = fragment_history_entry.edited_by return self fragment: str - edited_on: Optional[datetime] - old: Optional[Fragment] - edited_by: Optional[str] + edited_on: datetime + old: Fragment + edited_by: AuthenticatorInfo @property def proto(self) -> FragmentHistoryEntryPB: @@ -423,7 +495,7 @@ def proto(self) -> FragmentHistoryEntryPB: fragment=self.fragment, edited_on=datetime_to_timestamp(self.edited_on), edited_by=self.edited_by, - old=self.old.proto if self.old else None + old=self.old.proto if self.old else None, ) @@ -447,8 +519,7 @@ def from_proto(cls, robot_part_history_entry: RobotPartHistoryEntryPB) -> Self: self.part = robot_part_history_entry.part self.robot = robot_part_history_entry.robot self.when = robot_part_history_entry.when.ToDatetime() if robot_part_history_entry.HasField("when") else None - self.old = RobotPart.from_proto(robot_part_history_entry.old) if robot_part_history_entry.HasField( - "old") else None + self.old = RobotPart.from_proto(robot_part_history_entry.old) if robot_part_history_entry.HasField("old") else None return self part: str @@ -473,10 +544,10 @@ class APIKeyAuthorization: """ def __init__( - self, - role: Union[Literal["owner"], Literal["operator"]], - resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], - resource_id: str, + self, + role: Union[Literal["owner"], Literal["operator"]], + resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], + resource_id: str, ): """role (Union[Literal["owner"], Literal["operator"]]): The role to add. resource_type (Union[Literal["organization"], Literal["location"], Literal["robot"]]): Type of the resource to add role to. @@ -547,13 +618,13 @@ def __init__(self, channel: Channel, metadata: Mapping[str, str], location_id: O _organization_id: Optional[str] = None async def _create_authorization( - self, - organization_id: str, - identity_id: str, - identity_type: str, - role: Union[Literal["owner"], Literal["operator"]], - resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], - resource_id: str, + self, + organization_id: str, + identity_id: str, + identity_type: str, + role: Union[Literal["owner"], Literal["operator"]], + resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], + resource_id: str, ) -> Authorization: return Authorization( authorization_type="role", @@ -571,10 +642,8 @@ async def _create_authorization_for_new_api_key(self, org_id: str, auth: APIKeyA organization_id=org_id, identity_id="", # setting `identity_id` when creating an API key results in an error identity_type="api-key", - # type ignoring because it's technically a string - role=auth._role, - # type ignoring because this is technically a string - resource_type=auth._resource_type, + role=auth._role, # type: ignore -- Ignoring because this is technically a `string` + resource_type=auth._resource_type, # type: ignore -- Ignoring because this is technically a `string` resource_id=auth._resource_id, ) @@ -613,8 +682,7 @@ async def create_organization(self, name: str) -> Organization: For more information, see `Fleet Management API `_. """ request = CreateOrganizationRequest(name=name) - response: CreateOrganizationResponse = await self._app_client.CreateOrganization(request, - metadata=self._metadata) + response: CreateOrganizationResponse = await self._app_client.CreateOrganization(request, metadata=self._metadata) return response.organization async def list_organizations(self) -> List[Organization]: @@ -670,8 +738,7 @@ async def list_organizations_by_user(self, user_id: str) -> List[OrgDetails]: For more information, see `Fleet Management API `_. """ request = ListOrganizationsByUserRequest(user_id=user_id) - response: ListOrganizationsByUserResponse = await self._app_client.ListOrganizationsByUser(request, - metadata=self._metadata) + response: ListOrganizationsByUserResponse = await self._app_client.ListOrganizationsByUser(request, metadata=self._metadata) return list(response.orgs) async def get_organization(self, org_id: str) -> Organization: @@ -719,12 +786,12 @@ async def get_organization_namespace_availability(self, public_namespace: str) - return response.available async def update_organization( - self, - org_id: str, - name: Optional[str] = None, - public_namespace: Optional[str] = None, - region: Optional[str] = None, - cid: Optional[str] = None, + self, + org_id: str, + name: Optional[str] = None, + public_namespace: Optional[str] = None, + region: Optional[str] = None, + cid: Optional[str] = None, ) -> Organization: """Updates organization details. @@ -750,8 +817,7 @@ async def update_organization( cid=cid, name=name, ) - response: UpdateOrganizationResponse = await self._app_client.UpdateOrganization(request, - metadata=self._metadata) + response: UpdateOrganizationResponse = await self._app_client.UpdateOrganization(request, metadata=self._metadata) return response.organization async def delete_organization(self, org_id: str) -> None: @@ -787,16 +853,15 @@ async def list_organization_members(self, org_id: str) -> Tuple[List[Organizatio For more information, see `Fleet Management API `_. """ request = ListOrganizationMembersRequest(organization_id=org_id) - response: ListOrganizationMembersResponse = await self._app_client.ListOrganizationMembers(request, - metadata=self._metadata) + response: ListOrganizationMembersResponse = await self._app_client.ListOrganizationMembers(request, metadata=self._metadata) return list(response.members), list(response.invites) async def create_organization_invite( - self, - org_id: str, - email: str, - authorizations: Optional[List[Authorization]] = None, - send_email_invite: bool = True, + self, + org_id: str, + email: str, + authorizations: Optional[List[Authorization]] = None, + send_email_invite: bool = True, ) -> OrganizationInvite: """Creates an organization invite and sends it via email. @@ -827,16 +892,15 @@ async def create_organization_invite( request = CreateOrganizationInviteRequest( organization_id=org_id, email=email, authorizations=authorizations, send_email_invite=send_email_invite ) - response: CreateOrganizationInviteResponse = await self._app_client.CreateOrganizationInvite(request, - metadata=self._metadata) + response: CreateOrganizationInviteResponse = await self._app_client.CreateOrganizationInvite(request, metadata=self._metadata) return response.invite async def update_organization_invite_authorizations( - self, - org_id: str, - email: str, - add_authorizations: Optional[List[Authorization]] = None, - remove_authorizations: Optional[List[Authorization]] = None, + self, + org_id: str, + email: str, + add_authorizations: Optional[List[Authorization]] = None, + remove_authorizations: Optional[List[Authorization]] = None, ) -> OrganizationInvite: """Update the authorizations attached to an organization invite that has already been created. @@ -879,8 +943,7 @@ async def update_organization_invite_authorizations( For more information, see `Fleet Management API `_. """ request = UpdateOrganizationInviteAuthorizationsRequest( - organization_id=org_id, email=email, add_authorizations=add_authorizations, - remove_authorizations=remove_authorizations + organization_id=org_id, email=email, add_authorizations=add_authorizations, remove_authorizations=remove_authorizations ) response: UpdateOrganizationInviteAuthorizationsResponse = await self._app_client.UpdateOrganizationInviteAuthorizations( request, metadata=self._metadata @@ -948,8 +1011,7 @@ async def resend_organization_invite(self, org_id: str, email: str) -> Organizat For more information, see `Fleet Management API `_. """ request = ResendOrganizationInviteRequest(organization_id=org_id, email=email) - response: ResendOrganizationInviteResponse = await self._app_client.ResendOrganizationInvite(request, - metadata=self._metadata) + response: ResendOrganizationInviteResponse = await self._app_client.ResendOrganizationInvite(request, metadata=self._metadata) return response.invite async def create_location(self, org_id: str, name: str, parent_location_id: Optional[str] = None) -> Location: @@ -997,13 +1059,11 @@ async def get_location(self, location_id: Optional[str] = None) -> Location: For more information, see `Fleet Management API `_. """ - request = GetLocationRequest( - location_id=location_id if location_id else self._location_id if self._location_id else "") + request = GetLocationRequest(location_id=location_id if location_id else self._location_id if self._location_id else "") response: GetLocationResponse = await self._app_client.GetLocation(request, metadata=self._metadata) return response.location - async def update_location(self, location_id: str, name: Optional[str] = None, - parent_location_id: Optional[str] = None) -> Location: + async def update_location(self, location_id: str, name: Optional[str] = None, parent_location_id: Optional[str] = None) -> Location: """Change the name of a location and/or assign it a new parent location. :: @@ -1138,8 +1198,7 @@ async def location_auth(self, location_id: Optional[str] = None) -> LocationAuth For more information, see `Fleet Management API `_. """ - request = LocationAuthRequest( - location_id=location_id if location_id else self._location_id if self._location_id else "") + request = LocationAuthRequest(location_id=location_id if location_id else self._location_id if self._location_id else "") response: LocationAuthResponse = await self._app_client.LocationAuth(request, metadata=self._metadata) return response.auth @@ -1163,10 +1222,8 @@ async def create_location_secret(self, location_id: Optional[str] = None) -> Loc For more information, see `Fleet Management API `_. """ - request = CreateLocationSecretRequest( - location_id=location_id if location_id else self._location_id if self._location_id else "") - response: CreateLocationSecretResponse = await self._app_client.CreateLocationSecret(request, - metadata=self._metadata) + request = CreateLocationSecretRequest(location_id=location_id if location_id else self._location_id if self._location_id else "") + response: CreateLocationSecretResponse = await self._app_client.CreateLocationSecret(request, metadata=self._metadata) return response.auth async def delete_location_secret(self, secret_id: str, location_id: Optional[str] = None) -> None: @@ -1188,8 +1245,7 @@ async def delete_location_secret(self, secret_id: str, location_id: Optional[str For more information, see `Fleet Management API `_. """ request = DeleteLocationSecretRequest( - location_id=location_id if location_id else self._location_id if self._location_id else "", - secret_id=secret_id + location_id=location_id if location_id else self._location_id if self._location_id else "", secret_id=secret_id ) await self._app_client.DeleteLocationSecret(request, metadata=self._metadata) @@ -1232,8 +1288,7 @@ async def get_rover_rental_robots(self, org_id: str) -> List[RoverRentalRobot]: For more information, see `Fleet Management API `_. """ request = GetRoverRentalRobotsRequest(org_id=org_id) - response: GetRoverRentalRobotsResponse = await self._app_client.GetRoverRentalRobots(request, - metadata=self._metadata) + response: GetRoverRentalRobotsResponse = await self._app_client.GetRoverRentalRobots(request, metadata=self._metadata) return list(response.robots) async def get_robot_parts(self, robot_id: str) -> List[RobotPart]: @@ -1294,12 +1349,12 @@ async def get_robot_part(self, robot_part_id: str, dest: Optional[str] = None, i return RobotPart.from_proto(robot_part=response.part) async def get_robot_part_logs( - self, - robot_part_id: str, - filter: Optional[str] = None, - dest: Optional[str] = None, - log_levels: List[str] = [], - num_log_entries: int = 100, + self, + robot_part_id: str, + filter: Optional[str] = None, + dest: Optional[str] = None, + log_levels: List[str] = [], + num_log_entries: int = 100, ) -> List[LogEntry]: """Get the logs associated with a robot part. @@ -1333,8 +1388,7 @@ async def get_robot_part_logs( while True: new_logs, next_page_token = await self._get_robot_part_logs( - robot_part_id=robot_part_id, filter=filter if filter else "", page_token=page_token, - log_levels=log_levels + robot_part_id=robot_part_id, filter=filter if filter else "", page_token=page_token, log_levels=log_levels ) if num_log_entries != 0 and len(new_logs) > logs_left: logs += new_logs[0:logs_left] @@ -1357,20 +1411,19 @@ async def get_robot_part_logs( file.write(f"{time}\t{level}\t{logger_name}\t{file_name:<64}{message}\n") file.flush() except Exception as e: - LOGGER.error(f"Failed to write robot part from robot part with ID [{robot_part_id}]logs to file {dest}", - exc_info=e) + LOGGER.error(f"Failed to write robot part from robot part with ID [{robot_part_id}]logs to file {dest}", exc_info=e) return logs async def _get_robot_part_logs( - self, robot_part_id: str, filter: str, page_token: str, log_levels: List[str] + self, robot_part_id: str, filter: str, page_token: str, log_levels: List[str] ) -> Tuple[List[LogEntry], str]: request = GetRobotPartLogsRequest(id=robot_part_id, filter=filter, page_token=page_token, levels=log_levels) response: GetRobotPartLogsResponse = await self._app_client.GetRobotPartLogs(request, metadata=self._metadata) return [LogEntry.from_proto(log) for log in response.logs], response.next_page_token async def tail_robot_part_logs( - self, robot_part_id: str, errors_only: bool = True, filter: Optional[str] = None + self, robot_part_id: str, errors_only: bool = True, filter: Optional[str] = None ) -> _LogsStream[List[LogEntry]]: """Get an asynchronous iterator that receives live robot part logs. @@ -1424,12 +1477,10 @@ async def get_robot_part_history(self, robot_part_id: str) -> List[RobotPartHist For more information, see `Fleet Management API `_. """ request = GetRobotPartHistoryRequest(id=robot_part_id) - response: GetRobotPartHistoryResponse = await self._app_client.GetRobotPartHistory(request, - metadata=self._metadata) + response: GetRobotPartHistoryResponse = await self._app_client.GetRobotPartHistory(request, metadata=self._metadata) return [RobotPartHistoryEntry.from_proto(part_history) for part_history in response.history] - async def update_robot_part(self, robot_part_id: str, name: str, - robot_config: Optional[Mapping[str, Any]] = None) -> RobotPart: + async def update_robot_part(self, robot_part_id: str, name: str, robot_config: Optional[Mapping[str, Any]] = None) -> RobotPart: """Change the name and assign an optional new configuration to a robot part. :: @@ -1451,8 +1502,7 @@ async def update_robot_part(self, robot_part_id: str, name: str, For more information, see `Fleet Management API `_. """ - request = UpdateRobotPartRequest(id=robot_part_id, name=name, - robot_config=dict_to_struct(robot_config) if robot_config else None) + request = UpdateRobotPartRequest(id=robot_part_id, name=name, robot_config=dict_to_struct(robot_config) if robot_config else None) response: UpdateRobotPartResponse = await self._app_client.UpdateRobotPart(request, metadata=self._metadata) return RobotPart.from_proto(robot_part=response.part) @@ -1576,8 +1626,7 @@ async def create_robot_part_secret(self, robot_part_id: str) -> RobotPart: For more information, see `Fleet Management API `_. """ request = CreateRobotPartSecretRequest(part_id=robot_part_id) - response: CreateRobotPartSecretResponse = await self._app_client.CreateRobotPartSecret(request, - metadata=self._metadata) + response: CreateRobotPartSecretResponse = await self._app_client.CreateRobotPartSecret(request, metadata=self._metadata) return RobotPart.from_proto(response.part) async def delete_robot_part_secret(self, robot_part_id: str, secret_id: str) -> None: @@ -1621,8 +1670,7 @@ async def list_robots(self, location_id: Optional[str] = None) -> List[Robot]: For more information, see `Fleet Management API `_. """ - request = ListRobotsRequest( - location_id=location_id if location_id else self._location_id if self._location_id else "") + request = ListRobotsRequest(location_id=location_id if location_id else self._location_id if self._location_id else "") response: ListRobotsResponse = await self._app_client.ListRobots(request, metadata=self._metadata) return list(response.robots) @@ -1646,8 +1694,7 @@ async def new_robot(self, name: str, location_id: Optional[str] = None) -> str: For more information, see `Fleet Management API `_. """ - request = NewRobotRequest( - location=location_id if location_id else self._location_id if self._location_id else "", name=name) + request = NewRobotRequest(location=location_id if location_id else self._location_id if self._location_id else "", name=name) response: NewRobotResponse = await self._app_client.NewRobot(request, metadata=self._metadata) return response.id @@ -1676,8 +1723,7 @@ async def update_robot(self, robot_id: str, name: str, location_id: Optional[str For more information, see `Fleet Management API `_. """ request = UpdateRobotRequest( - id=robot_id, name=name, - location=location_id if location_id else self._location_id if self._location_id else "" + id=robot_id, name=name, location=location_id if location_id else self._location_id if self._location_id else "" ) response: UpdateRobotResponse = await self._app_client.UpdateRobot(request, metadata=self._metadata) return response.robot @@ -1701,7 +1747,7 @@ async def delete_robot(self, robot_id: str) -> None: await self._app_client.DeleteRobot(request, metadata=self._metadata) async def list_fragments( - self, org_id: str, show_public: bool = True, visibilities: Optional[List[Fragment.Visibility]] = None + self, org_id: str, show_public: bool = True, visibilities: Optional[List[Fragment.Visibility]] = None ) -> List[Fragment]: """Get a list of fragments under the currently authed-to organization. @@ -1780,18 +1826,17 @@ async def create_fragment(self, org_id: str, name: str, config: Optional[Mapping For more information, see `Fleet Management API `_. """ - request = CreateFragmentRequest(name=name, config=dict_to_struct(config) if config else None, - organization_id=org_id) + request = CreateFragmentRequest(name=name, config=dict_to_struct(config) if config else None, organization_id=org_id) response: CreateFragmentResponse = await self._app_client.CreateFragment(request, metadata=self._metadata) return Fragment.from_proto(response.fragment) async def update_fragment( - self, - fragment_id: str, - name: str, - config: Optional[Mapping[str, Any]] = None, - public: Optional[bool] = None, - visibility: Optional[Fragment.Visibility] = None, + self, + fragment_id: str, + name: str, + config: Optional[Mapping[str, Any]] = None, + public: Optional[bool] = None, + visibility: Optional[Fragment.Visibility] = None, ) -> Fragment: """Update a fragment name AND its config and/or visibility. @@ -1852,15 +1897,14 @@ async def delete_fragment(self, fragment_id: str) -> None: request = DeleteFragmentRequest(id=fragment_id) await self._app_client.DeleteFragment(request, metadata=self._metadata) - async def get_fragment_history(self, - id: str, - page_token: Optional[str] = "", - page_limit: Optional[int] = 10) -> list[FragmentHistoryEntry]: + async def get_fragment_history( + self, id: str, page_token: Optional[str] = "", page_limit: Optional[int] = 10 + ) -> List[FragmentHistoryEntry]: """Get fragment history. :: - await cloud.get_fragment_history( + fragment_history = await cloud.get_fragment_history( id = "12a12ab1-1234-5678-abcd-abcd01234567", page_token = "pg-token", page_limit = 10 @@ -1881,19 +1925,17 @@ async def get_fragment_history(self, For more information, see `Fleet Management API `_. """ - request: GetFragmentHistoryRequest = GetFragmentHistoryRequest(id=id, page_token=page_token, - page_limit=page_limit) - response: GetFragmentHistoryResponse = await self._app_client.GetFragmentHistory(request, - metadata=self._metadata) + request: GetFragmentHistoryRequest = GetFragmentHistoryRequest(id=id, page_token=page_token, page_limit=page_limit) + response: GetFragmentHistoryResponse = await self._app_client.GetFragmentHistory(request, metadata=self._metadata) return [FragmentHistoryEntry.from_proto(fragment_history) for fragment_history in response.history] async def add_role( - self, - org_id: str, - identity_id: str, - role: Union[Literal["owner"], Literal["operator"]], - resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], - resource_id: str, + self, + org_id: str, + identity_id: str, + role: Union[Literal["owner"], Literal["operator"]], + resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], + resource_id: str, ) -> None: """Add a role under the currently authed-to organization. @@ -1932,12 +1974,12 @@ async def add_role( await self._app_client.AddRole(request, metadata=self._metadata) async def remove_role( - self, - org_id: str, - identity_id: str, - role: Union[Literal["owner"], Literal["operator"]], - resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], - resource_id: str, + self, + org_id: str, + identity_id: str, + role: Union[Literal["owner"], Literal["operator"]], + resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], + resource_id: str, ) -> None: """Remove a role under the currently authed-to organization. @@ -1976,16 +2018,16 @@ async def remove_role( await self._app_client.RemoveRole(request, metadata=self._metadata) async def change_role( - self, - organization_id: str, - old_identity_id: str, - old_role: Union[Literal["owner"], Literal["operator"]], - old_resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], - old_resource_id: str, - new_identity_id: str, - new_role: Union[Literal["owner"], Literal["operator"]], - new_resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], - new_resource_id: str, + self, + organization_id: str, + old_identity_id: str, + old_role: Union[Literal["owner"], Literal["operator"]], + old_resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], + old_resource_id: str, + new_identity_id: str, + new_role: Union[Literal["owner"], Literal["operator"]], + new_resource_type: Union[Literal["organization"], Literal["location"], Literal["robot"]], + new_resource_id: str, ) -> None: """Changes a role to a new role. @@ -2061,8 +2103,7 @@ async def list_authorizations(self, org_id: str, resource_ids: Optional[List[str For more information, see `Fleet Management API `_. """ request = ListAuthorizationsRequest(organization_id=org_id, resource_ids=resource_ids) - response: ListAuthorizationsResponse = await self._app_client.ListAuthorizations(request, - metadata=self._metadata) + response: ListAuthorizationsResponse = await self._app_client.ListAuthorizations(request, metadata=self._metadata) return list(response.authorizations) async def check_permissions(self, permissions: List[AuthorizedPermissions]) -> List[AuthorizedPermissions]: @@ -2134,7 +2175,7 @@ async def create_registry_item(self, organization_id: str, name: str, type: Pack await self._app_client.CreateRegistryItem(request, metadata=self._metadata) async def update_registry_item( - self, item_id: str, type: PackageType.ValueType, description: str, visibility: Visibility.ValueType + self, item_id: str, type: PackageType.ValueType, description: str, visibility: Visibility.ValueType ) -> None: """Update a registry item. @@ -2153,14 +2194,14 @@ async def update_registry_item( await self._app_client.UpdateRegistryItem(request, metadata=self._metadata) async def list_registry_items( - self, - organization_id: str, - types: List[PackageType.ValueType], - visibilities: List[Visibility.ValueType], - platforms: List[str], - statuses: List[RegistryItemStatus.ValueType], - search_term: Optional[str] = None, - page_token: Optional[str] = None, + self, + organization_id: str, + types: List[PackageType.ValueType], + visibilities: List[Visibility.ValueType], + platforms: List[str], + statuses: List[RegistryItemStatus.ValueType], + search_term: Optional[str] = None, + page_token: Optional[str] = None, ) -> List[RegistryItem]: """List the registry items in an organization. @@ -2231,13 +2272,13 @@ async def create_module(self, org_id: str, name: str) -> Tuple[str, str]: return response.module_id, response.url async def update_module( - self, - module_id: str, - url: str, - description: str, - models: Optional[List[Model]], - entrypoint: str, - public: bool = False, + self, + module_id: str, + url: str, + description: str, + models: Optional[List[Model]], + entrypoint: str, + public: bool = False, ) -> str: """Update the documentation URL, description, models, entrypoint, and/or the visibility of a module. @@ -2348,8 +2389,7 @@ async def list_modules(self, org_id: str) -> List[Module]: # TODO(RSDK-5569): when user-based auth exists, make `name` default to `None` and let # app deal with setting a default. - async def create_key(self, org_id: str, authorizations: List[APIKeyAuthorization], name: Optional[str] = None) -> \ - Tuple[str, str]: + async def create_key(self, org_id: str, authorizations: List[APIKeyAuthorization], name: Optional[str] = None) -> Tuple[str, str]: """Creates a new API key. :: diff --git a/tests/package.json b/tests/package.json new file mode 100644 index 000000000..7a3ad017d --- /dev/null +++ b/tests/package.json @@ -0,0 +1,6 @@ +{ + "name": "tests", + "version": "1.0.0", + "dependencies": { + } +} diff --git a/tests/test_app_client.py b/tests/test_app_client.py index 84b750528..d243cb237 100644 --- a/tests/test_app_client.py +++ b/tests/test_app_client.py @@ -4,10 +4,9 @@ from grpclib.testing import ChannelFor from viam.app.app_client import APIKeyAuthorization, AppClient, Fragment, FragmentVisibilityPB -from viam.proto.app import APIKey, APIKeyWithAuthorizations, Authorization, AuthorizationDetails, AuthorizedPermissions +from viam.proto.app import APIKey, APIKeyWithAuthorizations, AuthenticatorInfo, Authorization, AuthorizationDetails, AuthorizedPermissions from viam.proto.app import Fragment as FragmentPB from viam.proto.app import ( - AuthenticatorInfo, FragmentHistoryEntry, Location, LocationAuth, @@ -38,8 +37,8 @@ IDS = [ID] NAME = "name" CID = "cid" -PAGE_TOKEN="" -PAGE_LIMIT=10 +PAGE_TOKEN = "" +PAGE_LIMIT = 10 TIME = datetime_to_timestamp(datetime.now()) PUBLIC_NAMESPACE = "public_namespace" DEFAULT_REGION = "default_region" From eb1676e55f6886db8225ff948c403142436aa2bd Mon Sep 17 00:00:00 2001 From: Katherine Pioro Date: Tue, 6 Aug 2024 12:36:35 -0400 Subject: [PATCH 3/7] comments --- src/viam/app/app_client.py | 83 ++------------------------------------ tests/mocks/services.py | 7 ++++ tests/package.json | 6 --- tests/test_app_client.py | 11 +++-- 4 files changed, 19 insertions(+), 88 deletions(-) delete mode 100644 tests/package.json diff --git a/src/viam/app/app_client.py b/src/viam/app/app_client.py index 71776a2b4..991f282ce 100644 --- a/src/viam/app/app_client.py +++ b/src/viam/app/app_client.py @@ -8,8 +8,10 @@ from viam import logging from viam.app._logs import _LogsStream, _LogsStreamWithIterator -from viam.proto.app import AddRoleRequest, APIKeyWithAuthorizations, AppServiceStub from viam.proto.app import ( + AddRoleRequest, + APIKeyWithAuthorizations, + AppServiceStub, AuthenticatorInfo, Authorization, AuthorizedPermissions, @@ -384,83 +386,6 @@ def proto(self) -> FragmentPB: visibility=self.visibility.to_proto(), ) - -# class AuthenticatorInfo: -# class AuthenticationType(str, Enum): -# """ -# AuthenticationType specifies the authentication method used by the caller in editing the fragment. -# """ -# -# WEB_OAUTH = "web_oauth" -# """ -# Caller authenticated via oauth, presumably via Viam app frontend. -# """ -# -# API_KEY = "api_key" -# """ -# Caller authenticated via api key. -# """ -# -# ROBOT_PART_SECRET = "robot_part_secret" -# """ -# Caller authenticated via robot part secret. -# """ -# -# LOCATION_SECRET = "location_secret" -# """ -# Caller authenticated via location secret. -# """ -# -# UNSPECIFIED = "unspecified" -# -# """ -# Unspecified authentication type. -# """ -# -# @classmethod -# def from_proto(cls, authentication_type: AuthenticationTypePB.ValueType): -# if authentication_type == AuthenticationTypePB.AUTHENTICATION_TYPE_WEB_OAUTH: -# return AuthenticatorInfo.AuthenticationType.WEB_OAUTH -# if authentication_type == AuthenticationTypePB.AUTHENTICATION_TYPE_API_KEY: -# return AuthenticatorInfo.AuthenticationType.API_KEY -# if authentication_type == AuthenticationTypePB.AUTHENTICATION_TYPE_LOCATION_SECRET: -# return AuthenticatorInfo.AuthenticationType.LOCATION_SECRET -# if authentication_type == AuthenticationTypePB.AUTHENTICATION_TYPE_ROBOT_PART_SECRET: -# return AuthenticatorInfo.AuthenticationType.ROBOT_PART_SECRET -# return AuthenticatorInfo.AuthenticationType.UNSPECIFIED -# -# def to_proto(self) -> AuthenticationTypePB.ValueType: -# if self == self.WEB_OAUTH: -# return AuthenticationTypePB.AUTHENTICATION_TYPE_WEB_OAUTH -# if self == self.API_KEY: -# return AuthenticationTypePB.AUTHENTICATION_TYPE_API_KEY -# if self == self.LOCATION_SECRET: -# return AuthenticationTypePB.AUTHENTICATION_TYPE_LOCATION_SECRET -# if self == self.ROBOT_PART_SECRET: -# return AuthenticationTypePB.AUTHENTICATION_TYPE_ROBOT_PART_SECRET -# return AuthenticationTypePB.AUTHENTICATION_TYPE_UNSPECIFIED -# -# @classmethod -# def from_proto(cls, auth_info: AuthenticatorInfoPB) -> Self: -# self = cls() -# self.type = AuthenticatorInfo.AuthenticationType.from_proto(auth_info.type) -# self.value = auth_info.value -# self.is_deactivated = auth_info.is_deactivated -# return self -# -# @classmethod -# def to_proto(self) -> AuthenticatorInfoPB: -# return AuthenticatorInfoPB( -# is_deactivated=self.is_deactivated, -# value=self.value, -# type=AuthenticatorInfo.AuthenticationType.to_proto(self.type) -# ) -# -# type: AuthenticationType -# value: str -# is_deactivated: bool - - class FragmentHistoryEntry: """A class that mirror the `FragmentHistoryEntry` proto message. @@ -1925,7 +1850,7 @@ async def get_fragment_history( For more information, see `Fleet Management API `_. """ - request: GetFragmentHistoryRequest = GetFragmentHistoryRequest(id=id, page_token=page_token, page_limit=page_limit) + request = GetFragmentHistoryRequest(id=id, page_token=page_token, page_limit=page_limit) response: GetFragmentHistoryResponse = await self._app_client.GetFragmentHistory(request, metadata=self._metadata) return [FragmentHistoryEntry.from_proto(fragment_history) for fragment_history in response.history] diff --git a/tests/mocks/services.py b/tests/mocks/services.py index 9161979d1..f3215663d 100644 --- a/tests/mocks/services.py +++ b/tests/mocks/services.py @@ -1175,6 +1175,8 @@ def __init__( location_auth: LocationAuth, robot_part_history: List[RobotPartHistoryEntry], fragment_history: List[FragmentHistoryEntry], + page_token: str, + page_limit: int, authorizations: List[Authorization], url: str, module: Module, @@ -1186,6 +1188,7 @@ def __init__( items: List[RegistryItem], package_type: PackageType.ValueType, ): + self.page_token = None self.organizations = organizations self.location = location self.robot = robot @@ -1198,6 +1201,8 @@ def __init__( self.location_auth = location_auth self.robot_part_history = robot_part_history self.fragment_history = fragment_history + self.page_token = page_token + self.page_limit=page_limit self.authorizations = authorizations self.url = url self.module = module @@ -1497,6 +1502,8 @@ async def GetFragmentHistory(self, stream: Stream[GetFragmentHistoryRequest, Get request = await stream.recv_message() assert request is not None self.id = request.id + self.page_token = request.page_token + self.page_limit = request.page_limit await stream.send_message(GetFragmentHistoryResponse(history=self.fragment_history)) async def CreateFragment(self, stream: Stream[CreateFragmentRequest, CreateFragmentResponse]) -> None: diff --git a/tests/package.json b/tests/package.json deleted file mode 100644 index 7a3ad017d..000000000 --- a/tests/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "tests", - "version": "1.0.0", - "dependencies": { - } -} diff --git a/tests/test_app_client.py b/tests/test_app_client.py index d243cb237..3253cb93b 100644 --- a/tests/test_app_client.py +++ b/tests/test_app_client.py @@ -37,8 +37,8 @@ IDS = [ID] NAME = "name" CID = "cid" -PAGE_TOKEN = "" -PAGE_LIMIT = 10 +PAGE_TOKEN = "123" +PAGE_LIMIT = 20 TIME = datetime_to_timestamp(datetime.now()) PUBLIC_NAMESPACE = "public_namespace" DEFAULT_REGION = "default_region" @@ -217,6 +217,8 @@ def service() -> MockApp: location_auth=LOCATION_AUTH, robot_part_history=ROBOT_PART_HISTORY, fragment_history=FRAGMENT_HISTORY, + page_limit=PAGE_LIMIT, + page_token=PAGE_TOKEN, authorizations=AUTHORIZATIONS, url=URL, module=MODULE, @@ -641,9 +643,12 @@ async def test_delete_fragment(self, service: MockApp): async def test_get_fragment_history(self, service: MockApp): async with ChannelFor([service]) as channel: client = AppClient(channel, METADATA, ID) - fragment_history = await client.get_fragment_history(id=ID, page_token="", page_limit=20) + fragment_history = await client.get_fragment_history(id=ID, page_token=PAGE_TOKEN, page_limit=PAGE_LIMIT) assert service.fragment.id == ID assert len(fragment_history) == len(FRAGMENT_HISTORY) + assert service.id == ID + assert service.page_token == PAGE_TOKEN + assert service.page_limit == PAGE_LIMIT for i in range(len(FRAGMENT_HISTORY)): assert fragment_history[i].proto == FRAGMENT_HISTORY[i] From a97ed18c089354217b1a89af29c810087b72d88e Mon Sep 17 00:00:00 2001 From: Katherine Pioro Date: Tue, 6 Aug 2024 13:39:10 -0400 Subject: [PATCH 4/7] get rid of .idea changes --- .idea/.gitignore | 8 -------- .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ .idea/viam-python-sdk.iml | 18 ------------------ src/viam/app/app_client.py | 1 + tests/mocks/services.py | 2 +- 6 files changed, 2 insertions(+), 41 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/viam-python-sdk.iml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b81b..000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 93d1fb3ab..000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddfb..000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/viam-python-sdk.iml b/.idea/viam-python-sdk.iml deleted file mode 100644 index daf0933a2..000000000 --- a/.idea/viam-python-sdk.iml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/viam/app/app_client.py b/src/viam/app/app_client.py index 991f282ce..d50039859 100644 --- a/src/viam/app/app_client.py +++ b/src/viam/app/app_client.py @@ -386,6 +386,7 @@ def proto(self) -> FragmentPB: visibility=self.visibility.to_proto(), ) + class FragmentHistoryEntry: """A class that mirror the `FragmentHistoryEntry` proto message. diff --git a/tests/mocks/services.py b/tests/mocks/services.py index f3215663d..d7c08b477 100644 --- a/tests/mocks/services.py +++ b/tests/mocks/services.py @@ -1202,7 +1202,7 @@ def __init__( self.robot_part_history = robot_part_history self.fragment_history = fragment_history self.page_token = page_token - self.page_limit=page_limit + self.page_limit = page_limit self.authorizations = authorizations self.url = url self.module = module From 037dad3e6926809dad629c8ca051a1334d71226a Mon Sep 17 00:00:00 2001 From: Katherine Pioro Date: Tue, 13 Aug 2024 15:30:03 -0400 Subject: [PATCH 5/7] fix test --- src/viam/app/app_client.py | 2 +- tests/mocks/services.py | 7 ++----- tests/test_app_client.py | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/viam/app/app_client.py b/src/viam/app/app_client.py index d50039859..a7d8c32b3 100644 --- a/src/viam/app/app_client.py +++ b/src/viam/app/app_client.py @@ -388,7 +388,7 @@ def proto(self) -> FragmentPB: class FragmentHistoryEntry: - """A class that mirror the `FragmentHistoryEntry` proto message. + """A class that mirrors the `FragmentHistoryEntry` proto message. Use this class to make the attributes of a `viam.proto.app.FragmentHistoryEntry` more accessible and easier to read/interpret. """ diff --git a/tests/mocks/services.py b/tests/mocks/services.py index d7c08b477..e05fe23ec 100644 --- a/tests/mocks/services.py +++ b/tests/mocks/services.py @@ -1175,8 +1175,6 @@ def __init__( location_auth: LocationAuth, robot_part_history: List[RobotPartHistoryEntry], fragment_history: List[FragmentHistoryEntry], - page_token: str, - page_limit: int, authorizations: List[Authorization], url: str, module: Module, @@ -1188,7 +1186,6 @@ def __init__( items: List[RegistryItem], package_type: PackageType.ValueType, ): - self.page_token = None self.organizations = organizations self.location = location self.robot = robot @@ -1201,8 +1198,8 @@ def __init__( self.location_auth = location_auth self.robot_part_history = robot_part_history self.fragment_history = fragment_history - self.page_token = page_token - self.page_limit = page_limit + self.page_token = "" + self.page_limit = 0 self.authorizations = authorizations self.url = url self.module = module diff --git a/tests/test_app_client.py b/tests/test_app_client.py index 3253cb93b..5872bf408 100644 --- a/tests/test_app_client.py +++ b/tests/test_app_client.py @@ -217,8 +217,8 @@ def service() -> MockApp: location_auth=LOCATION_AUTH, robot_part_history=ROBOT_PART_HISTORY, fragment_history=FRAGMENT_HISTORY, - page_limit=PAGE_LIMIT, - page_token=PAGE_TOKEN, + page_limit=0, + page_token="", authorizations=AUTHORIZATIONS, url=URL, module=MODULE, From 9d08e36f2cfab2bc91f1e90211e435110afec0cf Mon Sep 17 00:00:00 2001 From: Katherine Pioro Date: Tue, 13 Aug 2024 15:50:18 -0400 Subject: [PATCH 6/7] fix test --- tests/test_app_client.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_app_client.py b/tests/test_app_client.py index 5872bf408..c807f86c2 100644 --- a/tests/test_app_client.py +++ b/tests/test_app_client.py @@ -217,8 +217,6 @@ def service() -> MockApp: location_auth=LOCATION_AUTH, robot_part_history=ROBOT_PART_HISTORY, fragment_history=FRAGMENT_HISTORY, - page_limit=0, - page_token="", authorizations=AUTHORIZATIONS, url=URL, module=MODULE, From d22f642e6b82ea20e66e5f0ad5a737be01e78f05 Mon Sep 17 00:00:00 2001 From: Katherine Pioro Date: Fri, 16 Aug 2024 10:43:58 -0400 Subject: [PATCH 7/7] remove unnecessary default vals --- tests/mocks/services.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/mocks/services.py b/tests/mocks/services.py index e05fe23ec..1052c9190 100644 --- a/tests/mocks/services.py +++ b/tests/mocks/services.py @@ -1198,8 +1198,6 @@ def __init__( self.location_auth = location_auth self.robot_part_history = robot_part_history self.fragment_history = fragment_history - self.page_token = "" - self.page_limit = 0 self.authorizations = authorizations self.url = url self.module = module