示例#1
0
    def _handle_error(self, text):
        """
        Parse the response from a request to see if it failed

        Parameters
        ----------
        text : str
            The decoded body of the response.

        Raises
        ------
        InvalidColumnError :
            If ``select`` included an invalid column.
        InvalidTableError :
            If the queried ``table`` does not exist.
        RemoteServiceError :
            If anything else went wrong.
        """
        # Error messages will always be formatted starting with the word "ERROR"
        if not text.startswith("ERROR"):
            return

        # Some errors have the form:
        #   Error type: ...
        #   Message: ...
        # so we'll parse those to try to provide some reasonable feedback to the user
        error_type = None
        error_message = None
        for line in text.replace("<br>", "").splitlines():
            match = re.search(r"Error Type:\s(.+)$", line)
            if match:
                error_type = match.group(1).strip()
                continue

            match = re.search(r"Message:\s(.+)$", line)
            if match:
                error_message = match.group(1).strip()
                continue

        # If we hit this condition, that means that we weren't able to parse the error so we'll
        # just throw the full response
        if error_type is None or error_message is None:
            raise RemoteServiceError(text)

        # A useful special is if a column name is unrecognized. This has the format
        #   Error type: SystemError
        #   Message: ... "NAME_OF_COLUMN": invalid identifier ...
        if error_type.startswith("SystemError"):
            match = re.search(r'"(.*)": invalid identifier', error_message)
            if match:
                raise InvalidQueryError(
                    (
                        "'{0}' is an invalid identifier. This error can be caused by invalid "
                        "column names, missing quotes, or other syntax errors"
                    ).format(match.group(1).lower())
                )

        elif error_type.startswith("UserError"):
            # Another important one is when the table is not recognized. This has the format:
            #   Error type: UserError - "table" parameter
            #   Message: ... "NAME_OF_TABLE" is not a valid table.
            match = re.search(r'"(.*)" is not a valid table', error_message)
            if match:
                raise InvalidTableError("'{0}' is not a valid table".format(match.group(1).lower()))

            raise InvalidQueryError("{0}\n{1}".format(error_type, error_message))

        # Finally just return the full error message if we got here
        message = "\n".join(line for line in (error_type, error_message) if line is not None)
        raise RemoteServiceError(message)
示例#2
0
    def query_region_async(self,
                           coordinates=None,
                           *,
                           catalog=None,
                           spatial='Cone',
                           radius=10 * u.arcsec,
                           width=None,
                           polygon=None,
                           get_query_payload=False,
                           selcols=None,
                           verbose=False,
                           cache=True):
        """
        This function serves the same purpose as
        :meth:`~astroquery.irsa.IrsaClass.query_region`, but returns the raw
        HTTP response rather than the results in a `~astropy.table.Table`.

        Parameters
        ----------
        coordinates : str, `astropy.coordinates` object
            Gives the position of the center of the cone or box if
            performing a cone or box search. The string can give coordinates
            in various coordinate systems, or the name of a source that will
            be resolved on the server (see `here
            <https://irsa.ipac.caltech.edu/search_help.html>`_ for more
            details). Required if spatial is ``'Cone'`` or ``'Box'``. Optional
            if spatial is ``'Polygon'``.
        catalog : str
            The catalog to be used. To list the available catalogs, use
            :meth:`~astroquery.irsa.IrsaClass.print_catalogs`.
        spatial : str
            Type of spatial query: ``'Cone'``, ``'Box'``, ``'Polygon'``, and
            ``'All-Sky'``. If missing then defaults to ``'Cone'``.
        radius : str or `~astropy.units.Quantity` object, [optional for spatial is ``'Cone'``]
            The string must be parsable by `~astropy.coordinates.Angle`. The
            appropriate `~astropy.units.Quantity` object from
            `astropy.units` may also be used. Defaults to 10 arcsec.
        width : str, `~astropy.units.Quantity` object [Required for spatial is ``'Polygon'``.]
            The string must be parsable by `~astropy.coordinates.Angle`. The
            appropriate `~astropy.units.Quantity` object from `astropy.units`
            may also be used.
        polygon : list, [Required for spatial is ``'Polygon'``]
            A list of ``(ra, dec)`` pairs (as tuples), in decimal degrees,
            outlining the polygon to search in. It can also be a list of
            `astropy.coordinates` object or strings that can be parsed by
            `astropy.coordinates.ICRS`.
        get_query_payload : bool, optional
            If `True` then returns the dictionary sent as the HTTP request.
            Defaults to `False`.
        selcols : str, optional
            Target column list with value separated by a comma(,)
        verbose : bool, optional.
            If `True` then displays warnings when the returned VOTable does not
            conform to the standard. Defaults to `False`.
        cache : bool, optional
            Use local cache when set to `True`.

        Returns
        -------
        response : `requests.Response`
            The HTTP response returned from the service
        """
        if catalog is None:
            raise InvalidQueryError("Catalog name is required!")

        request_payload = self._args_to_payload(catalog, selcols=selcols)
        request_payload.update(
            self._parse_spatial(spatial=spatial,
                                coordinates=coordinates,
                                radius=radius,
                                width=width,
                                polygon=polygon))

        if get_query_payload:
            return request_payload
        response = self._request("GET",
                                 url=Irsa.IRSA_URL,
                                 params=request_payload,
                                 timeout=Irsa.TIMEOUT,
                                 cache=cache)
        return response
示例#3
0
    def query_object_async(self, object_name, *, table="ps", get_query_payload=False,
                           cache=None, regularize=True, **criteria):
        """
        Search the tables of confirmed exoplanets for information about a planet or planet host

        The tables available to this query are the following (more information can be found on
        the archive's documentation pages [1]_):

        - ``ps``: This table compiles parameters derived from a multiple published
          references on separate rows, each row containing self-consistent values from one reference.
        - ``pscomppars``: This table compiles all parameters of confirmed exoplanets from multiple,
          published references in one row (not all self-consistent) per object.

        Parameters
        ----------
        object_name : str
            The name of the planet or star.  If ``regularize`` is ``True``, an attempt will be made
            to regularize this name using the ``aliastable`` table. Defaults to ``True``.
        table : [``"ps"`` or ``"pscomppars"``], optional
            The table to query, must be one of the supported tables: ``"ps"`` or ``"pscomppars"``.
            Defaults to ``"ps"``.
        get_query_payload : bool, optional
            Just return the dict of HTTP request parameters. Defaults to ``False``.
        cache : bool, optional
            Should the request result be cached? This can be useful for large repeated queries,
            but since the data in the archive is updated regularly, this defaults to ``False``.
        regularize : bool, optional
            If ``True``, the ``aliastable`` will be used to regularize the target name.
        **criteria
            Any other filtering criteria to apply. Values provided using the ``where`` keyword will
            be ignored.

        Returns
        -------
        response : `requests.Response`
            The HTTP response returned from the service.

        References
        ----------

        .. [1] `NASA Exoplanet Archive TAP Documentation
           <https://exoplanetarchive.ipac.caltech.edu/docs/TAP/usingTAP.html>`_
        .. [2] `NASA Exoplanet Archive API Documentation
           <https://exoplanetarchive.ipac.caltech.edu/docs/program_interfaces.html>`_
        """

        prefix = OBJECT_TABLES.get(table, None)
        if prefix is None:
            raise InvalidQueryError(f"Invalid table '{table}'. The allowed options are: 'ps' and 'pscomppars'")

        if regularize:
            object_name = self._regularize_object_name(object_name)

        if "where" in criteria:
            warnings.warn("Any filters using the 'where' argument are ignored "
                          "in ``query_object``. Consider using ``query_criteria`` instead.", InputWarning)
        if table in self.TAP_TABLES:
            criteria["where"] = "hostname='{1}' OR {0}name='{1}'".format(prefix, object_name.strip())
        else:
            criteria["where"] = "{0}hostname='{1}' OR {0}name='{1}'".format(prefix, object_name.strip())

        return self.query_criteria_async(table, get_query_payload=get_query_payload, cache=cache, **criteria)
示例#4
0
    def query_criteria_async(self, table, get_query_payload=False, cache=None, **criteria):
        """
        Search a table given a set of criteria or return the full table

        The syntax for these queries is described on the Exoplanet Archive TAP[1]_ API[2]_ documentation pages.
        In particular, the most commonly used criteria will be ``select`` and ``where``.

        Parameters
        ----------
        table : str
            The name of the table to query. A list of the tables on the Exoplanet Archive can be
            found on the documentation pages [1]_, [2]_.
        get_query_payload : bool, optional
            Just return the dict of HTTP request parameters. Defaults to ``False``.
        cache : bool, optional
            Should the request result be cached? This can be useful for large repeated queries,
            but since the data in the archive is updated regularly, this defaults to ``False``.
        **criteria
            The filtering criteria to apply. These are described in detail in the archive
            documentation [1]_, [2]_ but some examples include ``select="*"`` to return all columns of
            the queried table or ``where=pl_name='K2-18 b'`` to filter a specific column.

        Returns
        -------
        response : `requests.Response`
            The HTTP response returned from the service.

        References
        ----------

        .. [1] `NASA Exoplanet Archive TAP Documentation
           <https://exoplanetarchive.ipac.caltech.edu/docs/TAP/usingTAP.html>`_
        .. [2] `NASA Exoplanet Archive API Documentation
           <https://exoplanetarchive.ipac.caltech.edu/docs/program_interfaces.html>`_
        """
        # Make sure table is lower-case
        table = table.lower()

        # Warn if old table is requested
        if table in MAP_TABLEWARNINGS.keys():
            raise InvalidTableError(
                "The `{0}` table is no longer updated and has been replacedby the `{1}` table, which"
                " is connected to the Exoplanet Archive TAP service. Although the argument keywords "
                "of the called method should still work on the new table, the allowed values could "
                "have changed since the database column names have changed; this document contains "
                "the current definitions and a mapping between the new and deprecated names: "
                "https://exoplanetarchive.ipac.caltech.edu/docs/API_PS_columns.html. You might also "
                "want to review the TAP User Guide for help on creating a new query for the most "
                "current data: https://exoplanetarchive.ipac.caltech.edu/docs/TAP/usingTAP.html."
                .format(table, MAP_TABLEWARNINGS[table]))

        # Deal with lists of columns instead of comma separated strings
        criteria = copy.copy(criteria)
        if "select" in criteria:
            select = criteria["select"]
            if not isinstance(select, str):
                select = ",".join(select)
            criteria["select"] = select

        # We prefer to work with IPAC format so that we get units, but everything it should work
        # with the other options too
        # Get the format, or set it to "ipac" if not given. Makes more sense to use CSV here.
        criteria["format"] = criteria.get("format", "ipac")
        # Less formats are allowed for TAP, so this needs to be updated. Default
        # is VOTable (vot?, xml?), also csv and tsv are allowed
        if "json" in criteria["format"].lower():
            raise InvalidQueryError("The 'json' format is not supported")

        # Build the query (and return it if requested)
        request_payload = dict(table=table, **criteria)
        if get_query_payload:
            return request_payload

        # Use the default cache setting if one was not provided
        if cache is None:
            cache = self.CACHE

        if table in self.TAP_TABLES:
            tap = pyvo.dal.tap.TAPService(baseurl=self.URL_TAP)
            # construct query from table and request_payload (including format)
            tap_query = self._request_to_sql(request_payload)
            try:
                response = tap.search(query=tap_query, language='ADQL')  # Note that this returns a VOTable
            except Exception as err:
                raise InvalidQueryError(str(err))
        else:
            response = self._request(
                "GET", self.URL_API, params=request_payload, timeout=self.TIMEOUT, cache=cache,
            )
            response.requested_format = criteria["format"]

        return response
示例#5
0
文件: core.py 项目: nmearl/astroquery
    def query_lines_async(self,
                          min_frequency,
                          max_frequency,
                          min_strength=-500,
                          max_lines=2000,
                          molecule='All',
                          flags=0,
                          parse_name_locally=False,
                          get_query_payload=False,
                          cache=True):
        """
        Creates an HTTP POST request based on the desired parameters and
        returns a response.

        Parameters
        ----------
        min_frequency : `astropy.units`
            Minimum frequency (or any spectral() equivalent)
        max_frequency : `astropy.units`
            Maximum frequency (or any spectral() equivalent)
        min_strength : int, optional
            Minimum strength in catalog units, the default is -500
        max_lines :  int, optional
            Maximum number of lines to query, the default is 2000.
            The most the query allows is 100000

        molecule : list, string of regex if parse_name_locally=True, optional
            Identifiers of the molecules to search for. If this parameter
            is not provided the search will match any species. Default is 'All'.

        flags : int, optional
            Regular expression flags. Default is set to 0

        parse_name_locally : bool, optional
            When set to True it allows the method to parse through catdir.cat
            in order to match the regex inputted in the molecule parameter
            and request the corresponding tags of the matches instead. Default
            is set to False

        get_query_payload : bool, optional
            When set to `True` the method should return the HTTP request
            parameters as a dict. Default value is set to False

        Returns
        -------
        response : `requests.Response`
            The HTTP response returned from the service.

        Examples
        --------
        >>> table = JPLSpec.query_lines(min_frequency=100*u.GHz,
        ...                             max_frequency=200*u.GHz,
        ...                             min_strength=-500, molecule=18003) # doctest: +REMOTE_DATA
        >>> print(table) # doctest: +SKIP
            FREQ     ERR    LGINT    DR    ELO    GUP  TAG   QNFMT   QN'      QN"
        ----------- ------ -------- --- --------- --- ------ ----- -------- --------
        115542.5692 0.6588 -13.2595   3 4606.1683  35  18003  1404 17 810 0 18 513 0
         139614.293   0.15  -9.3636   3 3080.1788  87 -18003  1404 14 6 9 0 15 312 0
         177317.068   0.15 -10.3413   3 3437.2774  31 -18003  1404 15 610 0 16 313 0
         183310.087  0.001  -3.6463   3  136.1639   7 -18003  1404  3 1 3 0  2 2 0 0
        """
        # first initialize the dictionary of HTTP request parameters
        payload = dict()

        if min_frequency is not None and max_frequency is not None:
            # allow setting payload without having *ANY* valid frequencies set
            min_frequency = min_frequency.to(u.GHz, u.spectral())
            max_frequency = max_frequency.to(u.GHz, u.spectral())
            if min_frequency > max_frequency:
                min_frequency, max_frequency = max_frequency, min_frequency

            payload['MinNu'] = min_frequency.value
            payload['MaxNu'] = max_frequency.value

        if max_lines is not None:
            payload['MaxLines'] = max_lines

        payload['UnitNu'] = 'GHz'
        payload['StrLim'] = min_strength

        if molecule is not None:
            if parse_name_locally:
                self.lookup_ids = build_lookup()
                payload['Mol'] = tuple(
                    self.lookup_ids.find(molecule, flags).values())
                if len(molecule) == 0:
                    raise InvalidQueryError(
                        'No matching species found. Please '
                        'refine your search or read the Docs '
                        'for pointers on how to search.')
            else:
                payload['Mol'] = molecule

        self.maxlines = max_lines

        payload = list(payload.items())

        if get_query_payload:
            return payload
        # BaseQuery classes come with a _request method that includes a
        # built-in caching system
        response = self._request(method='POST',
                                 url=self.URL,
                                 data=payload,
                                 timeout=self.TIMEOUT,
                                 cache=cache)

        return response
示例#6
0
文件: core.py 项目: nmearl/astroquery
    def query_lines_async(self,
                          min_frequency,
                          max_frequency,
                          *,
                          min_strength=-500,
                          molecule='All',
                          temperature_for_intensity=300,
                          flags=0,
                          parse_name_locally=False,
                          get_query_payload=False,
                          cache=True):
        """
        Creates an HTTP POST request based on the desired parameters and
        returns a response.

        Parameters
        ----------
        min_frequency : `astropy.units.Quantity` or None
            Minimum frequency (or any spectral() equivalent).
            ``None`` can be interpreted as zero.
        max_frequency : `astropy.units.Quantity` or None
            Maximum frequency (or any spectral() equivalent).
            ``None`` can be interpreted as infinite.

        min_strength : int, optional
            Minimum strength in catalog units, the default is -500

        molecule : list, string of regex if parse_name_locally=True, optional
            Identifiers of the molecules to search for. If this parameter
            is not provided the search will match any species. Default is 'All'.

        temperature_for_intensity : float
            The temperature to use when computing the intensity Smu^2.  Set
            to 300 by default for compatibility with JPL and the native
            catalog format, which defaults to 300.
            ** If temperature is set to zero, the return value in this column
            will be the Einstein A value **

        flags : int, optional
            Regular expression flags. Default is set to 0

        parse_name_locally : bool, optional
            When set to True it allows the method to parse through catdir.cat
            (see `get_species_table`) in order to match the regex inputted in
            the molecule parameter and request the corresponding tags of the
            matches instead. Default is set to False

        get_query_payload : bool, optional
            When set to `True` the method should return the HTTP request
            parameters as a dict. Default value is set to False

        cache : bool
            Cache the request and, for repeat identical requests, reuse the
            cache?

        Returns
        -------
        response : `requests.Response`
            The HTTP response returned from the service.

        Examples
        --------
        >>> table = CDMS.query_lines(min_frequency=100*u.GHz,
        ...                          max_frequency=110*u.GHz,
        ...                          min_strength=-500,
        ...                          molecule="018505 H2O+") # doctest: +REMOTE_DATA
        >>> print(table) # doctest: +SKIP
            FREQ     ERR   LGINT   DR   ELO    GUP  TAG  QNFMT  Ju  Ku  vu  Jl  Kl  vl      F      name
            MHz      MHz  MHz nm2      1 / cm
        ----------- ----- ------- --- -------- --- ----- ----- --- --- --- --- --- --- ----------- ----
        103614.4941 2.237 -4.1826   3 202.8941   8 18505  2356   4   1   4   4   0   4 3 2 1 3 0 3 H2O+
        107814.8763 148.6 -5.4438   3 878.1191  12 18505  2356   6   5   1   7   1   6 7 4 4 8 1 7 H2O+
        107822.3481 148.6 -5.3846   3 878.1178  14 18505  2356   6   5   1   7   1   7 7 4 4 8 1 8 H2O+
        107830.1216 148.6 -5.3256   3 878.1164  16 18505  2356   6   5   1   7   1   8 7 4 4 8 1 9 H2O+
        """
        # first initialize the dictionary of HTTP request parameters
        payload = dict()

        if min_frequency is not None and max_frequency is not None:
            # allow setting payload without having *ANY* valid frequencies set
            min_frequency = min_frequency.to(u.GHz, u.spectral())
            max_frequency = max_frequency.to(u.GHz, u.spectral())
            if min_frequency > max_frequency:
                raise InvalidQueryError(
                    "min_frequency must be less than max_frequency")

            payload['MinNu'] = min_frequency.value
            payload['MaxNu'] = max_frequency.value

        payload['UnitNu'] = 'GHz'
        payload['StrLim'] = min_strength
        payload['temp'] = temperature_for_intensity
        payload['logscale'] = 'yes'
        payload['mol_sort_query'] = 'tag'
        payload['sort'] = 'frequency'
        payload['output'] = 'text'
        payload['but_action'] = 'Submit'

        # changes interpretation of query
        self._last_query_temperature = temperature_for_intensity

        if molecule is not None:
            if parse_name_locally:
                self.lookup_ids = build_lookup()
                luts = self.lookup_ids.find(molecule, flags)
                payload['Molecules'] = tuple(f"{val:06d} {key}"
                                             for key, val in luts.items())[0]
                if len(molecule) == 0:
                    raise InvalidQueryError(
                        'No matching species found. Please '
                        'refine your search or read the Docs '
                        'for pointers on how to search.')
            else:
                payload['Molecules'] = molecule

        payload = list(payload.items())

        if get_query_payload:
            return payload
        # BaseQuery classes come with a _request method that includes a
        # built-in caching system
        response = self._request(method='POST',
                                 url=self.URL,
                                 data=payload,
                                 timeout=self.TIMEOUT,
                                 cache=cache)
        response.raise_for_status()

        soup = BeautifulSoup(response.text, 'html.parser')

        ok = False
        urls = [x.attrs['src'] for x in soup.findAll('frame', )]
        for url in urls:
            if 'tab' in url and 'head' not in url:
                ok = True
                break
        if not ok:
            raise EmptyResponseError("Did not find table in response")

        baseurl = self.URL.split('cgi-bin')[0]
        fullurl = f'{baseurl}/{url}'

        response2 = self._request(method='GET',
                                  url=fullurl,
                                  timeout=self.TIMEOUT,
                                  cache=cache)

        return response2
示例#7
0
文件: core.py 项目: nmearl/astroquery
    def query_region_async(self,
                           coordinate=None,
                           where=None,
                           mission=None,
                           dataset=None,
                           table=None,
                           columns=None,
                           width=None,
                           height=None,
                           action='search',
                           intersect='OVERLAPS',
                           most_centered=False):
        """
        For certain missions, this function can be used to search for image and
        catalog files based on a point, a box (bounded by great circles) and/or
        an SQL-like ``where`` clause.

        If ``coordinates`` is specified, then the optional ``width`` and
        ``height`` arguments control the width and height of the search
        box. If neither ``width`` nor ``height`` are provided, then the
        search area is a point. If only one of ``width`` or ``height`` are
        specified, then the search area is a square with that side length
        centered at the coordinate.

        Parameters
        ----------
        coordinate : str, `astropy.coordinates` object
            Gives the position of the center of the box if performing a box
            search. If it is a string, then it must be a valid argument to
            `~astropy.coordinates.SkyCoord`. Required if ``where`` is absent.
        where : str
            SQL-like query string. Required if ``coordinates`` is absent.
        mission : str
            The mission to be used (if not the default mission).
        dataset : str
            The dataset to be used (if not the default dataset).
        table : str
            The table to be queried (if not the default table).
        columns : str, list
            A space-separated string or a list of strings of the names of the
            columns to return.
        width : str or `~astropy.units.Quantity` object
            Width of the search box if ``coordinates`` is present.

            The string must be parsable by `~astropy.coordinates.Angle`. The
            appropriate `~astropy.units.Quantity` object from `astropy.units`
            may also be used.
        height : str, `~astropy.units.Quantity` object
            Height of the search box if ``coordinates`` is present.

            The string must be parsable by `~astropy.coordinates.Angle`. The
            appropriate `~astropy.units.Quantity` object from `astropy.units`
            may also be used.
        intersect : ``'COVERS'``, ``'ENCLOSED'``, ``'CENTER'``, ``'OVERLAPS'``
            Spatial relationship between search box and image footprint.

            ``'COVERS'``: X must completely contain S. Equivalent to
            ``'CENTER'`` and ``'OVERLAPS'`` if S is a point.

            ``'ENCLOSED'``: S must completely contain X. If S is a point, the
            query will always return an empty image table.

            ``'CENTER'``: X must contain the center of S. If S is a point, this
            is equivalent to ``'COVERS'`` and ``'OVERLAPS'``.

            ``'OVERLAPS'``: The intersection of S and X is non-empty. If S is a
            point, this is equivalent to ``'CENTER'`` and ``'COVERS'``.
        most_centered : bool
            If True, then only the most centered image is returned.
        action : ``'search'``, ``'data'``, or ``'sia'``
            The action to perform at the server.  The default is ``'search'``,
            which returns a table of the available data.  ``'data'`` requires
            advanced path construction that is not yet supported. ``'sia'``
            provides access to the 'simple image access' IVOA protocol

        Returns
        -------
        response : `~requests.Response`
            The HTTP response returned from the service
        """

        if coordinate is None and where is None:
            raise InvalidQueryError(
                'At least one of `coordinate` or `where` is required')

        intersect = intersect.upper()
        if intersect not in ('COVERS', 'ENCLOSED', 'CENTER', 'OVERLAPS'):
            raise InvalidQueryError(
                "Invalid value for `intersects` " +
                "(must be 'COVERS', 'ENCLOSED', 'CENTER', or 'OVERLAPS')")

        if action not in ('sia', 'data', 'search'):
            raise InvalidQueryError("Valid actions are: sia, data, search.")
        if action == 'data':
            raise NotImplementedError(
                "The action='data' option is a placeholder for future " +
                "functionality.")

        args = {'INTERSECT': intersect}

        # Note: in IBE, if 'mcen' argument is present, it is true.
        # If absent, it is false.
        if most_centered:
            args['mcen'] = '1'

        if coordinate is not None:
            c = commons.parse_coordinates(coordinate).transform_to(coord.ICRS)
            args['POS'] = '{0},{1}'.format(c.ra.deg, c.dec.deg)
            if width and height:
                args['SIZE'] = '{0},{1}'.format(
                    coord.Angle(width).value,
                    coord.Angle(height).value)
            elif width or height:
                args['SIZE'] = str(coord.Angle(width or height).value)

        if where:
            args['where'] = where

        if columns:
            if isinstance(columns, str):
                columns = columns.split()
            args['columns'] = ','.join(columns)

        url = "{URL}{action}/{mission}/{dataset}/{table}".format(
            URL=self.URL,
            action=action,
            mission=mission or self.MISSION,
            dataset=dataset or self.DATASET,
            table=table or self.TABLE)

        return self._request('GET', url, args, timeout=self.TIMEOUT)