# Copyright 2020 StrongDM Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# Code generated by protogen. DO NOT EDIT.

import grpc
from . import plumbing
from . import models
from .options_pb2 import *
from .options_pb2_grpc import *
from .spec_pb2 import *
from .spec_pb2_grpc import *
from .tags_pb2 import *
from .tags_pb2_grpc import *
from .access_requests_pb2 import *
from .access_requests_pb2_grpc import *
from .access_request_events_history_pb2 import *
from .access_request_events_history_pb2_grpc import *
from .access_requests_history_pb2 import *
from .access_requests_history_pb2_grpc import *
from .custom_headers_pb2 import *
from .custom_headers_pb2_grpc import *
from .drivers_pb2 import *
from .drivers_pb2_grpc import *
from .account_attachments_pb2 import *
from .account_attachments_pb2_grpc import *
from .account_attachments_history_pb2 import *
from .account_attachments_history_pb2_grpc import *
from .account_grants_pb2 import *
from .account_grants_pb2_grpc import *
from .account_grants_history_pb2 import *
from .account_grants_history_pb2_grpc import *
from .account_permissions_pb2 import *
from .account_permissions_pb2_grpc import *
from .account_resources_pb2 import *
from .account_resources_pb2_grpc import *
from .account_resources_history_pb2 import *
from .account_resources_history_pb2_grpc import *
from .accounts_pb2 import *
from .accounts_pb2_grpc import *
from .accounts_groups_pb2 import *
from .accounts_groups_pb2_grpc import *
from .accounts_groups_history_pb2 import *
from .accounts_groups_history_pb2_grpc import *
from .accounts_history_pb2 import *
from .accounts_history_pb2_grpc import *
from .activities_pb2 import *
from .activities_pb2_grpc import *
from .approval_workflow_approvers_pb2 import *
from .approval_workflow_approvers_pb2_grpc import *
from .approval_workflow_approvers_history_pb2 import *
from .approval_workflow_approvers_history_pb2_grpc import *
from .approval_workflow_steps_pb2 import *
from .approval_workflow_steps_pb2_grpc import *
from .approval_workflow_steps_history_pb2 import *
from .approval_workflow_steps_history_pb2_grpc import *
from .approval_workflows_pb2 import *
from .approval_workflows_pb2_grpc import *
from .approval_workflows_history_pb2 import *
from .approval_workflows_history_pb2_grpc import *
from .control_panel_pb2 import *
from .control_panel_pb2_grpc import *
from .discovery_connectors_pb2 import *
from .discovery_connectors_pb2_grpc import *
from .roles_pb2 import *
from .roles_pb2_grpc import *
from .groups_pb2 import *
from .groups_pb2_grpc import *
from .groups_history_pb2 import *
from .groups_history_pb2_grpc import *
from .groups_roles_pb2 import *
from .groups_roles_pb2_grpc import *
from .groups_roles_history_pb2 import *
from .groups_roles_history_pb2_grpc import *
from .health_checks_pb2 import *
from .health_checks_pb2_grpc import *
from .identity_aliases_pb2 import *
from .identity_aliases_pb2_grpc import *
from .identity_aliases_history_pb2 import *
from .identity_aliases_history_pb2_grpc import *
from .identity_sets_pb2 import *
from .identity_sets_pb2_grpc import *
from .identity_sets_history_pb2 import *
from .identity_sets_history_pb2_grpc import *
from .managed_secrets_pb2 import *
from .managed_secrets_pb2_grpc import *
from .nodes_pb2 import *
from .nodes_pb2_grpc import *
from .nodes_history_pb2 import *
from .nodes_history_pb2_grpc import *
from .organization_history_pb2 import *
from .organization_history_pb2_grpc import *
from .peering_group_nodes_pb2 import *
from .peering_group_nodes_pb2_grpc import *
from .peering_group_peers_pb2 import *
from .peering_group_peers_pb2_grpc import *
from .peering_group_resources_pb2 import *
from .peering_group_resources_pb2_grpc import *
from .peering_groups_pb2 import *
from .peering_groups_pb2_grpc import *
from .policies_pb2 import *
from .policies_pb2_grpc import *
from .policies_history_pb2 import *
from .policies_history_pb2_grpc import *
from .proxy_cluster_keys_pb2 import *
from .proxy_cluster_keys_pb2_grpc import *
from .queries_pb2 import *
from .queries_pb2_grpc import *
from .remote_identities_pb2 import *
from .remote_identities_pb2_grpc import *
from .remote_identities_history_pb2 import *
from .remote_identities_history_pb2_grpc import *
from .remote_identity_groups_pb2 import *
from .remote_identity_groups_pb2_grpc import *
from .remote_identity_groups_history_pb2 import *
from .remote_identity_groups_history_pb2_grpc import *
from .replays_pb2 import *
from .replays_pb2_grpc import *
from .resources_pb2 import *
from .resources_pb2_grpc import *
from .resources_history_pb2 import *
from .resources_history_pb2_grpc import *
from .role_resources_pb2 import *
from .role_resources_pb2_grpc import *
from .role_resources_history_pb2 import *
from .role_resources_history_pb2_grpc import *
from .roles_history_pb2 import *
from .roles_history_pb2_grpc import *
from .secret_engine_policy_pb2 import *
from .secret_engine_policy_pb2_grpc import *
from .secret_engine_types_pb2 import *
from .secret_engine_types_pb2_grpc import *
from .secret_store_types_pb2 import *
from .secret_store_types_pb2_grpc import *
from .secret_stores_pb2 import *
from .secret_stores_pb2_grpc import *
from .secret_engines_pb2 import *
from .secret_engines_pb2_grpc import *
from .secret_store_healths_pb2 import *
from .secret_store_healths_pb2_grpc import *
from .secret_stores_history_pb2 import *
from .secret_stores_history_pb2_grpc import *
from .workflow_approvers_pb2 import *
from .workflow_approvers_pb2_grpc import *
from .workflow_approvers_history_pb2 import *
from .workflow_approvers_history_pb2_grpc import *
from .workflow_roles_pb2 import *
from .workflow_roles_pb2_grpc import *
from .workflow_roles_history_pb2 import *
from .workflow_roles_history_pb2_grpc import *
from .workflows_pb2 import *
from .workflows_pb2_grpc import *
from .workflows_history_pb2 import *
from .workflows_history_pb2_grpc import *
import warnings
import functools
import time


def deprecated(func):
    """This is a decorator which can be used to mark functions
    as deprecated. It will result in a warning being emitted
    when the function is used."""
    @functools.wraps(func)
    def new_func(*args, **kwargs):
        warnings.warn("Call to deprecated function {}.".format(func.__name__),
                      category=DeprecationWarning,
                      stacklevel=2)
        return func(*args, **kwargs)

    return new_func


class AccessRequests:
    '''
     AccessRequests are requests for access to a resource that may match a Workflow.
    See `strongdm.models.AccessRequest`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccessRequestsStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         Lists existing access requests.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccessRequestListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'AccessRequests.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.access_requests:
                    yield plumbing.convert_access_request_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotAccessRequests:
    '''
    SnapshotAccessRequests exposes the read only methods of the AccessRequests
    service for historical queries.
    '''
    def __init__(self, access_requests):
        self.access_requests = access_requests

    def list(self, filter, *args, timeout=None):
        '''
         Lists existing access requests.
        '''
        return self.access_requests.list(filter, *args, timeout=timeout)


class AccessRequestEventsHistory:
    '''
     AccessRequestEventsHistory provides records of all changes to the state of an AccessRequest.
    See `strongdm.models.AccessRequestEventHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccessRequestEventsHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccessRequestEventHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccessRequestEventHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'AccessRequestEventsHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_access_request_event_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class AccessRequestsHistory:
    '''
     AccessRequestsHistory provides records of all changes to the state of an AccessRequest.
    See `strongdm.models.AccessRequestHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccessRequestsHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccessRequestHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccessRequestHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'AccessRequestsHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_access_request_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class AccountAttachments:
    '''
     AccountAttachments assign an account to a role.
    See `strongdm.models.AccountAttachment`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccountAttachmentsStub(channel)

    def create(self, account_attachment, timeout=None):
        '''
         Create registers a new AccountAttachment.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountAttachmentCreateRequest()

        if account_attachment is not None:
            req.account_attachment.CopyFrom(
                plumbing.convert_account_attachment_to_plumbing(
                    account_attachment))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata(
                        'AccountAttachments.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.AccountAttachmentCreateResponse()
        resp.account_attachment = plumbing.convert_account_attachment_to_porcelain(
            plumbing_response.account_attachment)
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one AccountAttachment by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountAttachmentGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('AccountAttachments.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.AccountAttachmentGetResponse()
        resp.account_attachment = plumbing.convert_account_attachment_to_porcelain(
            plumbing_response.account_attachment)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a AccountAttachment by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountAttachmentDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata(
                        'AccountAttachments.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.AccountAttachmentDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccountAttachments matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountAttachmentListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'AccountAttachments.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.account_attachments:
                    yield plumbing.convert_account_attachment_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotAccountAttachments:
    '''
    SnapshotAccountAttachments exposes the read only methods of the AccountAttachments
    service for historical queries.
    '''
    def __init__(self, account_attachments):
        self.account_attachments = account_attachments

    def get(self, id, timeout=None):
        '''
         Get reads one AccountAttachment by ID.
        '''
        return self.account_attachments.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccountAttachments matching a given set of criteria.
        '''
        return self.account_attachments.list(filter, *args, timeout=timeout)


class AccountAttachmentsHistory:
    '''
     AccountAttachmentsHistory records all changes to the state of an AccountAttachment.
    See `strongdm.models.AccountAttachmentHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccountAttachmentsHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccountAttachmentHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountAttachmentHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'AccountAttachmentsHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_account_attachment_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class AccountGrants:
    '''
     AccountGrants assign a resource directly to an account, giving the account the permission to connect to that resource.
    See `strongdm.models.AccountGrant`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccountGrantsStub(channel)

    def create(self, account_grant, timeout=None):
        '''
         Create registers a new AccountGrant.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountGrantCreateRequest()

        if account_grant is not None:
            req.account_grant.CopyFrom(
                plumbing.convert_account_grant_to_plumbing(account_grant))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('AccountGrants.Create',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.AccountGrantCreateResponse()
        resp.account_grant = plumbing.convert_account_grant_to_porcelain(
            plumbing_response.account_grant)
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one AccountGrant by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountGrantGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('AccountGrants.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.AccountGrantGetResponse()
        resp.account_grant = plumbing.convert_account_grant_to_porcelain(
            plumbing_response.account_grant)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a AccountGrant by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountGrantDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('AccountGrants.Delete',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.AccountGrantDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccountGrants matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountGrantListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'AccountGrants.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.account_grants:
                    yield plumbing.convert_account_grant_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotAccountGrants:
    '''
    SnapshotAccountGrants exposes the read only methods of the AccountGrants
    service for historical queries.
    '''
    def __init__(self, account_grants):
        self.account_grants = account_grants

    def get(self, id, timeout=None):
        '''
         Get reads one AccountGrant by ID.
        '''
        return self.account_grants.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccountGrants matching a given set of criteria.
        '''
        return self.account_grants.list(filter, *args, timeout=timeout)


class AccountGrantsHistory:
    '''
     AccountGrantsHistory records all changes to the state of an AccountGrant.
    See `strongdm.models.AccountGrantHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccountGrantsHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccountGrantHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountGrantHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'AccountGrantsHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_account_grant_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class AccountPermissions:
    '''
     AccountPermissions records the granular permissions accounts have, allowing them to execute
     relevant commands via StrongDM's APIs.
    See `strongdm.models.AccountPermission`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccountPermissionsStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Permission records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountPermissionListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'AccountPermissions.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.permissions:
                    yield plumbing.convert_account_permission_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotAccountPermissions:
    '''
    SnapshotAccountPermissions exposes the read only methods of the AccountPermissions
    service for historical queries.
    '''
    def __init__(self, account_permissions):
        self.account_permissions = account_permissions

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Permission records matching a given set of criteria.
        '''
        return self.account_permissions.list(filter, *args, timeout=timeout)


class AccountResources:
    '''
     AccountResources enumerates the resources to which accounts have access.
     The AccountResources service is read-only.
    See `strongdm.models.AccountResource`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccountResourcesStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccountResource records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountResourceListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'AccountResources.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.account_resources:
                    yield plumbing.convert_account_resource_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotAccountResources:
    '''
    SnapshotAccountResources exposes the read only methods of the AccountResources
    service for historical queries.
    '''
    def __init__(self, account_resources):
        self.account_resources = account_resources

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccountResource records matching a given set of criteria.
        '''
        return self.account_resources.list(filter, *args, timeout=timeout)


class AccountResourcesHistory:
    '''
     AccountResourcesHistory records all changes to the state of a AccountResource.
    See `strongdm.models.AccountResourceHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccountResourcesHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccountResourceHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountResourceHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'AccountResourcesHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_account_resource_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class Accounts:
    '''
     Accounts are users that have access to strongDM. There are two types of accounts:
     1. **Users:** humans who are authenticated through username and password or SSO.
     2. **Service Accounts:** machines that are authenticated using a service token.
     3. **Tokens** are access keys with permissions that can be used for authentication.
    See:
    `strongdm.models.Service`
    `strongdm.models.Token`
    `strongdm.models.User`
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccountsStub(channel)

    def create(self, account, timeout=None):
        '''
         Create registers a new Account.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountCreateRequest()

        if account is not None:
            req.account.CopyFrom(plumbing.convert_account_to_plumbing(account))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('Accounts.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.AccountCreateResponse()
        resp.access_key = (plumbing_response.access_key)
        resp.account = plumbing.convert_account_to_porcelain(
            plumbing_response.account)
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.secret_key = (plumbing_response.secret_key)
        resp.token = (plumbing_response.token)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one Account by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('Accounts.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.AccountGetResponse()
        resp.account = plumbing.convert_account_to_porcelain(
            plumbing_response.account)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def update(self, account, timeout=None):
        '''
         Update replaces all the fields of an Account by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountUpdateRequest()

        if account is not None:
            req.account.CopyFrom(plumbing.convert_account_to_plumbing(account))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata('Accounts.Update', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.AccountUpdateResponse()
        resp.account = plumbing.convert_account_to_porcelain(
            plumbing_response.account)
        resp.meta = plumbing.convert_update_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes an Account by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('Accounts.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.AccountDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Accounts matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata('Accounts.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.accounts:
                    yield plumbing.convert_account_to_porcelain(plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotAccounts:
    '''
    SnapshotAccounts exposes the read only methods of the Accounts
    service for historical queries.
    '''
    def __init__(self, accounts):
        self.accounts = accounts

    def get(self, id, timeout=None):
        '''
         Get reads one Account by ID.
        '''
        return self.accounts.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Accounts matching a given set of criteria.
        '''
        return self.accounts.list(filter, *args, timeout=timeout)


class AccountsGroups:
    '''
     An AccountGroup links an account and a group.
    See `strongdm.models.AccountGroup`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccountsGroupsStub(channel)

    def create(self, account_group, timeout=None):
        '''
         Create create a new AccountGroup.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountGroupCreateRequest()

        if account_group is not None:
            req.account_group.CopyFrom(
                plumbing.convert_account_group_to_plumbing(account_group))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('AccountsGroups.Create',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.AccountGroupCreateResponse()
        resp.account_group = plumbing.convert_account_group_to_porcelain(
            plumbing_response.account_group)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one AccountGroup by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountGroupGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('AccountsGroups.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.AccountGroupGetResponse()
        resp.account_group = plumbing.convert_account_group_to_porcelain(
            plumbing_response.account_group)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes an AccountGroup by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountGroupDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('AccountsGroups.Delete',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.AccountGroupDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccountGroups matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountGroupListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'AccountsGroups.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.account_groups:
                    yield plumbing.convert_account_group_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotAccountsGroups:
    '''
    SnapshotAccountsGroups exposes the read only methods of the AccountsGroups
    service for historical queries.
    '''
    def __init__(self, accounts_groups):
        self.accounts_groups = accounts_groups

    def get(self, id, timeout=None):
        '''
         Get reads one AccountGroup by ID.
        '''
        return self.accounts_groups.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccountGroups matching a given set of criteria.
        '''
        return self.accounts_groups.list(filter, *args, timeout=timeout)


class AccountsGroupsHistory:
    '''
     AccountsGroupsHistory records all changes to the state of an AccountGroup.
    See `strongdm.models.AccountGroupHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccountsGroupsHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccountGroupHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountGroupHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'AccountsGroupsHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_account_group_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class AccountsHistory:
    '''
     AccountsHistory records all changes to the state of an Account.
    See `strongdm.models.AccountHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = AccountsHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of AccountHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = AccountHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'AccountsHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_account_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class Activities:
    '''
     An Activity is a record of an action taken against a strongDM deployment, e.g.
     a user creation, resource deletion, sso configuration change, etc. The Activities
     service is read-only.
    See `strongdm.models.Activity`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = ActivitiesStub(channel)

    def get(self, id, timeout=None):
        '''
         Get reads one Activity by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ActivityGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('Activities.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ActivityGetResponse()
        resp.activity = plumbing.convert_activity_to_porcelain(
            plumbing_response.activity)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Activities matching a given set of criteria.
         The 'before' and 'after' filters can be used to control the time
         range of the output activities. If not provided, one week of back
         of activities will be returned.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ActivityListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'Activities.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.activities:
                    yield plumbing.convert_activity_to_porcelain(plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class ApprovalWorkflowApprovers:
    '''
     ApprovalWorkflowApprovers link approval workflow approvers to an ApprovalWorkflowStep
    See `strongdm.models.ApprovalWorkflowApprover`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = ApprovalWorkflowApproversStub(channel)

    def create(self, approval_workflow_approver, timeout=None):
        '''
         Deprecated: Create creates a new approval workflow approver.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowApproverCreateRequest()

        if approval_workflow_approver is not None:
            req.approval_workflow_approver.CopyFrom(
                plumbing.convert_approval_workflow_approver_to_plumbing(
                    approval_workflow_approver))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata(
                        'ApprovalWorkflowApprovers.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ApprovalWorkflowApproverCreateResponse()
        resp.approval_workflow_approver = plumbing.convert_approval_workflow_approver_to_porcelain(
            plumbing_response.approval_workflow_approver)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Deprecated: Get reads one approval workflow approver by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowApproverGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata(
                        'ApprovalWorkflowApprovers.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ApprovalWorkflowApproverGetResponse()
        resp.approval_workflow_approver = plumbing.convert_approval_workflow_approver_to_porcelain(
            plumbing_response.approval_workflow_approver)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Deprecated: Delete deletes an existing approval workflow approver.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowApproverDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata(
                        'ApprovalWorkflowApprovers.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ApprovalWorkflowApproverDeleteResponse()
        resp.id = (plumbing_response.id)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         Deprecated: Lists existing approval workflow approvers.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowApproverListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'ApprovalWorkflowApprovers.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.approval_workflow_approvers:
                    yield plumbing.convert_approval_workflow_approver_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotApprovalWorkflowApprovers:
    '''
    SnapshotApprovalWorkflowApprovers exposes the read only methods of the ApprovalWorkflowApprovers
    service for historical queries.
    '''
    def __init__(self, approval_workflow_approvers):
        self.approval_workflow_approvers = approval_workflow_approvers

    def get(self, id, timeout=None):
        '''
         Deprecated: Get reads one approval workflow approver by ID.
        '''
        return self.approval_workflow_approvers.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         Deprecated: Lists existing approval workflow approvers.
        '''
        return self.approval_workflow_approvers.list(filter,
                                                     *args,
                                                     timeout=timeout)


class ApprovalWorkflowApproversHistory:
    '''
     ApprovalWorkflowApproversHistory records all changes to the state of an ApprovalWorkflowApprover.
    See `strongdm.models.ApprovalWorkflowApproverHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = ApprovalWorkflowApproversHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of ApprovalWorkflowApproverHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowApproverHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'ApprovalWorkflowApproversHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_approval_workflow_approver_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class ApprovalWorkflowSteps:
    '''
     ApprovalWorkflowSteps link approval workflow steps to an ApprovalWorkflow
    See `strongdm.models.ApprovalWorkflowStep`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = ApprovalWorkflowStepsStub(channel)

    def create(self, approval_workflow_step, timeout=None):
        '''
         Deprecated: Create creates a new approval workflow step.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowStepCreateRequest()

        if approval_workflow_step is not None:
            req.approval_workflow_step.CopyFrom(
                plumbing.convert_approval_workflow_step_to_plumbing(
                    approval_workflow_step))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata(
                        'ApprovalWorkflowSteps.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ApprovalWorkflowStepCreateResponse()
        resp.approval_workflow_step = plumbing.convert_approval_workflow_step_to_porcelain(
            plumbing_response.approval_workflow_step)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Deprecated: Get reads one approval workflow step by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowStepGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata(
                        'ApprovalWorkflowSteps.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ApprovalWorkflowStepGetResponse()
        resp.approval_workflow_step = plumbing.convert_approval_workflow_step_to_porcelain(
            plumbing_response.approval_workflow_step)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Deprecated: Delete deletes an existing approval workflow step.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowStepDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata(
                        'ApprovalWorkflowSteps.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ApprovalWorkflowStepDeleteResponse()
        resp.id = (plumbing_response.id)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         Deprecated: Lists existing approval workflow steps.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowStepListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'ApprovalWorkflowSteps.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.approval_workflow_steps:
                    yield plumbing.convert_approval_workflow_step_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotApprovalWorkflowSteps:
    '''
    SnapshotApprovalWorkflowSteps exposes the read only methods of the ApprovalWorkflowSteps
    service for historical queries.
    '''
    def __init__(self, approval_workflow_steps):
        self.approval_workflow_steps = approval_workflow_steps

    def get(self, id, timeout=None):
        '''
         Deprecated: Get reads one approval workflow step by ID.
        '''
        return self.approval_workflow_steps.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         Deprecated: Lists existing approval workflow steps.
        '''
        return self.approval_workflow_steps.list(filter,
                                                 *args,
                                                 timeout=timeout)


class ApprovalWorkflowStepsHistory:
    '''
     ApprovalWorkflowStepsHistory records all changes to the state of an ApprovalWorkflowStep.
    See `strongdm.models.ApprovalWorkflowStepHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = ApprovalWorkflowStepsHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of ApprovalWorkflowStepHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowStepHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'ApprovalWorkflowStepsHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_approval_workflow_step_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class ApprovalWorkflows:
    '''
     ApprovalWorkflows are the mechanism by which requests for access can be viewed by authorized
     approvers and be approved or denied.
    See `strongdm.models.ApprovalWorkflow`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = ApprovalWorkflowsStub(channel)

    def create(self, approval_workflow, timeout=None):
        '''
         Create creates a new approval workflow and requires a name and approval mode for the approval workflow.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowCreateRequest()

        if approval_workflow is not None:
            req.approval_workflow.CopyFrom(
                plumbing.convert_approval_workflow_to_plumbing(
                    approval_workflow))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata(
                        'ApprovalWorkflows.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ApprovalWorkflowCreateResponse()
        resp.approval_workflow = plumbing.convert_approval_workflow_to_porcelain(
            plumbing_response.approval_workflow)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one approval workflow by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('ApprovalWorkflows.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ApprovalWorkflowGetResponse()
        resp.approval_workflow = plumbing.convert_approval_workflow_to_porcelain(
            plumbing_response.approval_workflow)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete deletes an existing approval workflow.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata(
                        'ApprovalWorkflows.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ApprovalWorkflowDeleteResponse()
        resp.id = (plumbing_response.id)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def update(self, approval_workflow, timeout=None):
        '''
         Update updates an existing approval workflow.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowUpdateRequest()

        if approval_workflow is not None:
            req.approval_workflow.CopyFrom(
                plumbing.convert_approval_workflow_to_plumbing(
                    approval_workflow))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata(
                        'ApprovalWorkflows.Update', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ApprovalWorkflowUpdateResponse()
        resp.approval_workflow = plumbing.convert_approval_workflow_to_porcelain(
            plumbing_response.approval_workflow)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         Lists existing approval workflows.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'ApprovalWorkflows.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.approval_workflows:
                    yield plumbing.convert_approval_workflow_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotApprovalWorkflows:
    '''
    SnapshotApprovalWorkflows exposes the read only methods of the ApprovalWorkflows
    service for historical queries.
    '''
    def __init__(self, approval_workflows):
        self.approval_workflows = approval_workflows

    def get(self, id, timeout=None):
        '''
         Get reads one approval workflow by ID.
        '''
        return self.approval_workflows.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         Lists existing approval workflows.
        '''
        return self.approval_workflows.list(filter, *args, timeout=timeout)


class ApprovalWorkflowsHistory:
    '''
     ApprovalWorkflowsHistory records all changes to the state of an ApprovalWorkflow.
    See `strongdm.models.ApprovalWorkflowHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = ApprovalWorkflowsHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of ApprovalWorkflowHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ApprovalWorkflowHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'ApprovalWorkflowsHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_approval_workflow_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class ControlPanel:
    '''
     ControlPanel contains all administrative controls.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = ControlPanelStub(channel)

    def get_sshca_public_key(self, timeout=None):
        '''
         GetSSHCAPublicKey retrieves the SSH CA public key.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ControlPanelGetSSHCAPublicKeyRequest()

        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.GetSSHCAPublicKey(
                    req,
                    metadata=self.parent.get_metadata(
                        'ControlPanel.GetSSHCAPublicKey', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ControlPanelGetSSHCAPublicKeyResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.public_key = (plumbing_response.public_key)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get_rdpca_public_key(self, timeout=None):
        '''
         GetRDPCAPublicKey retrieves the RDP CA public key.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ControlPanelGetRDPCAPublicKeyRequest()

        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.GetRDPCAPublicKey(
                    req,
                    metadata=self.parent.get_metadata(
                        'ControlPanel.GetRDPCAPublicKey', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ControlPanelGetRDPCAPublicKeyResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.public_key = (plumbing_response.public_key)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def verify_jwt(self, token, timeout=None):
        '''
         VerifyJWT reports whether the given JWT token (x-sdm-token) is valid.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ControlPanelVerifyJWTRequest()

        req.token = (token)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.VerifyJWT(
                    req,
                    metadata=self.parent.get_metadata('ControlPanel.VerifyJWT',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ControlPanelVerifyJWTResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.valid = (plumbing_response.valid)
        return resp


class DiscoveryConnectors:
    '''
     A Discovery Connector is a configuration object for performing Resource
     Scans in remote systems such as AWS, GCP, Azure, and other systems.
    See:
    `strongdm.models.AWSConnector`
    `strongdm.models.AzureConnector`
    `strongdm.models.GCPConnector`
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = DiscoveryConnectorsStub(channel)

    def create(self, connector, timeout=None):
        '''
         Create adds a new Connector.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ConnectorCreateRequest()

        if connector is not None:
            req.connector.CopyFrom(
                plumbing.convert_connector_to_plumbing(connector))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata(
                        'DiscoveryConnectors.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ConnectorCreateResponse()
        resp.connector = plumbing.convert_connector_to_porcelain(
            plumbing_response.connector)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one Connector by ID
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ConnectorGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata(
                        'DiscoveryConnectors.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ConnectorGetResponse()
        resp.connector = plumbing.convert_connector_to_porcelain(
            plumbing_response.connector)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def update(self, connector, timeout=None):
        '''
         Update replaces all the fields of a Connector by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ConnectorUpdateRequest()

        if connector is not None:
            req.connector.CopyFrom(
                plumbing.convert_connector_to_plumbing(connector))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata(
                        'DiscoveryConnectors.Update', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ConnectorUpdateResponse()
        resp.connector = plumbing.convert_connector_to_porcelain(
            plumbing_response.connector)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a Connector by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ConnectorDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata(
                        'DiscoveryConnectors.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ConnectorDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Connectors matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ConnectorListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'DiscoveryConnectors.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.connectors:
                    yield plumbing.convert_connector_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotDiscoveryConnectors:
    '''
    SnapshotDiscoveryConnectors exposes the read only methods of the DiscoveryConnectors
    service for historical queries.
    '''
    def __init__(self, discovery_connectors):
        self.discovery_connectors = discovery_connectors

    def get(self, id, timeout=None):
        '''
         Get reads one Connector by ID
        '''
        return self.discovery_connectors.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Connectors matching a given set of criteria.
        '''
        return self.discovery_connectors.list(filter, *args, timeout=timeout)


class Roles:
    '''
     A Role has a list of access rules which determine which Resources the members
     of the Role have access to. An Account can be a member of multiple Roles via
     AccountAttachments.
    See `strongdm.models.Role`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = RolesStub(channel)

    def create(self, role, timeout=None):
        '''
         Create registers a new Role.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RoleCreateRequest()

        if role is not None:
            req.role.CopyFrom(plumbing.convert_role_to_plumbing(role))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('Roles.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.RoleCreateResponse()
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.role = plumbing.convert_role_to_porcelain(plumbing_response.role)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one Role by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RoleGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('Roles.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.RoleGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.role = plumbing.convert_role_to_porcelain(plumbing_response.role)
        return resp

    def update(self, role, timeout=None):
        '''
         Update replaces all the fields of a Role by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RoleUpdateRequest()

        if role is not None:
            req.role.CopyFrom(plumbing.convert_role_to_plumbing(role))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata('Roles.Update', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.RoleUpdateResponse()
        resp.meta = plumbing.convert_update_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.role = plumbing.convert_role_to_porcelain(plumbing_response.role)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a Role by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RoleDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('Roles.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.RoleDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Roles matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RoleListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata('Roles.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.roles:
                    yield plumbing.convert_role_to_porcelain(plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotRoles:
    '''
    SnapshotRoles exposes the read only methods of the Roles
    service for historical queries.
    '''
    def __init__(self, roles):
        self.roles = roles

    def get(self, id, timeout=None):
        '''
         Get reads one Role by ID.
        '''
        return self.roles.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Roles matching a given set of criteria.
        '''
        return self.roles.list(filter, *args, timeout=timeout)


class Groups:
    '''
     A Group is a set of principals.
    See `strongdm.models.Group`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = GroupsStub(channel)

    def create(self, group, timeout=None):
        '''
         Create registers a new Group.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = GroupCreateRequest()

        if group is not None:
            req.group.CopyFrom(plumbing.convert_group_to_plumbing(group))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('Groups.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.GroupCreateResponse()
        resp.group = plumbing.convert_group_to_porcelain(
            plumbing_response.group)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def create_from_roles(self, role_ids, commit, timeout=None):
        deadline = None if timeout is None else time.time() + timeout
        req = GroupCreateFromRolesRequest()

        req.role_ids.extend((role_ids))
        req.commit = (commit)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.CreateFromRoles(
                    req,
                    metadata=self.parent.get_metadata('Groups.CreateFromRoles',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.GroupCreateFromRolesResponse()
        resp.group_from_role = plumbing.convert_repeated_group_from_role_to_porcelain(
            plumbing_response.group_from_role)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one Group by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = GroupGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('Groups.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.GroupGetResponse()
        resp.group = plumbing.convert_group_to_porcelain(
            plumbing_response.group)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def update(self, group, timeout=None):
        '''
         Update replaces all the fields of a Group by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = GroupUpdateRequest()

        if group is not None:
            req.group.CopyFrom(plumbing.convert_group_to_plumbing(group))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata('Groups.Update', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.GroupUpdateResponse()
        resp.group = plumbing.convert_group_to_porcelain(
            plumbing_response.group)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a Group by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = GroupDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('Groups.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.GroupDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Groups matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = GroupListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata('Groups.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.groups:
                    yield plumbing.convert_group_to_porcelain(plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotGroups:
    '''
    SnapshotGroups exposes the read only methods of the Groups
    service for historical queries.
    '''
    def __init__(self, groups):
        self.groups = groups

    def get(self, id, timeout=None):
        '''
         Get reads one Group by ID.
        '''
        return self.groups.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Groups matching a given set of criteria.
        '''
        return self.groups.list(filter, *args, timeout=timeout)


class GroupsHistory:
    '''
     GroupsHistory records all changes to the state of a Group.
    See `strongdm.models.GroupHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = GroupsHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of GroupHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = GroupHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'GroupsHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_group_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class GroupsRoles:
    '''
     A GroupRole is an assignment of a Group to a Role.
    See `strongdm.models.GroupRole`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = GroupsRolesStub(channel)

    def create(self, group_role, timeout=None):
        '''
         Create registers a new GroupRole.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = GroupRoleCreateRequest()

        if group_role is not None:
            req.group_role.CopyFrom(
                plumbing.convert_group_role_to_plumbing(group_role))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('GroupsRoles.Create',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.GroupRoleCreateResponse()
        resp.group_role = plumbing.convert_group_role_to_porcelain(
            plumbing_response.group_role)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one GroupRole by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = GroupRoleGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('GroupsRoles.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.GroupRoleGetResponse()
        resp.group_role = plumbing.convert_group_role_to_porcelain(
            plumbing_response.group_role)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a GroupRole by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = GroupRoleDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('GroupsRoles.Delete',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.GroupRoleDeleteResponse()
        resp.group_role = plumbing.convert_group_role_to_porcelain(
            plumbing_response.group_role)
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of GroupRoles matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = GroupRoleListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'GroupsRoles.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.groups_roles:
                    yield plumbing.convert_group_role_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotGroupsRoles:
    '''
    SnapshotGroupsRoles exposes the read only methods of the GroupsRoles
    service for historical queries.
    '''
    def __init__(self, groups_roles):
        self.groups_roles = groups_roles

    def get(self, id, timeout=None):
        '''
         Get reads one GroupRole by ID.
        '''
        return self.groups_roles.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of GroupRoles matching a given set of criteria.
        '''
        return self.groups_roles.list(filter, *args, timeout=timeout)


class GroupsRolesHistory:
    '''
     GroupsRolesHistory records all changes to the state of a GroupRole.
    See `strongdm.models.GroupRoleHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = GroupsRolesHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of GroupRoleHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = GroupRoleHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'GroupsRolesHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_group_role_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class HealthChecks:
    '''
     HealthChecks lists the last healthcheck between each node and resource.
     Note the unconventional capitalization here is to prevent having a collision with GRPC
    See `strongdm.models.Healthcheck`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = HealthChecksStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Healthchecks matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = HealthcheckListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'HealthChecks.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.healthchecks:
                    yield plumbing.convert_healthcheck_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class IdentityAliases:
    '''
     IdentityAliases assign an alias to an account within an IdentitySet.
     The alias is used as the username when connecting to a identity supported resource.
    See `strongdm.models.IdentityAlias`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = IdentityAliasesStub(channel)

    def create(self, identity_alias, timeout=None):
        '''
         Create registers a new IdentityAlias.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = IdentityAliasCreateRequest()

        if identity_alias is not None:
            req.identity_alias.CopyFrom(
                plumbing.convert_identity_alias_to_plumbing(identity_alias))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('IdentityAliases.Create',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.IdentityAliasCreateResponse()
        resp.identity_alias = plumbing.convert_identity_alias_to_porcelain(
            plumbing_response.identity_alias)
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one IdentityAlias by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = IdentityAliasGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('IdentityAliases.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.IdentityAliasGetResponse()
        resp.identity_alias = plumbing.convert_identity_alias_to_porcelain(
            plumbing_response.identity_alias)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def update(self, identity_alias, timeout=None):
        '''
         Update replaces all the fields of a IdentityAlias by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = IdentityAliasUpdateRequest()

        if identity_alias is not None:
            req.identity_alias.CopyFrom(
                plumbing.convert_identity_alias_to_plumbing(identity_alias))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata('IdentityAliases.Update',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.IdentityAliasUpdateResponse()
        resp.identity_alias = plumbing.convert_identity_alias_to_porcelain(
            plumbing_response.identity_alias)
        resp.meta = plumbing.convert_update_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a IdentityAlias by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = IdentityAliasDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('IdentityAliases.Delete',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.IdentityAliasDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of IdentityAliases matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = IdentityAliasListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'IdentityAliases.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.identity_aliases:
                    yield plumbing.convert_identity_alias_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotIdentityAliases:
    '''
    SnapshotIdentityAliases exposes the read only methods of the IdentityAliases
    service for historical queries.
    '''
    def __init__(self, identity_aliases):
        self.identity_aliases = identity_aliases

    def get(self, id, timeout=None):
        '''
         Get reads one IdentityAlias by ID.
        '''
        return self.identity_aliases.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of IdentityAliases matching a given set of criteria.
        '''
        return self.identity_aliases.list(filter, *args, timeout=timeout)


class IdentityAliasesHistory:
    '''
     IdentityAliasesHistory records all changes to the state of a IdentityAlias.
    See `strongdm.models.IdentityAliasHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = IdentityAliasesHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of IdentityAliasHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = IdentityAliasHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'IdentityAliasesHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_identity_alias_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class IdentitySets:
    '''
     A IdentitySet is a named grouping of Identity Aliases for Accounts.
     An Account's relationship to a IdentitySet is defined via IdentityAlias objects.
    See `strongdm.models.IdentitySet`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = IdentitySetsStub(channel)

    def create(self, identity_set, timeout=None):
        '''
         Create registers a new IdentitySet.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = IdentitySetCreateRequest()

        if identity_set is not None:
            req.identity_set.CopyFrom(
                plumbing.convert_identity_set_to_plumbing(identity_set))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('IdentitySets.Create',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.IdentitySetCreateResponse()
        resp.identity_set = plumbing.convert_identity_set_to_porcelain(
            plumbing_response.identity_set)
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one IdentitySet by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = IdentitySetGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('IdentitySets.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.IdentitySetGetResponse()
        resp.identity_set = plumbing.convert_identity_set_to_porcelain(
            plumbing_response.identity_set)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def update(self, identity_set, timeout=None):
        '''
         Update replaces all the fields of a IdentitySet by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = IdentitySetUpdateRequest()

        if identity_set is not None:
            req.identity_set.CopyFrom(
                plumbing.convert_identity_set_to_plumbing(identity_set))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata('IdentitySets.Update',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.IdentitySetUpdateResponse()
        resp.identity_set = plumbing.convert_identity_set_to_porcelain(
            plumbing_response.identity_set)
        resp.meta = plumbing.convert_update_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a IdentitySet by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = IdentitySetDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('IdentitySets.Delete',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.IdentitySetDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of IdentitySets matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = IdentitySetListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'IdentitySets.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.identity_sets:
                    yield plumbing.convert_identity_set_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotIdentitySets:
    '''
    SnapshotIdentitySets exposes the read only methods of the IdentitySets
    service for historical queries.
    '''
    def __init__(self, identity_sets):
        self.identity_sets = identity_sets

    def get(self, id, timeout=None):
        '''
         Get reads one IdentitySet by ID.
        '''
        return self.identity_sets.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of IdentitySets matching a given set of criteria.
        '''
        return self.identity_sets.list(filter, *args, timeout=timeout)


class IdentitySetsHistory:
    '''
     IdentitySetsHistory records all changes to the state of a IdentitySet.
    See `strongdm.models.IdentitySetHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = IdentitySetsHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of IdentitySetHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = IdentitySetHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'IdentitySetsHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_identity_set_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class ManagedSecrets:
    '''
     ManagedSecret is a private vertical for creating, reading, updating,
     deleting, listing and rotating the managed secrets in the secrets engines as
     an authenticated user.
    See `strongdm.models.ManagedSecret`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = ManagedSecretsStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List returns Managed Secrets from a Secret Engine.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ManagedSecretListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'ManagedSecrets.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.managed_secrets:
                    yield plumbing.convert_managed_secret_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)

    def list_by_actor(self, filter, *args, timeout=None):
        '''
         List returns Managed Secrets for an Actor from a Secret Engine.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ManagedSecretListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.ListByActor(
                        req,
                        metadata=svc.parent.get_metadata(
                            'ManagedSecrets.ListByActor', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.managed_secrets:
                    yield plumbing.convert_managed_secret_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)

    def create(self, managed_secret, timeout=None):
        '''
         Create creates a Managed Secret
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ManagedSecretCreateRequest()

        if managed_secret is not None:
            req.managed_secret.CopyFrom(
                plumbing.convert_managed_secret_to_plumbing(managed_secret))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('ManagedSecrets.Create',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ManagedSecretCreateResponse()
        resp.managed_secret = plumbing.convert_managed_secret_to_porcelain(
            plumbing_response.managed_secret)
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def update(self, managed_secret, timeout=None):
        '''
         Update updates a Managed Secret
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ManagedSecretUpdateRequest()

        if managed_secret is not None:
            req.managed_secret.CopyFrom(
                plumbing.convert_managed_secret_to_plumbing(managed_secret))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata('ManagedSecrets.Update',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ManagedSecretUpdateResponse()
        resp.managed_secret = plumbing.convert_managed_secret_to_porcelain(
            plumbing_response.managed_secret)
        resp.meta = plumbing.convert_update_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def rotate(self, id, timeout=None):
        '''
         Rotate forces rotation of Managed Secret
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ManagedSecretRotateRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Rotate(
                    req,
                    metadata=self.parent.get_metadata('ManagedSecrets.Rotate',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ManagedSecretRotateResponse()
        resp.meta = plumbing.convert_generic_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete deletes a Managed Secret
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ManagedSecretDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('ManagedSecrets.Delete',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ManagedSecretDeleteResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def force_delete(self, id, timeout=None):
        '''
         ForceDelete deletes a Managed Secret regardless of errors on external system
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ManagedSecretDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.ForceDelete(
                    req,
                    metadata=self.parent.get_metadata(
                        'ManagedSecrets.ForceDelete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ManagedSecretDeleteResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get gets details of a Managed Secret without sensitive data
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ManagedSecretGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('ManagedSecrets.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ManagedSecretGetResponse()
        resp.managed_secret = plumbing.convert_managed_secret_to_porcelain(
            plumbing_response.managed_secret)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def retrieve(self, id, timeout=None):
        '''
         Retrieve returns Managed Secret with sensitive data
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ManagedSecretRetrieveRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Retrieve(
                    req,
                    metadata=self.parent.get_metadata(
                        'ManagedSecrets.Retrieve', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ManagedSecretRetrieveResponse()
        resp.managed_secret = plumbing.convert_managed_secret_to_porcelain(
            plumbing_response.managed_secret)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def validate(self, id, timeout=None):
        '''
         Validate returns the result of testing the stored credential against the
         secret engine.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ManagedSecretValidateRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Validate(
                    req,
                    metadata=self.parent.get_metadata(
                        'ManagedSecrets.Validate', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ManagedSecretValidateResponse()
        resp.invalid_info = (plumbing_response.invalid_info)
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.valid = (plumbing_response.valid)
        return resp

    def logs(self, filter, *args, timeout=None):
        '''
         Logs returns the audit records for the managed secret. This may be replaced
         in the future.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ManagedSecretLogsRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.Logs(
                        req,
                        metadata=svc.parent.get_metadata(
                            'ManagedSecrets.Logs', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.managed_secret_logs:
                    yield plumbing.convert_managed_secret_log_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class Nodes:
    '''
     Nodes make up the StrongDM network, and allow your users to connect securely to your resources.
     There are three types of nodes:
     1. **Relay:** creates connectivity to your datasources, while maintaining the egress-only nature of your firewall
     2. **Gateway:** a relay that also listens for connections from StrongDM clients
     3. **Proxy Cluster:** a cluster of workers that together mediate access from clients to resources
    See:
    `strongdm.models.Gateway`
    `strongdm.models.ProxyCluster`
    `strongdm.models.Relay`
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = NodesStub(channel)

    def create(self, node, timeout=None):
        '''
         Create registers a new Node.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = NodeCreateRequest()

        if node is not None:
            req.node.CopyFrom(plumbing.convert_node_to_plumbing(node))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('Nodes.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.NodeCreateResponse()
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.node = plumbing.convert_node_to_porcelain(plumbing_response.node)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.token = (plumbing_response.token)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one Node by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = NodeGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('Nodes.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.NodeGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.node = plumbing.convert_node_to_porcelain(plumbing_response.node)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def update(self, node, timeout=None):
        '''
         Update replaces all the fields of a Node by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = NodeUpdateRequest()

        if node is not None:
            req.node.CopyFrom(plumbing.convert_node_to_plumbing(node))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata('Nodes.Update', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.NodeUpdateResponse()
        resp.meta = plumbing.convert_update_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.node = plumbing.convert_node_to_porcelain(plumbing_response.node)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a Node by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = NodeDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('Nodes.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.NodeDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Nodes matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = NodeListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata('Nodes.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.nodes:
                    yield plumbing.convert_node_to_porcelain(plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)

    def tcp_probe(self, node_id, host, port, timeout=None):
        '''
         TCPProbe instructs a Node to connect to an address via TCP and report the
         result.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = NodeTCPProbeRequest()

        req.node_id = (node_id)
        req.host = (host)
        req.port = (port)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.TCPProbe(
                    req,
                    metadata=self.parent.get_metadata('Nodes.TCPProbe', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.NodeTCPProbeResponse()
        resp.error = (plumbing_response.error)
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.succeeded = (plumbing_response.succeeded)
        return resp


class SnapshotNodes:
    '''
    SnapshotNodes exposes the read only methods of the Nodes
    service for historical queries.
    '''
    def __init__(self, nodes):
        self.nodes = nodes

    def get(self, id, timeout=None):
        '''
         Get reads one Node by ID.
        '''
        return self.nodes.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Nodes matching a given set of criteria.
        '''
        return self.nodes.list(filter, *args, timeout=timeout)


class NodesHistory:
    '''
     NodesHistory records all changes to the state of a Node.
    See `strongdm.models.NodeHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = NodesHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of NodeHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = NodeHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'NodesHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_node_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class OrganizationHistory:
    '''
     OrganizationHistory records all changes to the state of an Organization.
    See `strongdm.models.OrganizationHistoryRecord`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = OrganizationHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of OrganizationHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = OrganizationHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'OrganizationHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_organization_history_record_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class PeeringGroupNodes:
    '''
     PeeringGroupNodes provides the building blocks necessary to obtain attach a node to a peering group.
    See `strongdm.models.PeeringGroupNode`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = PeeringGroupNodesStub(channel)

    def create(self, peering_group_node, timeout=None):
        '''
         Create attaches a Node to a PeeringGroup
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupNodeCreateRequest()

        if peering_group_node is not None:
            req.peering_group_node.CopyFrom(
                plumbing.convert_peering_group_node_to_plumbing(
                    peering_group_node))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata(
                        'PeeringGroupNodes.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PeeringGroupNodeCreateResponse()
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.peering_group_node = plumbing.convert_peering_group_node_to_porcelain(
            plumbing_response.peering_group_node)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete detaches a Node to a PeeringGroup.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupNodeDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata(
                        'PeeringGroupNodes.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PeeringGroupNodeDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads the information of one peering group to node attachment.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupNodeGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('PeeringGroupNodes.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PeeringGroupNodeGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.peering_group_node = plumbing.convert_peering_group_node_to_porcelain(
            plumbing_response.peering_group_node)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of peering group node attachments.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupNodeListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'PeeringGroupNodes.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.peering_group_nodes:
                    yield plumbing.convert_peering_group_node_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class PeeringGroupPeers:
    '''
     PeeringGroupPeers provides the building blocks necessary to link two peering groups.
    See `strongdm.models.PeeringGroupPeer`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = PeeringGroupPeersStub(channel)

    def create(self, peering_group_peer, timeout=None):
        '''
         Create links two peering groups.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupPeerCreateRequest()

        if peering_group_peer is not None:
            req.peering_group_peer.CopyFrom(
                plumbing.convert_peering_group_peer_to_plumbing(
                    peering_group_peer))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata(
                        'PeeringGroupPeers.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PeeringGroupPeerCreateResponse()
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.peering_group_peer = plumbing.convert_peering_group_peer_to_porcelain(
            plumbing_response.peering_group_peer)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete unlinks two peering groups.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupPeerDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata(
                        'PeeringGroupPeers.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PeeringGroupPeerDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads the information of one peering group link.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupPeerGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('PeeringGroupPeers.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PeeringGroupPeerGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.peering_group_peer = plumbing.convert_peering_group_peer_to_porcelain(
            plumbing_response.peering_group_peer)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of peering group links.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupPeerListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'PeeringGroupPeers.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.peering_group_peers:
                    yield plumbing.convert_peering_group_peer_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class PeeringGroupResources:
    '''
     PeeringGroupResources provides the building blocks necessary to obtain attach a resource to a peering group.
    See `strongdm.models.PeeringGroupResource`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = PeeringGroupResourcesStub(channel)

    def create(self, peering_group_resource, timeout=None):
        '''
         Create attaches a Resource to a PeeringGroup
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupResourceCreateRequest()

        if peering_group_resource is not None:
            req.peering_group_resource.CopyFrom(
                plumbing.convert_peering_group_resource_to_plumbing(
                    peering_group_resource))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata(
                        'PeeringGroupResources.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PeeringGroupResourceCreateResponse()
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.peering_group_resource = plumbing.convert_peering_group_resource_to_porcelain(
            plumbing_response.peering_group_resource)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete detaches a Resource to a PeeringGroup
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupResourceDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata(
                        'PeeringGroupResources.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PeeringGroupResourceDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads the information of one peering group to resource attachment.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupResourceGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata(
                        'PeeringGroupResources.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PeeringGroupResourceGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.peering_group_resource = plumbing.convert_peering_group_resource_to_porcelain(
            plumbing_response.peering_group_resource)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of peering group resource attachments.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupResourceListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'PeeringGroupResources.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.peering_group_resources:
                    yield plumbing.convert_peering_group_resource_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class PeeringGroups:
    '''
     PeeringGroups provides the building blocks necessary to obtain explicit network topology and routing.
    See `strongdm.models.PeeringGroup`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = PeeringGroupsStub(channel)

    def create(self, peering_group, timeout=None):
        '''
         Create registers a new PeeringGroup.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupCreateRequest()

        if peering_group is not None:
            req.peering_group.CopyFrom(
                plumbing.convert_peering_group_to_plumbing(peering_group))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('PeeringGroups.Create',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PeeringGroupCreateResponse()
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.peering_group = plumbing.convert_peering_group_to_porcelain(
            plumbing_response.peering_group)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a PeeringGroup by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('PeeringGroups.Delete',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PeeringGroupDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one PeeringGroup by ID. It will load all its dependencies.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('PeeringGroups.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PeeringGroupGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.peering_group = plumbing.convert_peering_group_to_porcelain(
            plumbing_response.peering_group)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Peering Groups.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PeeringGroupListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'PeeringGroups.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.peering_groups:
                    yield plumbing.convert_peering_group_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class Policies:
    '''
     Policies are the collection of one or more statements that enforce fine-grained access
     control for the users of an organization.
    See `strongdm.models.Policy`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = PoliciesStub(channel)

    def create(self, policy, timeout=None):
        '''
         Create creates a new Policy.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PolicyCreateRequest()

        if policy is not None:
            req.policy.CopyFrom(plumbing.convert_policy_to_plumbing(policy))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('Policies.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PolicyCreateResponse()
        resp.policy = plumbing.convert_policy_to_porcelain(
            plumbing_response.policy)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a Policy by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PolicyDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('Policies.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PolicyDeleteResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def update(self, policy, timeout=None):
        '''
         Update replaces all the fields of a Policy by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PolicyUpdateRequest()

        if policy is not None:
            req.policy.CopyFrom(plumbing.convert_policy_to_plumbing(policy))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata('Policies.Update', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PolicyUpdateResponse()
        resp.policy = plumbing.convert_policy_to_porcelain(
            plumbing_response.policy)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one Policy by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PolicyGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('Policies.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.PolicyGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.policy = plumbing.convert_policy_to_porcelain(
            plumbing_response.policy)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Policy matching a given set of criteria
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PolicyListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata('Policies.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.policies:
                    yield plumbing.convert_policy_to_porcelain(plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotPolicies:
    '''
    SnapshotPolicies exposes the read only methods of the Policies
    service for historical queries.
    '''
    def __init__(self, policies):
        self.policies = policies

    def get(self, id, timeout=None):
        '''
         Get reads one Policy by ID.
        '''
        return self.policies.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Policy matching a given set of criteria
        '''
        return self.policies.list(filter, *args, timeout=timeout)


class PoliciesHistory:
    '''
     PoliciesHistory records all changes to the state of a Policy.
    See `strongdm.models.PolicyHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = PoliciesHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of PolicyHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = PoliciesHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'PoliciesHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_policy_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class ProxyClusterKeys:
    '''
     Proxy Cluster Keys are authentication keys for all proxies within a cluster.
     The proxies within a cluster share the same key. One cluster can have
     multiple keys in order to facilitate key rotation.
    See `strongdm.models.ProxyClusterKey`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = ProxyClusterKeysStub(channel)

    def create(self, proxy_cluster_key, timeout=None):
        '''
         Create registers a new ProxyClusterKey.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ProxyClusterKeyCreateRequest()

        if proxy_cluster_key is not None:
            req.proxy_cluster_key.CopyFrom(
                plumbing.convert_proxy_cluster_key_to_plumbing(
                    proxy_cluster_key))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata(
                        'ProxyClusterKeys.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ProxyClusterKeyCreateResponse()
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.proxy_cluster_key = plumbing.convert_proxy_cluster_key_to_porcelain(
            plumbing_response.proxy_cluster_key)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.secret_key = (plumbing_response.secret_key)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one ProxyClusterKey by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ProxyClusterKeyGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('ProxyClusterKeys.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ProxyClusterKeyGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.proxy_cluster_key = plumbing.convert_proxy_cluster_key_to_porcelain(
            plumbing_response.proxy_cluster_key)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a ProxyClusterKey by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ProxyClusterKeyDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata(
                        'ProxyClusterKeys.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ProxyClusterKeyDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of ProxyClusterKeys matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ProxyClusterKeyListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'ProxyClusterKeys.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.proxy_cluster_keys:
                    yield plumbing.convert_proxy_cluster_key_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotProxyClusterKeys:
    '''
    SnapshotProxyClusterKeys exposes the read only methods of the ProxyClusterKeys
    service for historical queries.
    '''
    def __init__(self, proxy_cluster_keys):
        self.proxy_cluster_keys = proxy_cluster_keys

    def get(self, id, timeout=None):
        '''
         Get reads one ProxyClusterKey by ID.
        '''
        return self.proxy_cluster_keys.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of ProxyClusterKeys matching a given set of criteria.
        '''
        return self.proxy_cluster_keys.list(filter, *args, timeout=timeout)


class Queries:
    '''
     A Query is a record of a single client request to a resource, such as a SQL query.
     Long-running SSH, RDP, or Kubernetes interactive sessions also count as queries.
     The Queries service is read-only.
    See `strongdm.models.Query`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = QueriesStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Queries matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = QueryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata('Queries.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.queries:
                    yield plumbing.convert_query_to_porcelain(plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class RemoteIdentities:
    '''
     RemoteIdentities assign a resource directly to an account, giving the account the permission to connect to that resource.
    See `strongdm.models.RemoteIdentity`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = RemoteIdentitiesStub(channel)

    def create(self, remote_identity, timeout=None):
        '''
         Create registers a new RemoteIdentity.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RemoteIdentityCreateRequest()

        if remote_identity is not None:
            req.remote_identity.CopyFrom(
                plumbing.convert_remote_identity_to_plumbing(remote_identity))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata(
                        'RemoteIdentities.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.RemoteIdentityCreateResponse()
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.remote_identity = plumbing.convert_remote_identity_to_porcelain(
            plumbing_response.remote_identity)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one RemoteIdentity by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RemoteIdentityGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('RemoteIdentities.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.RemoteIdentityGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.remote_identity = plumbing.convert_remote_identity_to_porcelain(
            plumbing_response.remote_identity)
        return resp

    def update(self, remote_identity, timeout=None):
        '''
         Update replaces all the fields of a RemoteIdentity by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RemoteIdentityUpdateRequest()

        if remote_identity is not None:
            req.remote_identity.CopyFrom(
                plumbing.convert_remote_identity_to_plumbing(remote_identity))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata(
                        'RemoteIdentities.Update', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.RemoteIdentityUpdateResponse()
        resp.meta = plumbing.convert_update_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.remote_identity = plumbing.convert_remote_identity_to_porcelain(
            plumbing_response.remote_identity)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a RemoteIdentity by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RemoteIdentityDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata(
                        'RemoteIdentities.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.RemoteIdentityDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of RemoteIdentities matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RemoteIdentityListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'RemoteIdentities.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.remote_identities:
                    yield plumbing.convert_remote_identity_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotRemoteIdentities:
    '''
    SnapshotRemoteIdentities exposes the read only methods of the RemoteIdentities
    service for historical queries.
    '''
    def __init__(self, remote_identities):
        self.remote_identities = remote_identities

    def get(self, id, timeout=None):
        '''
         Get reads one RemoteIdentity by ID.
        '''
        return self.remote_identities.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of RemoteIdentities matching a given set of criteria.
        '''
        return self.remote_identities.list(filter, *args, timeout=timeout)


class RemoteIdentitiesHistory:
    '''
     RemoteIdentitiesHistory records all changes to the state of a RemoteIdentity.
    See `strongdm.models.RemoteIdentityHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = RemoteIdentitiesHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of RemoteIdentityHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RemoteIdentityHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'RemoteIdentitiesHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_remote_identity_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class RemoteIdentityGroups:
    '''
     A RemoteIdentityGroup is a named grouping of Remote Identities for Accounts.
     An Account's relationship to a RemoteIdentityGroup is defined via RemoteIdentity objects.
    See `strongdm.models.RemoteIdentityGroup`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = RemoteIdentityGroupsStub(channel)

    def get(self, id, timeout=None):
        '''
         Get reads one RemoteIdentityGroup by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RemoteIdentityGroupGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata(
                        'RemoteIdentityGroups.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.RemoteIdentityGroupGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.remote_identity_group = plumbing.convert_remote_identity_group_to_porcelain(
            plumbing_response.remote_identity_group)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of RemoteIdentityGroups matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RemoteIdentityGroupListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'RemoteIdentityGroups.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.remote_identity_groups:
                    yield plumbing.convert_remote_identity_group_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotRemoteIdentityGroups:
    '''
    SnapshotRemoteIdentityGroups exposes the read only methods of the RemoteIdentityGroups
    service for historical queries.
    '''
    def __init__(self, remote_identity_groups):
        self.remote_identity_groups = remote_identity_groups

    def get(self, id, timeout=None):
        '''
         Get reads one RemoteIdentityGroup by ID.
        '''
        return self.remote_identity_groups.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of RemoteIdentityGroups matching a given set of criteria.
        '''
        return self.remote_identity_groups.list(filter, *args, timeout=timeout)


class RemoteIdentityGroupsHistory:
    '''
     RemoteIdentityGroupsHistory records all changes to the state of a RemoteIdentityGroup.
    See `strongdm.models.RemoteIdentityGroupHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = RemoteIdentityGroupsHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of RemoteIdentityGroupHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RemoteIdentityGroupHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'RemoteIdentityGroupsHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_remote_identity_group_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class Replays:
    '''
     A Replay captures the data transferred over a long-running SSH, RDP, or Kubernetes interactive session
     (otherwise referred to as a query). The Replays service is read-only.
    See `strongdm.models.ReplayChunk`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = ReplaysStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of ReplayChunks for the Query ID specified by the filter criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ReplayListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata('Replays.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.chunks:
                    yield plumbing.convert_replay_chunk_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class Resources:
    '''
     Resources are databases, servers, clusters, websites, or clouds that strongDM
     delegates access to.
    See:
    `strongdm.models.Aerospike`
    `strongdm.models.AKS`
    `strongdm.models.AKSBasicAuth`
    `strongdm.models.AKSServiceAccount`
    `strongdm.models.AKSServiceAccountUserImpersonation`
    `strongdm.models.AKSUserImpersonation`
    `strongdm.models.AmazonEKS`
    `strongdm.models.AmazonEKSInstanceProfile`
    `strongdm.models.AmazonEKSInstanceProfileUserImpersonation`
    `strongdm.models.AmazonEKSUserImpersonation`
    `strongdm.models.AmazonES`
    `strongdm.models.AmazonESIAM`
    `strongdm.models.AmazonMQAMQP091`
    `strongdm.models.AMQP`
    `strongdm.models.Athena`
    `strongdm.models.AthenaIAM`
    `strongdm.models.AuroraMysql`
    `strongdm.models.AuroraMysqlIAM`
    `strongdm.models.AuroraPostgres`
    `strongdm.models.AuroraPostgresIAM`
    `strongdm.models.AWS`
    `strongdm.models.AWSConsole`
    `strongdm.models.AWSConsoleStaticKeyPair`
    `strongdm.models.AWSInstanceProfile`
    `strongdm.models.Azure`
    `strongdm.models.AzureCertificate`
    `strongdm.models.AzureMysql`
    `strongdm.models.AzureMysqlManagedIdentity`
    `strongdm.models.AzurePostgres`
    `strongdm.models.AzurePostgresManagedIdentity`
    `strongdm.models.BigQuery`
    `strongdm.models.Cassandra`
    `strongdm.models.Citus`
    `strongdm.models.ClickHouseHTTP`
    `strongdm.models.ClickHouseMySQL`
    `strongdm.models.ClickHouseTCP`
    `strongdm.models.Clustrix`
    `strongdm.models.Cockroach`
    `strongdm.models.CouchbaseDatabase`
    `strongdm.models.CouchbaseWebUI`
    `strongdm.models.DB2I`
    `strongdm.models.DB2LUW`
    `strongdm.models.DocumentDBHost`
    `strongdm.models.DocumentDBHostIAM`
    `strongdm.models.DocumentDBReplicaSet`
    `strongdm.models.DocumentDBReplicaSetIAM`
    `strongdm.models.Druid`
    `strongdm.models.DynamoDB`
    `strongdm.models.DynamoDBIAM`
    `strongdm.models.Elastic`
    `strongdm.models.ElasticacheRedis`
    `strongdm.models.ElasticacheRedisIAM`
    `strongdm.models.EntraID`
    `strongdm.models.GCP`
    `strongdm.models.GCPConsole`
    `strongdm.models.GCPWIF`
    `strongdm.models.GoogleGKE`
    `strongdm.models.GoogleGKEUserImpersonation`
    `strongdm.models.GoogleSpanner`
    `strongdm.models.Greenplum`
    `strongdm.models.HTTPAuth`
    `strongdm.models.HTTPBasicAuth`
    `strongdm.models.HTTPNoAuth`
    `strongdm.models.Kubernetes`
    `strongdm.models.KubernetesBasicAuth`
    `strongdm.models.KubernetesPodIdentity`
    `strongdm.models.KubernetesServiceAccount`
    `strongdm.models.KubernetesServiceAccountUserImpersonation`
    `strongdm.models.KubernetesUserImpersonation`
    `strongdm.models.Maria`
    `strongdm.models.MCP`
    `strongdm.models.Memcached`
    `strongdm.models.Memsql`
    `strongdm.models.MongoHost`
    `strongdm.models.MongoLegacyHost`
    `strongdm.models.MongoLegacyReplicaset`
    `strongdm.models.MongoReplicaSet`
    `strongdm.models.MongoShardedCluster`
    `strongdm.models.MTLSMysql`
    `strongdm.models.MTLSPostgres`
    `strongdm.models.Mysql`
    `strongdm.models.Neptune`
    `strongdm.models.NeptuneIAM`
    `strongdm.models.OktaGroups`
    `strongdm.models.Oracle`
    `strongdm.models.OracleNNE`
    `strongdm.models.Postgres`
    `strongdm.models.Presto`
    `strongdm.models.RabbitMQAMQP091`
    `strongdm.models.RawTCP`
    `strongdm.models.RDP`
    `strongdm.models.RDPCert`
    `strongdm.models.RDSPostgresIAM`
    `strongdm.models.Redis`
    `strongdm.models.RedisCluster`
    `strongdm.models.Redshift`
    `strongdm.models.RedshiftIAM`
    `strongdm.models.RedshiftServerlessIAM`
    `strongdm.models.SingleStore`
    `strongdm.models.Snowflake`
    `strongdm.models.Snowsight`
    `strongdm.models.SQLServer`
    `strongdm.models.SQLServerAzureAD`
    `strongdm.models.SQLServerKerberosAD`
    `strongdm.models.SSH`
    `strongdm.models.SSHCert`
    `strongdm.models.SSHCustomerKey`
    `strongdm.models.SSHPassword`
    `strongdm.models.Sybase`
    `strongdm.models.SybaseIQ`
    `strongdm.models.Teradata`
    `strongdm.models.Trino`
    `strongdm.models.Vertica`
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = ResourcesStub(channel)

    def enumerate_tags(self, filter, *args, timeout=None):
        '''
         EnumerateTags gets a list of the filter matching tags.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = EnumerateTagsRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.EnumerateTags(
                        req,
                        metadata=svc.parent.get_metadata(
                            'Resources.EnumerateTags', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.matches:
                    yield plumbing.convert_tag_to_porcelain(plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)

    def create(self, resource, timeout=None):
        '''
         Create registers a new Resource.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ResourceCreateRequest()

        if resource is not None:
            req.resource.CopyFrom(
                plumbing.convert_resource_to_plumbing(resource))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('Resources.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ResourceCreateResponse()
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.resource = plumbing.convert_resource_to_porcelain(
            plumbing_response.resource)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one Resource by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ResourceGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('Resources.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ResourceGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.resource = plumbing.convert_resource_to_porcelain(
            plumbing_response.resource)
        return resp

    def update(self, resource, timeout=None):
        '''
         Update replaces all the fields of a Resource by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ResourceUpdateRequest()

        if resource is not None:
            req.resource.CopyFrom(
                plumbing.convert_resource_to_plumbing(resource))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata('Resources.Update', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ResourceUpdateResponse()
        resp.meta = plumbing.convert_update_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.resource = plumbing.convert_resource_to_porcelain(
            plumbing_response.resource)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a Resource by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ResourceDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('Resources.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ResourceDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Resources matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ResourceListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'Resources.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.resources:
                    yield plumbing.convert_resource_to_porcelain(plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)

    def healthcheck(self, id, timeout=None):
        '''
         Healthcheck triggers a remote healthcheck. It may take minutes to propagate across a
         large network of Nodes. The call will return immediately, and the updated health of the
         Resource can be retrieved via Get or List.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ResourceHealthcheckRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Healthcheck(
                    req,
                    metadata=self.parent.get_metadata('Resources.Healthcheck',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.ResourceHealthcheckResponse()
        resp.meta = plumbing.convert_update_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp


class SnapshotResources:
    '''
    SnapshotResources exposes the read only methods of the Resources
    service for historical queries.
    '''
    def __init__(self, resources):
        self.resources = resources

    def get(self, id, timeout=None):
        '''
         Get reads one Resource by ID.
        '''
        return self.resources.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of Resources matching a given set of criteria.
        '''
        return self.resources.list(filter, *args, timeout=timeout)


class ResourcesHistory:
    '''
     ResourcesHistory records all changes to the state of a Resource.
    See `strongdm.models.ResourceHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = ResourcesHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of ResourceHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = ResourceHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'ResourcesHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_resource_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class RoleResources:
    '''
     RoleResources enumerates the resources to which roles have access.
     The RoleResources service is read-only.
    See `strongdm.models.RoleResource`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = RoleResourcesStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of RoleResource records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RoleResourceListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'RoleResources.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.role_resources:
                    yield plumbing.convert_role_resource_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotRoleResources:
    '''
    SnapshotRoleResources exposes the read only methods of the RoleResources
    service for historical queries.
    '''
    def __init__(self, role_resources):
        self.role_resources = role_resources

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of RoleResource records matching a given set of criteria.
        '''
        return self.role_resources.list(filter, *args, timeout=timeout)


class RoleResourcesHistory:
    '''
     RoleResourcesHistory records all changes to the state of a RoleResource.
    See `strongdm.models.RoleResourceHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = RoleResourcesHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of RoleResourceHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RoleResourceHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'RoleResourcesHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_role_resource_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class RolesHistory:
    '''
     RolesHistory records all changes to the state of a Role.
    See `strongdm.models.RoleHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = RolesHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of RoleHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = RoleHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'RolesHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_role_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SecretStores:
    '''
     SecretStores are servers where resource secrets (passwords, keys) are stored.
    See:
    `strongdm.models.ActiveDirectoryStore`
    `strongdm.models.AWSStore`
    `strongdm.models.AWSCertX509Store`
    `strongdm.models.AzureStore`
    `strongdm.models.CyberarkConjurStore`
    `strongdm.models.CyberarkPAMStore`
    `strongdm.models.CyberarkPAMExperimentalStore`
    `strongdm.models.DelineaStore`
    `strongdm.models.GCPStore`
    `strongdm.models.GCPCertX509Store`
    `strongdm.models.KeyfactorSSHStore`
    `strongdm.models.KeyfactorX509Store`
    `strongdm.models.StrongVaultStore`
    `strongdm.models.VaultAppRoleStore`
    `strongdm.models.VaultAppRoleCertSSHStore`
    `strongdm.models.VaultAppRoleCertX509Store`
    `strongdm.models.VaultAWSEC2Store`
    `strongdm.models.VaultAWSEC2CertSSHStore`
    `strongdm.models.VaultAWSEC2CertX509Store`
    `strongdm.models.VaultAWSIAMStore`
    `strongdm.models.VaultAWSIAMCertSSHStore`
    `strongdm.models.VaultAWSIAMCertX509Store`
    `strongdm.models.VaultTLSStore`
    `strongdm.models.VaultTLSCertSSHStore`
    `strongdm.models.VaultTLSCertX509Store`
    `strongdm.models.VaultTokenStore`
    `strongdm.models.VaultTokenCertSSHStore`
    `strongdm.models.VaultTokenCertX509Store`
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = SecretStoresStub(channel)

    def create(self, secret_store, timeout=None):
        deadline = None if timeout is None else time.time() + timeout
        req = SecretStoreCreateRequest()

        if secret_store is not None:
            req.secret_store.CopyFrom(
                plumbing.convert_secret_store_to_plumbing(secret_store))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('SecretStores.Create',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.SecretStoreCreateResponse()
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.secret_store = plumbing.convert_secret_store_to_porcelain(
            plumbing_response.secret_store)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one SecretStore by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretStoreGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('SecretStores.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.SecretStoreGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.secret_store = plumbing.convert_secret_store_to_porcelain(
            plumbing_response.secret_store)
        return resp

    def update(self, secret_store, timeout=None):
        '''
         Update replaces all the fields of a SecretStore by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretStoreUpdateRequest()

        if secret_store is not None:
            req.secret_store.CopyFrom(
                plumbing.convert_secret_store_to_plumbing(secret_store))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata('SecretStores.Update',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.SecretStoreUpdateResponse()
        resp.meta = plumbing.convert_update_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.secret_store = plumbing.convert_secret_store_to_porcelain(
            plumbing_response.secret_store)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete removes a SecretStore by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretStoreDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('SecretStores.Delete',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.SecretStoreDeleteResponse()
        resp.meta = plumbing.convert_delete_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of SecretStores matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretStoreListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'SecretStores.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.secret_stores:
                    yield plumbing.convert_secret_store_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotSecretStores:
    '''
    SnapshotSecretStores exposes the read only methods of the SecretStores
    service for historical queries.
    '''
    def __init__(self, secret_stores):
        self.secret_stores = secret_stores

    def get(self, id, timeout=None):
        '''
         Get reads one SecretStore by ID.
        '''
        return self.secret_stores.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of SecretStores matching a given set of criteria.
        '''
        return self.secret_stores.list(filter, *args, timeout=timeout)


class SecretEngines:
    '''

    See:
    `strongdm.models.ActiveDirectoryEngine`
    `strongdm.models.KeyValueEngine`
    `strongdm.models.MysqlEngine`
    `strongdm.models.PostgresEngine`
    `strongdm.models.SqlserverEngine`
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = SecretEnginesStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List returns a list of Secret Engines
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretEngineListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'SecretEngines.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.secret_engines:
                    yield plumbing.convert_secret_engine_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)

    def get(self, id, timeout=None):
        '''
         Get returns a secret engine details
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretEngineGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('SecretEngines.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.SecretEngineGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.secret_engine = plumbing.convert_secret_engine_to_porcelain(
            plumbing_response.secret_engine)
        return resp

    def create(self, secret_engine, timeout=None):
        '''
         Create creates a secret engine
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretEngineCreateRequest()

        if secret_engine is not None:
            req.secret_engine.CopyFrom(
                plumbing.convert_secret_engine_to_plumbing(secret_engine))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('SecretEngines.Create',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.SecretEngineCreateResponse()
        resp.meta = plumbing.convert_create_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.secret_engine = plumbing.convert_secret_engine_to_porcelain(
            plumbing_response.secret_engine)
        return resp

    def update(self, secret_engine, timeout=None):
        '''
         Update updates a secret engine
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretEngineUpdateRequest()

        if secret_engine is not None:
            req.secret_engine.CopyFrom(
                plumbing.convert_secret_engine_to_plumbing(secret_engine))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata('SecretEngines.Update',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.SecretEngineUpdateResponse()
        resp.meta = plumbing.convert_update_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.secret_engine = plumbing.convert_secret_engine_to_porcelain(
            plumbing_response.secret_engine)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete deletes a secret engine
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretEngineDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('SecretEngines.Delete',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.SecretEngineDeleteResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list_secret_stores(self, filter, *args, timeout=None):
        '''
         ListSecretStores returns a list of Secret Stores that can be used as a backing store
         for Secret Engine
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretStoreListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.ListSecretStores(
                        req,
                        metadata=svc.parent.get_metadata(
                            'SecretEngines.ListSecretStores', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.secret_stores:
                    yield plumbing.convert_secret_store_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)

    def generate_keys(self, secret_engine_id, timeout=None):
        '''
         GenerateKeys generates a private key, stores it in a secret store and stores a public key in a secret engine
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = GenerateKeysRequest()

        req.secret_engine_id = (secret_engine_id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.GenerateKeys(
                    req,
                    metadata=self.parent.get_metadata(
                        'SecretEngines.GenerateKeys', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.GenerateKeysResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def healthcheck(self, secret_engine_id, timeout=None):
        '''
         Healthcheck triggers a healthcheck for all nodes serving a secret engine
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = HealthcheckRequest()

        req.secret_engine_id = (secret_engine_id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Healthcheck(
                    req,
                    metadata=self.parent.get_metadata(
                        'SecretEngines.Healthcheck', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.HealthcheckResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.status = plumbing.convert_repeated_healthcheck_status_to_porcelain(
            plumbing_response.status)
        return resp

    def rotate(self, id, password_policy, timeout=None):
        '''
         Rotate rotates secret engine's credentials
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretEngineRotateRequest()

        req.id = (id)
        if password_policy is not None:
            req.password_policy.CopyFrom(
                plumbing.convert_secret_engine_password_policy_to_plumbing(
                    password_policy))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Rotate(
                    req,
                    metadata=self.parent.get_metadata('SecretEngines.Rotate',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.SecretEngineRotateResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp


class SecretStoreHealths:
    '''
     SecretStoreHealths exposes health states for secret stores.
    See `strongdm.models.SecretStoreHealth`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = SecretStoreHealthsStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List reports the health status of node to secret store pairs.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretStoreHealthListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'SecretStoreHealths.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.secret_store_healths:
                    yield plumbing.convert_secret_store_health_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)

    def healthcheck(self, secret_store_id, timeout=None):
        '''
         Healthcheck triggers a remote healthcheck request for a secret store. It may take minutes
         to propagate across a large network of Nodes. The call will return immediately, and the
         updated health of the Secret Store can be retrieved via List.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretStoreHealthcheckRequest()

        req.secret_store_id = (secret_store_id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Healthcheck(
                    req,
                    metadata=self.parent.get_metadata(
                        'SecretStoreHealths.Healthcheck', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.SecretStoreHealthcheckResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp


class SecretStoresHistory:
    '''
     SecretStoresHistory records all changes to the state of a SecretStore.
    See `strongdm.models.SecretStoreHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = SecretStoresHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of SecretStoreHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = SecretStoreHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'SecretStoresHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_secret_store_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class WorkflowApprovers:
    '''
     WorkflowApprovers is an account or a role with the ability to approve requests bound to a workflow.
    See `strongdm.models.WorkflowApprover`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = WorkflowApproversStub(channel)

    def create(self, workflow_approver, timeout=None):
        '''
         Create creates a new workflow approver
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowApproversCreateRequest()

        if workflow_approver is not None:
            req.workflow_approver.CopyFrom(
                plumbing.convert_workflow_approver_to_plumbing(
                    workflow_approver))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata(
                        'WorkflowApprovers.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.WorkflowApproversCreateResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.workflow_approver = plumbing.convert_workflow_approver_to_porcelain(
            plumbing_response.workflow_approver)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one workflow approver by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowApproverGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('WorkflowApprovers.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.WorkflowApproverGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.workflow_approver = plumbing.convert_workflow_approver_to_porcelain(
            plumbing_response.workflow_approver)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete deletes a workflow approver
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowApproversDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata(
                        'WorkflowApprovers.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.WorkflowApproversDeleteResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         Lists existing workflow approvers.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowApproversListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'WorkflowApprovers.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.workflow_approvers:
                    yield plumbing.convert_workflow_approver_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotWorkflowApprovers:
    '''
    SnapshotWorkflowApprovers exposes the read only methods of the WorkflowApprovers
    service for historical queries.
    '''
    def __init__(self, workflow_approvers):
        self.workflow_approvers = workflow_approvers

    def get(self, id, timeout=None):
        '''
         Get reads one workflow approver by ID.
        '''
        return self.workflow_approvers.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         Lists existing workflow approvers.
        '''
        return self.workflow_approvers.list(filter, *args, timeout=timeout)


class WorkflowApproversHistory:
    '''
     WorkflowApproversHistory provides records of all changes to the state of a WorkflowApprover.
    See `strongdm.models.WorkflowApproverHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = WorkflowApproversHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of WorkflowApproversHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowApproversHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'WorkflowApproversHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_workflow_approver_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class WorkflowRoles:
    '''
     WorkflowRole links a role to a workflow. The linked roles indicate which roles a user must be a part of
     to request access to a resource via the workflow.
    See `strongdm.models.WorkflowRole`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = WorkflowRolesStub(channel)

    def create(self, workflow_role, timeout=None):
        '''
         Create creates a new workflow role
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowRolesCreateRequest()

        if workflow_role is not None:
            req.workflow_role.CopyFrom(
                plumbing.convert_workflow_role_to_plumbing(workflow_role))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('WorkflowRoles.Create',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.WorkflowRolesCreateResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.workflow_role = plumbing.convert_workflow_role_to_porcelain(
            plumbing_response.workflow_role)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one workflow role by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowRoleGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('WorkflowRoles.Get',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.WorkflowRoleGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.workflow_role = plumbing.convert_workflow_role_to_porcelain(
            plumbing_response.workflow_role)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete deletes a workflow role
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowRolesDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('WorkflowRoles.Delete',
                                                      req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.WorkflowRolesDeleteResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         Lists existing workflow roles.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowRolesListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'WorkflowRoles.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.workflow_role:
                    yield plumbing.convert_workflow_role_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotWorkflowRoles:
    '''
    SnapshotWorkflowRoles exposes the read only methods of the WorkflowRoles
    service for historical queries.
    '''
    def __init__(self, workflow_roles):
        self.workflow_roles = workflow_roles

    def get(self, id, timeout=None):
        '''
         Get reads one workflow role by ID.
        '''
        return self.workflow_roles.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         Lists existing workflow roles.
        '''
        return self.workflow_roles.list(filter, *args, timeout=timeout)


class WorkflowRolesHistory:
    '''
     WorkflowRolesHistory provides records of all changes to the state of a WorkflowRole
    See `strongdm.models.WorkflowRoleHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = WorkflowRolesHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of WorkflowRolesHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowRolesHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'WorkflowRolesHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_workflow_role_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class Workflows:
    '''
     Workflows are the collection of rules that define the resources to which access can be requested,
     the users that can request that access, and the mechanism for approving those requests which can either
     be automatic approval or a set of users authorized to approve the requests.
    See `strongdm.models.Workflow`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = WorkflowsStub(channel)

    def create(self, workflow, timeout=None):
        '''
         Create creates a new workflow and requires a name for the workflow.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowCreateRequest()

        if workflow is not None:
            req.workflow.CopyFrom(
                plumbing.convert_workflow_to_plumbing(workflow))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Create(
                    req,
                    metadata=self.parent.get_metadata('Workflows.Create', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.WorkflowCreateResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.workflow = plumbing.convert_workflow_to_porcelain(
            plumbing_response.workflow)
        return resp

    def get(self, id, timeout=None):
        '''
         Get reads one workflow by ID.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowGetRequest()
        if self.parent.snapshot_datetime is not None:
            req.meta.CopyFrom(GetRequestMetadata())
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Get(
                    req,
                    metadata=self.parent.get_metadata('Workflows.Get', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.WorkflowGetResponse()
        resp.meta = plumbing.convert_get_response_metadata_to_porcelain(
            plumbing_response.meta)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.workflow = plumbing.convert_workflow_to_porcelain(
            plumbing_response.workflow)
        return resp

    def delete(self, id, timeout=None):
        '''
         Delete deletes an existing workflow.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowDeleteRequest()

        req.id = (id)
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Delete(
                    req,
                    metadata=self.parent.get_metadata('Workflows.Delete', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.WorkflowDeleteResponse()
        resp.id = (plumbing_response.id)
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        return resp

    def update(self, workflow, timeout=None):
        '''
         Update updates an existing workflow.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowUpdateRequest()

        if workflow is not None:
            req.workflow.CopyFrom(
                plumbing.convert_workflow_to_plumbing(workflow))
        tries = 0
        plumbing_response = None
        while True:
            t = None if deadline is None else deadline - time.time()
            try:
                plumbing_response = self.stub.Update(
                    req,
                    metadata=self.parent.get_metadata('Workflows.Update', req),
                    timeout=t)
            except Exception as e:
                if self.parent.shouldRetry(tries, e, deadline):
                    tries += 1
                    time.sleep(self.parent.exponentialBackoff(tries, deadline))
                    continue
                raise plumbing.convert_error_to_porcelain(e) from e
            break

        resp = models.WorkflowUpdateResponse()
        resp.rate_limit = plumbing.convert_rate_limit_metadata_to_porcelain(
            plumbing_response.rate_limit)
        resp.workflow = plumbing.convert_workflow_to_porcelain(
            plumbing_response.workflow)
        return resp

    def list(self, filter, *args, timeout=None):
        '''
         Lists existing workflows.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'Workflows.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.workflows:
                    yield plumbing.convert_workflow_to_porcelain(plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)


class SnapshotWorkflows:
    '''
    SnapshotWorkflows exposes the read only methods of the Workflows
    service for historical queries.
    '''
    def __init__(self, workflows):
        self.workflows = workflows

    def get(self, id, timeout=None):
        '''
         Get reads one workflow by ID.
        '''
        return self.workflows.get(id, timeout=timeout)

    def list(self, filter, *args, timeout=None):
        '''
         Lists existing workflows.
        '''
        return self.workflows.list(filter, *args, timeout=timeout)


class WorkflowsHistory:
    '''
     WorkflowsHistory provides records of all changes to the state of a Workflow.
    See `strongdm.models.WorkflowHistory`.
    '''
    def __init__(self, channel, client):
        self.parent = client
        self.stub = WorkflowsHistoryStub(channel)

    def list(self, filter, *args, timeout=None):
        '''
         List gets a list of WorkflowHistory records matching a given set of criteria.
        '''
        deadline = None if timeout is None else time.time() + timeout
        req = WorkflowHistoryListRequest()
        req.meta.CopyFrom(ListRequestMetadata())
        if self.parent.page_limit > 0:
            req.meta.limit = self.parent.page_limit
        if self.parent.snapshot_datetime is not None:
            req.meta.snapshot_at.FromDatetime(self.parent.snapshot_datetime)

        req.filter = plumbing.quote_filter_args(filter, *args)

        def generator(svc, req):
            tries = 0
            while True:
                t = None if deadline is None else deadline - time.time()
                try:
                    plumbing_response = svc.stub.List(
                        req,
                        metadata=svc.parent.get_metadata(
                            'WorkflowsHistory.List', req),
                        timeout=t)
                except Exception as e:
                    if self.parent.shouldRetry(tries, e, deadline):
                        tries += 1
                        time.sleep(
                            self.parent.exponentialBackoff(tries, deadline))
                        continue
                    raise plumbing.convert_error_to_porcelain(e) from e
                tries = 0
                for plumbing_item in plumbing_response.history:
                    yield plumbing.convert_workflow_history_to_porcelain(
                        plumbing_item)
                if plumbing_response.meta.next_cursor == '':
                    break
                req.meta.cursor = plumbing_response.meta.next_cursor

        return generator(self, req)
