Exemplo n.º 1
0
class WebsocketClient:
    """
    Websocket client class.

    """
    def __init__(self,
                 token: Optional[str] = None,
                 username: Optional[str] = None,
                 password: Optional[str] = None,
                 url: Optional[str] = None,
                 verify: bool = True):
        self._token = token
        self._username = username
        self._password = password
        self._url = url
        self._verify = verify
        self._websocket = None
        self._cm = ConfigManager()

    async def start(self):
        """
        Initializes the websocket and starts the event loop.

        """
        if self._url.startswith('wss:'):
            ssl_context = ssl.SSLContext()
            if self._verify:
                ssl_context.load_verify_locations(self._cm.getCaBundle())
            else:
                ssl_context.check_hostname = False
                ssl_context.verify_mode = ssl.CERT_NONE
        else:
            ssl_context = None

        async with websockets.connect(self._url, ssl=ssl_context) as ws:
            await self.send_recieve(ws)

    async def send_recieve(self, ws: websockets.WebSocketClientProtocol):
        """
        The main loop that sends/receives data.

        :param ws: the web socket client

        """
        while True:
            msg = await ws.recv()

            data = json.loads(msg)
            pretty_print(data)

            if data['type'] == 'message':
                if data['name'] == 'authentication-required':
                    await self.send_auth(ws)

                if data['name'] == 'authentication-succeeded':
                    await self.send_subscribe(ws)

    async def send_auth(self, ws: websockets.WebSocketClientProtocol):
        """
        Sends an authentication request.

        :param ws: the web socket client

        """
        if self._token:
            data = {
                'action': 'authenticate',
                'method': 'jwt',
                'data': {
                    'token': self._token
                }
            }
        else:
            data = {
                'action': 'authenticate',
                'method': 'password',
                'data': {
                    'username': self._username,
                    'password': self._password
                }
            }

        await ws.send(json.dumps(data))

    async def send_subscribe(self, ws: websockets.WebSocketClientProtocol):
        """
        Sends a subscription request.

        :param ws: the web socket client

        """
        data = {'action': 'subscribe'}

        await ws.send(json.dumps(data))
Exemplo n.º 2
0
class RestApiClient:
    """
    A generic REST API Client class.

    """
    def __init__(self,
                 username: Optional[str] = None,
                 password: Optional[str] = None,
                 baseurl: Optional[str] = None,
                 verify: bool = True):

        if baseurl.endswith('/'):
            baseurl = baseurl[:-1]

        self.baseurl = baseurl
        self.username = username
        self.password = password
        self.verify = verify

        self._requests_kwargs = None
        self._logger = logging.getLogger(WEBSERVICE_CLIENT_NAMESPACE)

        if not verify:
            self._logger.warning('SSL verification turned off')

        self._cm = ConfigManager()

    def get_requests_kwargs(self) -> dict:
        #
        # Cache the base kwargs
        #
        if self._requests_kwargs is None:
            #
            # Authentication
            #
            self._requests_kwargs = {'auth': (self.username, self.password)}
            #
            # SSL cert verification
            #
            if self.verify:
                self._requests_kwargs['verify'] = self._cm.getCaBundle()
                self._logger.debug('Using CA path: {}'.format(
                    self._cm.getCaBundle()))

            else:
                self._requests_kwargs['verify'] = False

        return self._requests_kwargs

    def build_url(self, path: str) -> str:
        """
        Given a path, returns a fully qualified URL.

        :param str path: the path for which to build the URL

        :return str: the full URL

        """
        if not path.startswith('/'):
            path = '/{}'.format(path)

        return '{}{}'.format(self.baseurl, path)

    def process_response(
            self, response: requests.Response) -> Optional[Union[list, dict]]:
        """
        Process the response, parsing out the data and handling
        errors/exceptions as required.

        :param requests.Response response:        the response from the
                                                  request

        :return Optional[Union[list, dict]]: the response
        
        :raises RequestError:   if the a non 2xx status code is returned

        """
        #
        # Check for 2xx status code. If it isn't a 2xx status code, then
        # treat it as an error, and handle appropriately
        #
        if round(response.status_code / 100) != 2:
            self.process_error_response(response)

        #
        # Attempt to get JSON from the response, otherwise None
        #
        data = None
        try:
            data = response.json()

        except Exception:
            pass

        self._logger.debug('Response Payload: {}'.format(json.dumps(data)))

        return data

    def process_error_response(self, error_response: requests.Response):
        """
        Process the response as an error.

        :param requests.Response error_response:
                    the response from the request

        :raises RequestError:   if the a non 2xx status code is returned

        """
        self._logger.debug('ERROR Code: {}'.format(error_response.status_code))

        #
        # Attempt to get JSON data from the error response
        #
        data = None
        try:
            data = error_response.json()

        except Exception:
            pass

        self._logger.debug('ERROR Payload: {}'.format(json.dumps(data)))

        raise RequestError("ERROR: API Request Error {}".format(
            error_response.status_code),
                           status_code=error_response.status_code,
                           data=data)

    def get(self, path: str) -> Union[list, dict]:
        """
        Performs a GET request on the specified path. It is assumed the
        result is JSON, and it is decoded as such.

        :param str path: the API path to get from

        :return Union[list, dict]: the response, JSON decoded

        """
        url = self.build_url(path)
        self._logger.debug('GET: {}'.format(url))

        result = requests.get(url, **self.get_requests_kwargs())

        return self.process_response(result)

    def post(self, path: str, data: Optional[dict] = None) -> Optional[dict]:
        """
        Post data to a specified path (API endpoint). Data will automatically
        be encoded as JSON.

        :param str path:  the API path to post to
        :param dict data: the data to post

        :return dict: the response of the request

        """
        url = self.build_url(path)
        self._logger.debug('POST: {}'.format(url))

        result = requests.post(url, json=data, **self.get_requests_kwargs())

        return self.process_response(result)

    def put(self, path: str, data: Optional[dict] = None) -> Optional[dict]:
        """
        Put data to a specified path (API endpoint). Data will automatically
        be encoded as JSON.

        :param str path:  the API path to put to
        :param dict data: the data to post

        :return dict: the response of the request

        """
        url = self.build_url(path)
        self._logger.debug('PUT: {}'.format(url))

        result = requests.put(url, json=data, **self.get_requests_kwargs())

        return self.process_response(result)

    def delete(self, path: str) -> Optional[dict]:
        """
        Delete from the specified path.

        :param str path: the path to delete

        :return dict : the result of the delete

        """
        url = self.build_url(path)
        self._logger.debug('DELETE: {}'.format(url))

        result = requests.delete(url, **self.get_requests_kwargs())

        return self.process_response(result)

    def patch(self, path: str, data: Optional[dict] = None) -> Optional[dict]:
        """
        Patch data to a specified path (API endpoint). Data will automatically
        be encoded as JSON.

        :param str path:  the API path to put to
        :param dict data: the data to post

        :return dict: the response of the request

        """
        url = self.build_url(path)
        self._logger.debug('PATCH: {}'.format(url))

        result = requests.patch(url, json=data, **self.get_requests_kwargs())

        return self.process_response(result)