Esempio n. 1
0
    def request_station_stats(
        self,
        start_ts_s: int,
        end_ts_s: int,
        station_ids: List[str],
    ) -> Optional[station_stats_api.StationStatsResp]:
        """
        Request signed URLs for RedVox packets.
        :param start_ts_s: The start epoch of the window.
        :param end_ts_s:  The end epoch of the window.
        :param station_ids: A list of station ids.
        :return: A response containing a list of signed URLs for the RedVox packets.
        """
        if end_ts_s <= start_ts_s:
            raise cloud_errors.CloudApiError("start_ts_s must be < end_ts_s")

        if len(station_ids) == 0:
            raise cloud_errors.CloudApiError(
                "At least one station_id must be provided")

        station_stats_req: station_stats_api.StationStatReq = (
            station_stats_api.StationStatReq(
                self.auth_token,
                start_ts_s,
                end_ts_s,
                station_ids,
                self.redvox_config.secret_token,
            ))

        return station_stats_api.request_station_stats(self.redvox_config,
                                                       station_stats_req,
                                                       self.__session,
                                                       self.timeout)
Esempio n. 2
0
def chunk_time_range(start_ts: int, end_ts: int,
                     max_chunk: int) -> List[Tuple[int, int]]:
    """
    Chunks the given request window into smaller windows.
    :param start_ts: Start of the request window.
    :param end_ts: End of the request window.
    :param max_chunk: Max chunk size.
    :return: A list of window chunks.
    """

    if end_ts <= start_ts:
        raise cloud_errors.CloudApiError("start_ts must be < end_ts")

    if max_chunk <= 0:
        raise cloud_errors.CloudApiError("max_chunk must be > 0")

    chunks: List[Tuple[int, int]] = []

    start: int = start_ts

    while start + max_chunk < end_ts:
        chunks.append((start, start + max_chunk))
        start += max_chunk

    if start < end_ts:
        chunks.append((start, end_ts))

    return chunks
Esempio n. 3
0
    def request_metadata(
        self,
        start_ts_s: int,
        end_ts_s: int,
        station_ids: List[str],
        metadata_to_include: List[str],
        chunk_by_seconds: int = constants.SECONDS_PER_DAY,
    ) -> Optional[metadata_api.MetadataResp]:
        """
        Requests RedVox packet metadata.
        :param start_ts_s: Start epoch of request window.
        :param end_ts_s: End epoch of request window.
        :param station_ids: A list of station ids.
        :param metadata_to_include: A list of metadata fields to include (see: redvox.cloud.metadata.AvailableMetadata)
        :param chunk_by_seconds: Split up longer requests into chunks of chunk_by_seconds size (default 86400s/1d)
        :return: A metadata result containing the requested metadata or None on error.
        """
        if end_ts_s <= start_ts_s:
            raise cloud_errors.CloudApiError("start_ts_s must be < end_ts_s")

        if len(station_ids) == 0:
            raise cloud_errors.CloudApiError(
                "At least one station_id must be included")

        if len(metadata_to_include) == 0:
            raise cloud_errors.CloudApiError(
                "At least one metadata field must be included")

        if chunk_by_seconds <= 0:
            raise cloud_errors.CloudApiError("chunk_by_seconds must be > 0")

        time_chunks: List[Tuple[int, int]] = chunk_time_range(
            start_ts_s, end_ts_s, chunk_by_seconds)
        metadata_resp: metadata_api.MetadataResp = metadata_api.MetadataResp(
            [])

        for start_ts, end_ts in time_chunks:
            metadata_req: metadata_api.MetadataReq = metadata_api.MetadataReq(
                self.auth_token,
                start_ts,
                end_ts,
                station_ids,
                metadata_to_include,
                self.redvox_config.secret_token,
            )

            chunked_resp: Optional[
                metadata_api.MetadataResp] = metadata_api.request_metadata(
                    self.redvox_config,
                    metadata_req,
                    session=self.__session,
                    timeout=self.timeout,
                )

            if chunked_resp:
                metadata_resp.metadata.extend(chunked_resp.metadata)

        return metadata_resp
Esempio n. 4
0
    def request_timing_metadata(
        self,
        start_ts_s: int,
        end_ts_s: int,
        station_ids: List[str],
        chunk_by_seconds: int = constants.SECONDS_PER_DAY,
    ) -> metadata_api.TimingMetaResponse:
        """
        Requests timing metadata from RedVox packets.
        :param start_ts_s: Start epoch of the request.
        :param end_ts_s: End epoch of the request.
        :param station_ids: A list of station ids.
        :param chunk_by_seconds: Split up longer requests into chunks of chunk_by_seconds size (default 86400s/1d)
        :return: A response containing the requested metadata.
        """
        if end_ts_s <= start_ts_s:
            raise cloud_errors.CloudApiError("start_ts_s must be < end_ts_s")

        if len(station_ids) == 0:
            raise cloud_errors.CloudApiError(
                "At least one station_id must be included")

        if chunk_by_seconds <= 0:
            raise cloud_errors.CloudApiError("chunk_by_seconds must be > 0")

        time_chunks: List[Tuple[int, int]] = chunk_time_range(
            start_ts_s, end_ts_s, chunk_by_seconds)
        metadata_resp: metadata_api.TimingMetaResponse = (
            metadata_api.TimingMetaResponse([]))

        for start_ts, end_ts in time_chunks:
            timing_req: metadata_api.TimingMetaRequest = metadata_api.TimingMetaRequest(
                self.auth_token,
                start_ts,
                end_ts,
                station_ids,
                self.redvox_config.secret_token,
            )
            chunked_resp: metadata_api.TimingMetaResponse = (
                metadata_api.request_timing_metadata(
                    self.redvox_config,
                    timing_req,
                    session=self.__session,
                    timeout=self.timeout,
                ))

            if chunked_resp:
                metadata_resp.items.extend(chunked_resp.items)

        return metadata_resp
Esempio n. 5
0
def cloud_client(
    redvox_config: Optional[RedVoxConfig] = RedVoxConfig.find(),
    refresh_token_interval: float = 600.0,
    timeout: float = 10.0,
):
    """
    Function that can be used within a "with" block to automatically handle the closing of open resources.
    Creates and returns a CloudClient that will automatically be closed when exiting the with block or if an error
    occurs.

    See https://docs.python.org/3/library/contextlib.html for more info.

    :param redvox_config: The Redvox endpoint configuration.
    :param refresh_token_interval: An optional token refresh interval
    :param timeout: An optional timeout.
    :return: A CloudClient.
    """
    if redvox_config is None:
        raise cloud_errors.CloudApiError(
            "A RedVoxConfig was not found in the environment and one wasn't provided"
        )

    client: CloudClient = CloudClient(redvox_config, refresh_token_interval,
                                      timeout)
    try:
        yield client
    finally:
        if client is not None:
            client.close()
Esempio n. 6
0
    def request_station_statuses(
        self,
        start_ts_s: int,
        end_ts_s: int,
        station_ids: List[str],
    ) -> Optional[metadata_api.StationStatusResp]:
        """
        Requests station timing information from the cloud services.
        :param start_ts_s: The start of the request data window.
        :param end_ts_s: The end of the request data window.
        :param station_ids: A list of station IDs.
        :return: A StationStatsResp.
        """
        if end_ts_s <= start_ts_s:
            raise cloud_errors.CloudApiError("start_ts_s must be < end_ts_s")

        station_status_req: metadata_api.StationStatusReq = (
            metadata_api.StationStatusReq(
                self.redvox_config.secret_token,
                self.auth_token,
                start_ts_s,
                end_ts_s,
                station_ids,
            ))

        return metadata_api.request_station_statuses(
            self.redvox_config,
            station_status_req,
            session=self.__session,
            timeout=self.timeout,
        )
Esempio n. 7
0
    def authenticate_user(self, username: str,
                          password: str) -> auth_api.AuthResp:
        """
        Attempts to authenticate the given RedVox user.
        :param username: The RedVox username.
        :param password: The RedVox password.
        :return: An authenticate response.
        """
        if len(username) == 0:
            raise cloud_errors.CloudApiError("Username must be provided")

        if len(password) == 0:
            raise cloud_errors.CloudApiError("Password must be provided")

        auth_req: auth_api.AuthReq = auth_api.AuthReq(username, password)
        return auth_api.authenticate_user(self.redvox_config,
                                          auth_req,
                                          session=self.__session,
                                          timeout=self.timeout)
Esempio n. 8
0
    def __init__(
        self,
        redvox_config: Optional[RedVoxConfig] = RedVoxConfig.find(),
        refresh_token_interval: float = 600.0,
        timeout: Optional[float] = 10.0,
    ):
        """
        Instantiates this client.
        :param redvox_config: The Redvox endpoint configuration.
        :param refresh_token_interval: An optional interval in seconds that the auth token should be refreshed.
        :param timeout: An optional timeout
        """

        if redvox_config is None:
            raise cloud_errors.CloudApiError(
                "A RedVoxConfig was not found in the environment and one wasn't provided"
            )

        if refresh_token_interval <= 0:
            raise cloud_errors.CloudApiError(
                "refresh_token_interval must be strictly > 0")

        if timeout is not None and (timeout <= 0):
            raise cloud_errors.CloudApiError("timeout must be strictly > 0")

        self.redvox_config: RedVoxConfig = redvox_config
        self.refresh_token_interval: float = refresh_token_interval
        self.timeout: Optional[float] = timeout

        self.__session = (requests.Session()
                          )  # This must be initialized before the auth req!

        self.__refresh_timer = None
        self.__authenticate()
        self.__refresh_timer = threading.Timer(self.refresh_token_interval,
                                               self.__refresh_token)
        self.__refresh_timer.start()
Esempio n. 9
0
    def validate_auth_token(
            self, auth_token: str) -> Optional[auth_api.ValidateTokenResp]:
        """
        Validates the provided authentication token with the cloud API.
        :param auth_token: Authentication token to validate.
        :return: An authentication response with token details or None if token in invalid
        """
        if len(auth_token) == 0:
            raise cloud_errors.CloudApiError("auth_token must be provided")

        token_req: auth_api.ValidateTokenReq = auth_api.ValidateTokenReq(
            auth_token)
        return auth_api.validate_token(self.redvox_config,
                                       token_req,
                                       session=self.__session,
                                       timeout=self.timeout)
Esempio n. 10
0
    def request_report_data(
            self, report_id: str) -> Optional[data_api.ReportDataResp]:
        """
        Requests a signed URL for a given report ID.
        :param report_id: The report ID to request data for.
        :return: A response containing a signed URL of the report data.
        """
        if len(report_id) == 0:
            raise cloud_errors.CloudApiError("report_id must be included")

        report_data_req: data_api.ReportDataReq = data_api.ReportDataReq(
            self.auth_token, report_id, self.redvox_config.secret_token)
        return data_api.request_report_data(
            self.redvox_config,
            report_data_req,
            session=self.__session,
            timeout=self.timeout,
        )
Esempio n. 11
0
    def refresh_auth_token(
            self, auth_token: str) -> Optional[auth_api.RefreshTokenResp]:
        """
        Retrieves a new authentication token from a given valid authentication token.
        :param auth_token: The authentication token to verify.
        :return: A new authentication token or None if the provide auth token is not valid.
        """
        if len(auth_token) == 0:
            raise cloud_errors.CloudApiError("auth_token must be provided")

        refresh_token_req: auth_api.RefreshTokenReq = auth_api.RefreshTokenReq(
            auth_token)
        return auth_api.refresh_token(
            self.redvox_config,
            refresh_token_req,
            session=self.__session,
            timeout=self.timeout,
        )
Esempio n. 12
0
    def request_data_range(
            self,
            start_ts_s: int,
            end_ts_s: int,
            station_ids: List[str],
            req_type: data_api.DataRangeReqType = data_api.DataRangeReqType.
        API_900_1000,
            correct_query_timing: bool = True,
            out_queue: Optional[Queue] = None) -> data_api.DataRangeResp:
        """
        Request signed URLs for RedVox packets.
        :param start_ts_s: The start epoch of the window.
        :param end_ts_s:  The end epoch of the window.
        :param station_ids: A list of station ids.
        :param req_type: The type of data to request.
        :param correct_query_timing: If set to true, timing correction will be applied to each station before the data is
                                    correct_timing queried.
        :return: A response containing a list of signed URLs for the RedVox packets.
        """
        def _make_req(_start_ts_s: int, _end_ts_s: int,
                      _station_ids: List[str]) -> data_api.DataRangeResp:
            """
            Makes the actual data request after timing correction was or was not applied.
            :param _start_ts_s: The start epoch of the window.
            :param _end_ts_s: The end epoch of the window.
            :param _station_ids: A list of station IDs.
            :return: A response containing a list of signed URLs for the RedVox packets.
            """
            data_range_req: data_api.DataRangeReq = data_api.DataRangeReq(
                self.auth_token,
                _start_ts_s,
                _end_ts_s,
                _station_ids,
                self.redvox_config.secret_token,
            )

            req_resp: data_api.DataRangeResp = data_api.request_range_data(
                self.redvox_config,
                data_range_req,
                session=self.__session,
                timeout=self.timeout,
                req_type=req_type,
            )

            return req_resp

        if end_ts_s <= start_ts_s:
            raise cloud_errors.CloudApiError("start_ts_s must be < end_ts_s")

        if len(station_ids) == 0:
            raise cloud_errors.CloudApiError(
                "At least one station_id must be provided")

        # If timing correction was requested, we want to find the corrected start and end offsets in order to
        # request the correct range of data for each station's particular clock offset
        if correct_query_timing:
            corrected_queries: Optional[
                List["CorrectedQuery"]] = do_correct_query_timing(
                    self, start_ts_s, end_ts_s, station_ids)

            # If we get nothing back, we can't apply a correction
            if corrected_queries is None or len(corrected_queries) == 0:
                print(
                    "No timing corrections returned, running original query.")
                return _make_req(start_ts_s, end_ts_s, station_ids)

            # Make a request for each corrected query, aggregating the results of each request
            resp: data_api.DataRangeResp = data_api.DataRangeResp([])
            corrected_query: "CorrectedQuery"
            for corrected_query in corrected_queries:
                correction_msg: str = f"Running timing corrected query for {corrected_query.station_id} " \
                                      f"start offset={corrected_query.start_offset()} " \
                                      f"end offset={corrected_query.end_offset()}"

                if out_queue is None:
                    print(correction_msg)
                else:
                    out_queue.put(correction_msg, block=False)

                resp.append(
                    _make_req(
                        round(corrected_query.corrected_start_ts),
                        round(corrected_query.corrected_end_ts),
                        [corrected_query.station_id],
                    ))
            return resp
        else:
            # No timing correction requested, go ahead just make the original uncorrected request
            return _make_req(start_ts_s, end_ts_s, station_ids)