From 5bbe3aa84f6705faf7f58e2abb06eefbc5864c35 Mon Sep 17 00:00:00 2001 From: bgeels <bgeels@usgs.gov> Date: Wed, 7 Aug 2024 10:28:35 -0600 Subject: [PATCH 1/5] Add metadatafactory docstrings --- geomagio/metadata/MetadataFactory.py | 76 ++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/geomagio/metadata/MetadataFactory.py b/geomagio/metadata/MetadataFactory.py index 92b44975..9da66684 100644 --- a/geomagio/metadata/MetadataFactory.py +++ b/geomagio/metadata/MetadataFactory.py @@ -9,7 +9,7 @@ from pydantic import parse_obj_as from .Metadata import Metadata from .MetadataQuery import MetadataQuery - +# Set the API host and URL for the Geomagnetic Metadata service GEOMAG_API_HOST = os.getenv("GEOMAG_API_HOST", "geomag.usgs.gov") GEOMAG_API_URL = f"https://{GEOMAG_API_HOST}/ws/secure/metadata" if "127.0.0.1" in GEOMAG_API_URL: @@ -17,15 +17,36 @@ if "127.0.0.1" in GEOMAG_API_URL: class MetadataFactory(object): + """ + A factory class for interacting with metadata servers. + + Attributes: + url (str): The base URL for the metadata server. + token (str): The authorization token for accessing the API. + """ + def __init__( self, url: str = GEOMAG_API_URL, token: str = os.getenv("GITLAB_API_TOKEN"), ): + """ + Initializes the MetadataFactory with the specified URL and token. + + Args: + url (str): The base URL for the metadata server (default is GEOMAG_API_URL). + token (str): The authorization token (default is fetched from environment). + """ self.url = url self.token = token def _get_headers(self): + """ + Constructs the headers for the API request. + + Returns: + dict: A dictionary containing the authorization token and content type. + """ return ( {"Authorization": self.token, "content-type": "application/json"} if self.token @@ -33,6 +54,15 @@ class MetadataFactory(object): ) def get_metadata(self, query: MetadataQuery) -> List[Metadata]: + """ + Retrieves metadata based on the provided query. + + Args: + query (MetadataQuery): The query object containing search parameters. + + Returns: + List[Metadata]: A list of Metadata objects matching the query. + """ if query.id: metadata = [self.get_metadata_by_id(id=query.id)] else: @@ -43,7 +73,7 @@ class MetadataFactory(object): ) metadata = parse_obj_as(List[Metadata], response.json()) - # if metadata dict() provided in query, only include Metadata objects with matching key-value pairs + # Filter metadata based on additional criteria if provided if query.metadata is not None: query_metadata_items = query.metadata.items() metadata_list = metadata @@ -60,6 +90,15 @@ class MetadataFactory(object): return metadata def get_metadata_by_id(self, id: int) -> Metadata: + """ + Retrieves metadata for a specific ID. + + Args: + id (int): The ID of the metadata to retrieve. + + Returns: + Metadata: The Metadata object corresponding to the given ID. + """ response = requests.get( url=f"{self.url}/{id}", headers=self._get_headers(), @@ -67,6 +106,15 @@ class MetadataFactory(object): return Metadata(**response.json()) def create_metadata(self, metadata: Metadata) -> Metadata: + """ + Creates new metadata objects in the database. + + Args: + metadata (Metadata): The Metadata object to create. + + Returns: + Metadata: The created Metadata object. + """ response = requests.post( url=self.url, data=metadata.json(), @@ -75,6 +123,15 @@ class MetadataFactory(object): return Metadata(**response.json()) def update_metadata(self, metadata: Metadata) -> Metadata: + """ + Updates existing metadata in the system. + + Args: + metadata (Metadata): The Metadata object to update. + + Returns: + Metadata: The updated Metadata object. + """ response = requests.put( url=f"{self.url}/{metadata.id}", data=metadata.json(), @@ -84,17 +141,26 @@ class MetadataFactory(object): def parse_params(query: MetadataQuery) -> str: + """ + Parses the query object into a dictionary of parameters for the API request. + + Args: + query (MetadataQuery): The query object containing search parameters. + + Returns: + dict: A dictionary of parameters formatted for the API request. + """ query = query.dict(exclude_none=True) args = {} for key in query.keys(): element = query[key] - # convert times to strings + # Convert times to ISO format strings if isinstance(element, UTCDateTime): element = element.isoformat() - # serialize the metadata dictionary to a JSON string + # Serialize the metadata dictionary to a JSON string elif key == "metadata" and isinstance(element, dict): args[key] = json.dumps(element) - # get string value of metadata category + # Get string value of metadata category if key == "category": element = element.value args[key] = element -- GitLab From 903f0da98283eb388b32903fc57c418e6e84fbfe Mon Sep 17 00:00:00 2001 From: bgeels <bgeels@usgs.gov> Date: Wed, 7 Aug 2024 10:29:02 -0600 Subject: [PATCH 2/5] Add timeouts to metadata factory --- geomagio/metadata/MetadataFactory.py | 106 ++++++++++++++++++++------- 1 file changed, 78 insertions(+), 28 deletions(-) diff --git a/geomagio/metadata/MetadataFactory.py b/geomagio/metadata/MetadataFactory.py index 9da66684..2f718fe3 100644 --- a/geomagio/metadata/MetadataFactory.py +++ b/geomagio/metadata/MetadataFactory.py @@ -21,8 +21,11 @@ class MetadataFactory(object): A factory class for interacting with metadata servers. Attributes: - url (str): The base URL for the metadata server. - token (str): The authorization token for accessing the API. + ---------- + url: str + The base URL for the metadata server. + token: str + The authorization token for accessing the API. """ def __init__( @@ -33,9 +36,12 @@ class MetadataFactory(object): """ Initializes the MetadataFactory with the specified URL and token. - Args: - url (str): The base URL for the metadata server (default is GEOMAG_API_URL). - token (str): The authorization token (default is fetched from environment). + Parameters: + ----------- + url: str + The base URL for the metadata server (default is GEOMAG_API_URL). + token: str + The authorization token (default is fetched from environment). """ self.url = url self.token = token @@ -45,7 +51,9 @@ class MetadataFactory(object): Constructs the headers for the API request. Returns: - dict: A dictionary containing the authorization token and content type. + -------- + dict: + A dictionary containing the authorization token and content type. """ return ( {"Authorization": self.token, "content-type": "application/json"} @@ -53,15 +61,25 @@ class MetadataFactory(object): else None ) - def get_metadata(self, query: MetadataQuery) -> List[Metadata]: + def get_metadata( + self, query: MetadataQuery, timeout: float | tuple = None + ) -> List[Metadata]: """ Retrieves metadata based on the provided query. - Args: - query (MetadataQuery): The query object containing search parameters. + Parameters: + ----------- + query: MetadataQuery + The query object containing search parameters. + timeout: float or tuple, optional + The timeout for the API request. + Can be a single number (for both connection and read timeouts) + or a tuple (connection timeout, read timeout). Defaults to None. Returns: - List[Metadata]: A list of Metadata objects matching the query. + -------- + metadata: List[Metadata] + A list of Metadata objects matching the query. """ if query.id: metadata = [self.get_metadata_by_id(id=query.id)] @@ -70,6 +88,7 @@ class MetadataFactory(object): url=self.url, headers=self._get_headers(), params=parse_params(query=query), + timeout=timeout, ) metadata = parse_obj_as(List[Metadata], response.json()) @@ -89,15 +108,23 @@ class MetadataFactory(object): return metadata - def get_metadata_by_id(self, id: int) -> Metadata: + def get_metadata_by_id(self, id: int, timeout: float | tuple = None) -> Metadata: """ Retrieves metadata for a specific ID. - Args: - id (int): The ID of the metadata to retrieve. + Parameters: + ----------- + id: int + The ID of the metadata to retrieve. + timeout: float or tuple, optional + The timeout for the API request. + Can be a single number (for both connection and read timeouts) + or a tuple (connection timeout, read timeout). Defaults to None. Returns: - Metadata: The Metadata object corresponding to the given ID. + -------- + Metadata: object + The Metadata object corresponding to the given ID. """ response = requests.get( url=f"{self.url}/{id}", @@ -105,14 +132,23 @@ class MetadataFactory(object): ) return Metadata(**response.json()) - def create_metadata(self, metadata: Metadata) -> Metadata: + def create_metadata( + self, metadata: Metadata, timeout: float | tuple = None + ) -> Metadata: """ Creates new metadata objects in the database. - Args: - metadata (Metadata): The Metadata object to create. + Parameters: + ----------- + metadata: Metadata + The Metadata object to create. + timeout: float or tuple, optional + The timeout for the API request. + Can be a floating-point number (for both connection and read timeouts) + or a tuple (connection timeout, read timeout). Defaults to None. Returns: + -------- Metadata: The created Metadata object. """ response = requests.post( @@ -122,15 +158,25 @@ class MetadataFactory(object): ) return Metadata(**response.json()) - def update_metadata(self, metadata: Metadata) -> Metadata: + def update_metadata( + self, metadata: Metadata, timeout: float | tuple = None + ) -> Metadata: """ Updates existing metadata in the system. - Args: - metadata (Metadata): The Metadata object to update. + Parameters: + ----------- + metadata: Metadata + The Metadata object to update. + timeout: float or tuple, optional + The timeout for the API request. + Can be a floating-point number (for both connection and read timeouts) + or a tuple (connection timeout, read timeout). Defaults to None. Returns: - Metadata: The updated Metadata object. + ----------- + Metadata: + The updated Metadata object. """ response = requests.put( url=f"{self.url}/{metadata.id}", @@ -144,14 +190,18 @@ def parse_params(query: MetadataQuery) -> str: """ Parses the query object into a dictionary of parameters for the API request. - Args: - query (MetadataQuery): The query object containing search parameters. + Parameters: + ----------- + query: MetadataQuery + The query object containing search parameters. Returns: - dict: A dictionary of parameters formatted for the API request. + -------- + dict: + A dictionary of parameters formatted for the API request. """ query = query.dict(exclude_none=True) - args = {} + Parameters = {} for key in query.keys(): element = query[key] # Convert times to ISO format strings @@ -159,9 +209,9 @@ def parse_params(query: MetadataQuery) -> str: element = element.isoformat() # Serialize the metadata dictionary to a JSON string elif key == "metadata" and isinstance(element, dict): - args[key] = json.dumps(element) + Parameters[key] = json.dumps(element) # Get string value of metadata category if key == "category": element = element.value - args[key] = element - return args + Parameters[key] = element + return Parameters -- GitLab From fe5946724a3c0ac7820d933088615499b51c00c6 Mon Sep 17 00:00:00 2001 From: bgeels <bgeels@usgs.gov> Date: Wed, 7 Aug 2024 10:36:28 -0600 Subject: [PATCH 3/5] Add timeout to InstrumentCalibrations & fix docstrings --- .../instrument/InstrumentCalibrations.py | 141 +++++++++--------- 1 file changed, 72 insertions(+), 69 deletions(-) diff --git a/geomagio/metadata/instrument/InstrumentCalibrations.py b/geomagio/metadata/instrument/InstrumentCalibrations.py index 1bdc33d7..8dfcef48 100644 --- a/geomagio/metadata/instrument/InstrumentCalibrations.py +++ b/geomagio/metadata/instrument/InstrumentCalibrations.py @@ -14,33 +14,33 @@ class InstrumentCalibrations: Attributes ---------- - metadata_list : list - a list of metadata objects to be processed - previous_calibration : dict - a dictionary to store the previous calibration values for each axis and key - station : str - the station code, checked for consistency across all metadata objects - location : str - the location code, checked for consistency across all metadata objects - network : str - the network code, checked for consistency across all metadata objects + metadata_list : list + a list of metadata objects to be processed + previous_calibration : dict + a dictionary to store the previous calibration values for each axis and key + station : str + the station code, checked for consistency across all metadata objects + location : str + the location code, checked for consistency across all metadata objects + network : str + the network code, checked for consistency across all metadata objects Methods ------- - get_calibrations(): - Main method to gather applicable calibrations from the metadata list. - create_overlap_interval(current_metadata): - Creates an overlap interval from the current metadata. - update_overlap_interval(overlap_interval, current_metadata): - Updates the overlap interval with the current metadata. - update_overlap_interval_with_same_starttime(i, sorted_metadata_list, overlap_interval): - Updates the overlap interval with metadata that have the same starttime. - set_endtime(i, sorted_metadata_list, overlap_interval, current_metadata): - Sets the endtime for the overlap interval. - convert_to_calibration(overlap_metadata): - Converts overlapping metadata information to an applicable calibration. - get_channels(overlap_metadata): - Gets the channels for the applicable calibration. + get_calibrations(): + Main method to gather applicable calibrations from the metadata list. + create_overlap_interval(current_metadata): + Creates an overlap interval from the current metadata. + update_overlap_interval(overlap_interval, current_metadata): + Updates the overlap interval with the current metadata. + update_overlap_interval_with_same_starttime(i, sorted_metadata_list, overlap_interval): + Updates the overlap interval with metadata that have the same starttime. + set_endtime(i, sorted_metadata_list, overlap_interval, current_metadata): + Sets the endtime for the overlap interval. + convert_to_calibration(overlap_metadata): + Converts overlapping metadata information to an applicable calibration. + get_channels(overlap_metadata): + Gets the channels for the applicable calibration. """ def __init__(self, metadata_list): @@ -81,14 +81,14 @@ class InstrumentCalibrations: Main method to compile applicable calibrations from the metadata list. Parameters - ---------- - query_starttime : UTCDateTime - the query starttime + ---------- + query_starttime : UTCDateTime + the query starttime Returns ------- - list - a list of dictionaries, each representing an applicable calibration + list + a list of dictionaries, each representing an applicable calibration """ sorted_metadata_list = sorted( self.metadata_list, @@ -120,13 +120,13 @@ class InstrumentCalibrations: Parameters ---------- - current_metadata : Metadata - the current metadata object + current_metadata : Metadata + the current metadata object Returns ------- - dict - a dictionary representing an overlap interval + dict + a dictionary representing an overlap interval """ overlap_interval = {"starttime": current_metadata.starttime} self.update_overlap_interval(overlap_interval, current_metadata) @@ -138,10 +138,10 @@ class InstrumentCalibrations: Parameters ---------- - overlap_interval : dict - the overlap interval to be updated - current_metadata : Metadata - the current metadata object + overlap_interval : dict + the overlap interval to be updated + current_metadata : Metadata + the current metadata object """ for axis in ["u", "v", "w"]: for key in ["constant", "bin", "offset"]: @@ -172,17 +172,17 @@ class InstrumentCalibrations: Parameters ---------- - i : int - the current index in the sorted metadata list - sorted_metadata_list : list - the sorted list of metadata objects - overlap_interval : dict - the overlap interval to be updated + i : int + the current index in the sorted metadata list + sorted_metadata_list : list + the sorted list of metadata objects + overlap_interval : dict + the overlap interval to be updated Returns ------- - int - the updated index in the sorted metadata list + int + the updated index in the sorted metadata list """ while ( i + 1 < len(sorted_metadata_list) @@ -200,14 +200,14 @@ class InstrumentCalibrations: Parameters ---------- - i : int - the current index in the sorted metadata list - sorted_metadata_list : list - the sorted list of metadata objects - overlap_interval : dict - the overlap interval to be updated - current_metadata : Metadata - the current metadata object + i : int + the current index in the sorted metadata list + sorted_metadata_list : list + the sorted list of metadata objects + overlap_interval : dict + the overlap interval to be updated + current_metadata : Metadata + the current metadata object """ if ( i + 1 < len(sorted_metadata_list) @@ -223,13 +223,13 @@ class InstrumentCalibrations: Parameters ---------- - overlap_metadata : dict - the metadata overlap data to be converted + overlap_metadata : dict + the metadata overlap data to be converted Returns ------- - dict - a dictionary representing an applicable calibration + dict + a dictionary representing an applicable calibration """ calibration = { "network": self.network, @@ -250,13 +250,13 @@ class InstrumentCalibrations: Parameters ---------- - overlap_metadata : dict - the metadata overlap data from which to get the channels + overlap_metadata : dict + the metadata overlap data from which to get the channels Returns ------- - dict - a dictionary representing the channels for the applicable calibration + dict + a dictionary representing the channels for the applicable calibration """ channels = {} for axis in ["U", "V", "W"]: @@ -304,14 +304,17 @@ def get_instrument_calibrations( ): """Get instrument metadata - Args: - observatory: observatory code - start_time: start time to match, or None to match any. - end_time: end time to match, or None to match any. - calibrations: use custom list, defaults to pulling and converting instrument metadata - metadata_url: metadata database url + Parameters: + ----------- + observatory: observatory code + start_time: start time to match, or None to match any. + end_time: end time to match, or None to match any. + calibrations: use custom list, defaults to pulling and converting instrument metadata + metadata_url: metadata database url + Returns: - list of applicable instrument calibrations + -------- + list of applicable instrument calibrations """ if not calibrations: @@ -329,7 +332,7 @@ def get_instrument_calibrations( data_valid=True, ) try: - metadata = factory.get_metadata(query=query) + metadata = factory.get_metadata(query=query, timeout=28) except: print( "Warning: An error occurred while trying to pull metadata from the metadata server!" -- GitLab From 6e331efba3ccaa86f01778fb52f3c7b4fbaf51b7 Mon Sep 17 00:00:00 2001 From: bgeels <bgeels@usgs.gov> Date: Wed, 7 Aug 2024 10:49:45 -0600 Subject: [PATCH 4/5] It might be a good idea to actually use the timeout params... --- geomagio/metadata/MetadataFactory.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/geomagio/metadata/MetadataFactory.py b/geomagio/metadata/MetadataFactory.py index 2f718fe3..3212f6e7 100644 --- a/geomagio/metadata/MetadataFactory.py +++ b/geomagio/metadata/MetadataFactory.py @@ -127,8 +127,7 @@ class MetadataFactory(object): The Metadata object corresponding to the given ID. """ response = requests.get( - url=f"{self.url}/{id}", - headers=self._get_headers(), + url=f"{self.url}/{id}", headers=self._get_headers(), timeout=timeout ) return Metadata(**response.json()) @@ -155,6 +154,7 @@ class MetadataFactory(object): url=self.url, data=metadata.json(), headers=self._get_headers(), + timeout=timeout, ) return Metadata(**response.json()) @@ -182,6 +182,7 @@ class MetadataFactory(object): url=f"{self.url}/{metadata.id}", data=metadata.json(), headers=self._get_headers(), + timeout=timeout, ) return Metadata(**response.json()) -- GitLab From 801fb843fddd499b387a5351d60d88abf349307c Mon Sep 17 00:00:00 2001 From: bgeels <bgeels@usgs.gov> Date: Wed, 7 Aug 2024 10:53:32 -0600 Subject: [PATCH 5/5] Undo overzealous 'replace-all' --- geomagio/metadata/MetadataFactory.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/geomagio/metadata/MetadataFactory.py b/geomagio/metadata/MetadataFactory.py index 3212f6e7..fdd134e6 100644 --- a/geomagio/metadata/MetadataFactory.py +++ b/geomagio/metadata/MetadataFactory.py @@ -202,7 +202,7 @@ def parse_params(query: MetadataQuery) -> str: A dictionary of parameters formatted for the API request. """ query = query.dict(exclude_none=True) - Parameters = {} + args = {} for key in query.keys(): element = query[key] # Convert times to ISO format strings @@ -210,9 +210,9 @@ def parse_params(query: MetadataQuery) -> str: element = element.isoformat() # Serialize the metadata dictionary to a JSON string elif key == "metadata" and isinstance(element, dict): - Parameters[key] = json.dumps(element) + args[key] = json.dumps(element) # Get string value of metadata category if key == "category": element = element.value - Parameters[key] = element - return Parameters + args[key] = element + return args -- GitLab