예제 #1
0
    def get_token(auth_url: str, username: str, password: str = None) -> str:
        """
        Generate a token from the GBDX authorization service.

        :param auth_url: URL to authorization service, generally "https://geobigdata.io/auth/v1/oauth/token"
        :param username: GBDX username.
        :param password: GBDX password.
        :return: GBDX token.
        :raises CatalogError: if user is unauthorized, or any other kind of error generating token.
        """

        # GBDX auth service successful response with status 200:
        # {
        #     "access_token": "eyJ0eXAiOiJKV1QiLCJhbGci..."
        #     "expires_in": 604800,
        #     "token_type": "Bearer",
        #     "scope": "openid email offline_access",
        #     "refresh_token": "B35E3o-Kn7LSW5WmjIkdZP3F65inHZrWpGJx7yx_mBsGT"
        # }
        #
        # GBDX auth service failed response with status 400:
        # {
        #     "Error": "400 Bad Request: Invalid POST data"
        # }

        params = {
            'grant_type': 'password',
            'username': username,
            'password': password
        }
        Catalog.logger.info(f'Requesting token from {auth_url}')
        response = requests.post(auth_url, data=params)
        try:
            body = json.loads(response.text)
        except Exception as exp:
            raise CatalogError(
                'Error requesting GBDX token.  Response from GBDX authorization service is not JSON.'
            ) from exp
        token = body.get('access_token')
        if token:
            Catalog.logger.info('Token successfully received.')
            return token
        error = body.get('Error')
        if error:
            raise CatalogError(f'Error requesting GBDX token: {error}')
        raise CatalogError(
            f'Error requesting GBDX token.  Unexpected response from service: {body}'
        )
예제 #2
0
    def get_item(
            self,
            item_id: str,
            collection_id: str = None
    ) -> Union[maxarcat_client.models.Item, None]:
        """
        Return a single item given its ID.

        :param item_id: Item ID
        :param collection_id: Optional collection ID
        :return: Item if it exists, else None.
        """
        if not item_id:
            raise CatalogError('Required parameter item_id is empty.')

        if collection_id:
            Catalog.logger.info(
                f'Get item {item_id} in collection {collection_id}')
            try:
                return self._call_api(
                    self._item_api.get_item_stac_with_http_info,
                    collection_id=collection_id,
                    item_id=item_id)
            except CatalogError as exp:
                if exp.status == 404:
                    return None
                raise

        Catalog.logger.info(f'Get item {item_id}')
        response: maxarcat_client.models.ItemCollection = self._call_api(
            self._stac_api.get_search_stac_with_http_info, ids=item_id)
        if response.features:
            return response.features[0]
        else:
            return None
예제 #3
0
    def _call_api(self, function, *args, **kwargs):
        """
        Call a Swagger client method and process its response.

        :param function: Swagger client method to call
        :param args: Arbitrary arguments to pass to function
        :param kwargs: Arbitrar keyword arguments to pass to function
        :raises CatalogError: if error returned by web service
        :return: Service response parsed as a model object
        """

        try:
            with Catalog.timer():
                (body, status_code, headers) = function(*args, **kwargs)
            Catalog.logger.info(f'HTTP Status: {status_code}')

            request_id = headers.get('X-Maxar-RequestId')
            if request_id:
                Catalog.logger.info(f'Request ID: {request_id}')

            # Secret feature:  Stash response so user can examine it in case of error
            self.last_response = body
            return body
        except maxarcat_client.rest.ApiException as exp:
            # Upon errors the Catalog methods generally return JSON with a "message" property.
            # API Gateway as well sometimes returns JSON with a "message" property.
            # So try to raise our own exception with the message if possible.
            Catalog.logger.info(f'HTTP Status: {exp.status}')
            try:
                body = json.loads(exp.body)
                message = body['message']
                request_id = exp.headers.get('X-Maxar-RequestId')
            except Exception:
                # Fallback on generic error
                raise CatalogError(
                    f'Service error:  HTTP status {exp.status} returned.',
                    status=exp.status) from exp
            else:
                raise CatalogError(message,
                                   status=exp.status,
                                   request_id=request_id) from exp
예제 #4
0
 def get_url_json(self, url: str):
     """
     Perform an HTTP GET on a URL from an item's assets, and parse
     the response as JSON.
     :param url: URL from an item's asset's href property
     :return: Parsed JSON, generally returning a dict
     """
     logging.info(f'GET {url}')
     response = self._request_url(requests.get, url)
     try:
         return json.loads(str(response, encoding='utf-8'))
     except Exception as exp:
         raise CatalogError(
             f'Response from URL is not JSON: {url}') from exp
예제 #5
0
    def _request_url(self, function, *args, **kwargs):
        """
        Process a call to a requests method
        :param function: Method in the requests package, e.g. requests.get
        :param args: Arbitrary arguments to pass to function
        :param kwargs: Arbitrar keyword arguments to pass to function
        :return: Response body
        """
        try:
            headers = {'Authorization': f'Bearer {self._token}'}
            with Catalog.timer():
                response = function(*args, **kwargs, headers=headers)
        except Exception as exp:
            Catalog.logger.error(exp)
            raise

        Catalog.logger.info(f'HTTP Status: {response.status_code}')

        request_id = response.headers.get('X-Maxar-RequestId')
        if request_id:
            Catalog.logger.info(f'Request ID: {request_id}')

        if 200 <= response.status_code < 300:
            return response.content

        # For errors we expect the response to have a JSON properties "message" and "request_id"
        try:
            body = json.loads(response.text)
            message = body['message']
        except Exception:
            # Fallback for any unrecognized error
            raise CatalogError(
                f'Service error:  HTTP status {response.status_code} returned.',
                response, request_id)
        else:
            raise CatalogError(message, response, request_id)
예제 #6
0
 def connect(username: str = None, password: str = None, **kwargs):
     """
     Construct and return a Catalog using a GBDX token generated from the given
     credentials.  Prompt the user to input username and password if they are not provided.
     :param username: GBDX username
     :param password: GBDX password
     :param kwargs: Additional keyword args to pass to Catalog constructor
     :return: New Catalog
     """
     if not username and password:
         raise CatalogError('Cannot specify password without username')
     if not username:
         username = input('Username: '******'Password: ')
     token = Catalog.get_token(Catalog.default_auth_url, username, password)
     del password
     return Catalog(token, **kwargs)
예제 #7
0
    def get_collection(
            self,
            collection_id=None
    ) -> Union[maxarcat_client.models.Collection, None]:
        """
        Get a collection by its ID.

        :param collection_id: Collection to get.
        :raises CatalogError: On error during request.
        :return: Collection model, or None if collection does not exist.
        """
        if not collection_id:
            raise CatalogError('Required parameter collection_id is empty')
        Catalog.logger.info(f'Get collection {collection_id}')
        try:
            return self._call_api(
                self._coll_api.get_collection_stac_with_http_info,
                collection_id=collection_id)
        except CatalogError as exp:
            if exp.status == 404:
                return None
            raise
예제 #8
0
    def search(self,
               collections: list = None,
               bbox: list = None,
               intersects: dict = None,
               start_datetime: datetime = None,
               end_datetime: datetime = None,
               item_ids: list = None,
               where: str = None,
               orderby: str = None,
               limit: int = None,
               page: int = None,
               complete: bool = None) -> maxarcat_client.models.ItemCollection:
        """
        Query the Maxar catalog.

        :param collections: A list of collections to query against
        :param bbox: Bounding box in degrees to search by.  Format is a sequence of the form [west, south, east, north]
            or [west, south, zmin, east, north, zmax].  Optional.
        :param intersects: Geometry to search by.  Dict of GeoJSON.  Optional.
        :param start_datetime:
        :param end_datetime:
        :param item_ids: List of item IDs to query.
        :param where: STAC item properties filter.
        :param orderby: Columns to order result by.
        :param limit: Maximum number of items to return.
        :param page: Page number to return, starting at 1.
        :param complete: If False then include incomplete features in the search.  These are features
            added to the catalog but with incomplete metadata.  Most users should only request complete features.
        :return: GeoJSON FeatureCollection as dict
        """

        body = {}
        if collections:
            body['collections'] = collections
            Catalog.logger.info(f'Search collections: {collections}')
        if bbox:
            body['bbox'] = bbox
            Catalog.logger.info(f'Search bbox: {bbox}')
        if intersects:
            body['intersects'] = intersects
            Catalog.logger.info('Search geometry: {} ...'.format(
                json.dumps(intersects)[:100]))

        # Handle date range
        date_range = None
        if start_datetime and end_datetime:
            date_range = '{}/{}'.format(
                Catalog.format_datetime_iso8601(start_datetime),
                Catalog.format_datetime_iso8601(end_datetime))
        elif start_datetime:
            date_range = '{}/..'.format(
                Catalog.format_datetime_iso8601(start_datetime))
        elif end_datetime:
            date_range = '../{}'.format(
                Catalog.format_datetime_iso8601(end_datetime))
        if date_range:
            body['datetime'] = date_range
            Catalog.logger.info(f'Search time range: {date_range}')

        if item_ids:
            # Guard against a single image ID being passed in, which as a string would be interpreted as a sequence.
            if not isinstance(item_ids, abc.Sequence):
                raise CatalogError('item_ids must be a sequence')
            body['ids'] = list(item_ids)

        if where:
            body['where'] = where
            Catalog.logger.info(f'Search where: {where}')
        if orderby:
            body['orderby'] = orderby
            Catalog.logger.info(f'Search order by: {orderby}')
        if limit:
            body['limit'] = limit
            Catalog.logger.info(f'Search limit: {limit}')
        if page:
            body['page'] = page
            Catalog.logger.info(f'Search page: {page}')
        if complete is not None:
            complete_value = True if complete else False
            body['complete'] = complete_value
            Catalog.logger.info(f'Complete: {complete_value}')

        return self._call_api(self._stac_api.post_search_stac_with_http_info,
                              body=body)