def get_build(self, train_id, build_id): """ Return information about a specific build :param str train_id: The train ID that owns the provided build ID :param str build_id: The build ID to retrieve information for :returns: :py:class:`fl33t.models.Build` :raises InvalidBuildIdError: if the build does not exist :raises UnprivilegedToken: if the session token does not have enough privilege to perform this action :raises Fl33tApiException: if there was a 5xx error returned by fl33t """ url = "/".join((self.base_team_url, 'train/{}/build/{}'.format(train_id, build_id))) result = self.get(url) if result.status_code in (400, 404): raise InvalidBuildIdError('train={}:{}'.format(train_id, build_id)) if 'build' in result.json(): build = result.json()['build'] return Build(client=self, **build) raise Fl33tApiException(ENDPOINT_FAILED_MSG.format('build retrieval'))
def has_upgrade_available(self, device_id, currently_installed_id=None): """ Does this device have pending firmware updates? :param str device_id: The device ID to check for updates :param currently_installed_id: If provided, the build ID currently installed on the device :type currently_installed_id: str or None :returns: :py:class:`fl33t.models.Build` or False :raises UnprivilegedToken: if the session token does not have enough privilege to perform this action :raises Fl33tApiException: if there was a 5xx error returned by fl33t """ url = '/'.join( (self.base_team_url, 'device/{}/build'.format(device_id))) params = None if currently_installed_id: params = {'installed_build_id': currently_installed_id} result = self.get(url, params=params) # No update available. if result.status_code == 204: return False if 'build' in result.json(): build = result.json()['build'] return self.Build(**build) raise Fl33tApiException( ENDPOINT_FAILED_MSG.format('device firmware check'))
def get_session(self, session_token): """ Return information about a specific session_token :param str session_token: The session token that you want information about :returns: :py:class:`fl33t.models.Session` :raises InvalidSessionIdError: if the session token does not exist :raises UnprivilegedToken: if the session token does not have enough privilege to perform this action :raises Fl33tApiException: if there was a 5xx error returned by fl33t """ url = "/".join( (self.base_team_url, 'session/{}'.format(session_token))) result = self.get(url) if result.status_code in (400, 404): raise InvalidSessionIdError() if 'session' in result.json(): session = result.json()['session'] return Session(client=self, **session) raise Fl33tApiException( ENDPOINT_FAILED_MSG.format('session retrieval'))
def create(self): """ Create this object in fl33t :returns: :py:class:`self`, on success or False, on failure :raises UnprivilegedToken: if the session token does not have enough privilege to perform this action :raises Fl33tApiException: if there was a 5xx error returned by fl33t :raises Fl33tClientException: if the model was instantiated without a :py:class:`fl33t.Fl33tClient` """ if not self._client: raise Fl33tClientException() class_name = self.__class__.__name__.lower() result = self._client.post(self.base_url, data=self) # 409 is a duplicate ID error for devices. For other models, this # error shouldn't occur because all other ID's are generated by fl33t if result.status_code == 409: raise Fl33tApiException(result.text) if class_name not in result.json(): self.logger.exception('Could not create {}'.format(class_name)) return False data = result.json()[class_name] for key in data.keys(): setattr(self, key, data[key]) return self
def _paginator(self, single_page, url, params, model_name, model, error_msg): """ Paginate through a specific listing endpoint. :param bool single_page: If we should be doing pagination, or simply returning the single page of results. :param str url: The URL to use for the page retrieval :param dict params: A :py:`dict` of parameteres to send with the request :param str model_name: The name of the model that will be used to return data :param model: The actual class to be used to return data :type model: Any subclass of :py:class:`fl33t.models.Base` :param str error_msg: The error message to return in the case of an API communication exception :yields: generator of the provided `model` type :raises UnprivilegedToken: if the session token does not have enough privilege to perform this action :raises Fl33tApiException: if there was a 5xx error returned by fl33t """ total_count = None plural_model = '{}s'.format(model_name) while True: result = self.get(url, params=params) data = result.json() if plural_model not in data: raise Fl33tApiException(ENDPOINT_FAILED_MSG.format(error_msg)) record_count = 0 for item in data[plural_model]: record_count += 1 yield model(client=self, **item) if single_page: break total_returned = params['offset'] + record_count if total_count is None: if '{}_count'.format(model_name) in data: total_count = data['{}_count'.format(model_name)] else: total_count = 0 if total_count > total_returned: params['offset'] = params['offset'] + params['limit'] else: break
def get_device(self, device_id): """ Get a device by ID from fl33t. :param str device_id: The device ID to retrieve information for :returns: :py:class:`fl33t.models.Device` :raises InvalidDeviceIdError: if the device does not exist :raises UnprivilegedToken: if the session token does not have enough privilege to perform this action :raises Fl33tApiException: if there was a 5xx error returned by fl33t """ url = "/".join((self.base_team_url, 'device/{}'.format(device_id))) result = self.get(url) if result.status_code in (400, 404): raise InvalidDeviceIdError(device_id) if 'device' in result.json(): device = result.json()['device'] return Device(client=self, **device) raise Fl33tApiException(ENDPOINT_FAILED_MSG.format('device retrieval'))
def get_train(self, train_id): """ Return information about a specific train :param str train_id: The train ID to retrieve information for :returns: :py:class:`fl33t.models.Train` :raises InvalidTrainIdError: if the train does not exist :raises UnprivilegedToken: if the session token does not have enough privilege to perform this action :raises Fl33tApiException: if there was a 5xx error returned by fl33t """ url = "/".join((self.base_team_url, 'train/{}'.format(train_id))) result = self.get(url) if result.status_code in (400, 404): raise InvalidTrainIdError(train_id) if 'train' in result.json(): train = result.json()['train'] return Train(client=self, **train) raise Fl33tApiException(ENDPOINT_FAILED_MSG.format('train retrieval'))
def get_fleet(self, fleet_id): """ Return information about a specific fleet :param str fleet_id: The fleet ID to retrieve information for :returns: :py:class:`fl33t.models.Fleet` :raises InvalidFleetIdError: if the fleet does not exist :raises UnprivilegedToken: if the session token does not have enough privilege to perform this action :raises Fl33tApiException: if there was a 5xx error returned by fl33t """ url = "/".join((self.base_team_url, 'fleet/{}'.format(fleet_id))) result = self.get(url) if result.status_code in (400, 404): raise InvalidFleetIdError(fleet_id) if 'fleet' in result.json(): fleet = result.json()['fleet'] return Fleet(client=self, **fleet) raise Fl33tApiException(ENDPOINT_FAILED_MSG.format('fleet retrieval'))
def _request(self, method, url, **kwargs): """ Wrapper for `requests` methods to include the bearer token If you need to make a call without the bearer token, make it directly against `requests` :param str method: The request method to use :param str url: The URL to request :param kwargs: Any keyword args that :py:module:`requests` methods accept :returns: :py:class:`requests.Response` :raises UnprivilegedToken: if the session token does not have enough privilege to perform this action :raises Fl33tApiException: if there was a 5xx error returned by fl33t """ headers = kwargs.get('headers') if kwargs.get('headers') else {} if 'Authorization' not in headers: headers['Authorization'] = 'Bearer {}'.format(self.token) if 'Content-Type' not in headers: headers['Content-Type'] = 'application/json' if 'Accept' not in headers: headers['Accept'] = 'application/json' kwargs['headers'] = headers data = kwargs.pop('data', None) if data and isinstance(data, BaseModel): data = data.to_json() params = kwargs.pop('params', None) self.logger.debug('Sending {} request with params: {}'.format( method, params)) self.logger.debug('Sending {} request with payload: {}'.format( method, data)) method = getattr(requests, method.lower()) try: result = method(url, params=params, data=data, **kwargs) result.raise_for_status() except requests.exceptions.HTTPError as exc: if not isinstance(result, requests.Response): # The request failed in a way that we don't handle, give the # user the raised exception directly. raise if result.status_code in (401, 403): raise UnprivilegedToken(url) if result.status_code >= 500: # Raise that something went wrong with the fl33t API request message = '{} returned a {} error: {}'.format( url, result.status_code, result.text) raise Fl33tApiException(message) if result.status_code not in (400, 404, 409): # Log the exception if the request failed with a status code # that we don't handle gracefully. 400/404 (InvalidIdError) and # 409 (DuplicateIdError) are meant to be handled by the caller. self.logger.exception(exc) return result
def _paginator(self, offset, limit, url, params, model_name, model, error_msg): """ Paginate through a specific listing endpoint. :param offset: If provided, the starting offset for result sets. If not provided, will paginate through *all* records available, effectively ignoring the `limit` parameter. To only retrieve the first page of results, you must specifically set offset to `0`. :type offset: int or None :param limit: If provided, the number of records to return. Defaults to :py:attr:`default_query_limit` :type limit: int or None :param str url: The URL to use for the page retrieval :param dict params: A :py:`dict` of parameteres to send with the request :param str model_name: The name of the model that will be used to return data :param model: The actual class to be used to return data :type model: Any subclass of :py:class:`fl33t.models.Base` :param str error_msg: The error message to return in the case of an API communication exception :yields: generator of the provided `model` type :raises UnprivilegedToken: if the session token does not have enough privilege to perform this action :raises Fl33tApiException: if there was a 5xx error returned by fl33t """ total_count = None plural_model = '{}s'.format(model_name) params.update(self._build_offset_limit(offset=offset, limit=limit)) single_page_only = not (offset is None and limit is None) while True: result = self.get(url, params=params) data = result.json() if plural_model not in data: raise Fl33tApiException(ENDPOINT_FAILED_MSG.format(error_msg)) record_count = 0 for item in data[plural_model]: record_count += 1 yield model(client=self, **item) if single_page_only: break total_returned = params['offset'] + record_count if total_count is None: if '{}_count'.format(model_name) in data: total_count = data['{}_count'.format(model_name)] else: total_count = 0 if total_count > total_returned: params['offset'] = params['offset'] + params['limit'] else: break