Source code for openghg.util._util

""" Utility functions that are used by multiple modules

"""

from collections.abc import Iterable
from pathlib import Path
from typing import Any, Dict, Iterator, Optional, Tuple, Union
import logging

from openghg.types import multiPathType

logger = logging.getLogger("openghg.util")
logger.setLevel(logging.DEBUG)  # Have to set level for logger as well as handler


[docs] def running_in_cloud() -> bool: """Are we running in the cloud? Checks for the OPENGHG_CLOUD environment variable being set Returns: bool: True if running in cloud """ from os import environ cloud_env = environ.get("OPENGHG_CLOUD", "0") return bool(int(cloud_env))
[docs] def running_on_hub() -> bool: """Are we running on the OpenGHG Hub? Checks for the OPENGHG_CLOUD environment variable being set Returns: bool: True if running in cloud """ from os import environ hub_env = environ.get("OPENGHG_HUB", "0") return bool(int(hub_env))
[docs] def running_locally() -> bool: """Are we running OpenGHG locally? Returns: bool: True if running locally """ return not (running_on_hub() or running_in_cloud())
[docs] def unanimous(seq: Dict) -> bool: """Checks that all values in an iterable object are the same Args: seq: Iterable object Returns bool: True if all values are the same """ it = iter(seq.values()) try: first = next(it) except StopIteration: return True else: return all(i == first for i in it)
[docs] def pairwise(iterable: Iterable) -> Iterator[Tuple[Any, Any]]: """Return a zip of an iterable where a is the iterable and b is the iterable advanced one step. Args: iterable: Any iterable type Returns: tuple: Tuple of iterables """ from itertools import tee a, b = tee(iterable) next(b, None) return zip(a, b)
[docs] def site_code_finder(site_name: str) -> Optional[str]: """Find the site code for a given site name. Args: site_name: Site long name Returns: str or None: Three letter site code if found """ from openghg.util import remove_punctuation from rapidfuzz import process # type: ignore site_name = remove_punctuation(site_name) inverted = _create_site_lookup_dict() # rapidfuzz 3.9.0 seemed to stop giving type details - ignoring for now. matches = process.extract(query=site_name, choices=inverted.keys()) # type:ignore highest_score = matches[0][1] if highest_score < 90: return None # If there are multiple >= 90 matches we return None as this is ambiguous greater_than_90 = sum(match[1] >= 90 for match in matches) if greater_than_90 > 1: logger.warning("Please provide more site information, more than one site found.") return None matched_site = matches[0][0] site_code: str = inverted[matched_site] return site_code.lower()
[docs] def find_matching_site(site_name: str, possible_sites: Dict) -> str: """Try and find a similar name to site_name in site_list and return a suggestion or error string. Args: site_name: Name of site site_list: List of sites to check Returns: str: Suggestion / error message """ from rapidfuzz import process site_list = possible_sites.keys() # rapidfuzz 3.9.0 seemed to stop giving type details - ignoring for now. matches = process.extract(site_name, site_list) # type:ignore scores = [s for m, s, _ in matches] # This seems like a decent cutoff score for a decent find cutoff_score = 85 if scores[0] < cutoff_score: return f"No suggestion for {site_name}." elif scores[0] > cutoff_score and scores[0] > scores[1]: best_match = matches[0][0] return f"Did you mean {best_match.upper()}, code: {possible_sites[best_match]} ?" elif scores[0] == scores[1]: suggestions = [f"{match.title()}, code: {possible_sites[match]}" for match, _, _ in matches] nl_char = "\n" return f"Did you mean one of : \n {nl_char.join(suggestions)}" else: return f"Unknown site: {site_name}"
def _create_site_lookup_dict() -> Dict: """Create a dictionary of site name: three letter site code values Returns: dict: Dictionary of site_name: site_code values """ from openghg_defs import site_info_file from openghg.util import load_json, remove_punctuation site_info = load_json(path=site_info_file) inverted = {} for site, site_data in site_info.items(): for _, network_data in site_data.items(): try: long_name = network_data["long_name"] except KeyError: pass else: # Remove the country from the name try: no_country = remove_punctuation(long_name.split(",")[0]) except IndexError: no_country = remove_punctuation(long_name) inverted[no_country] = site break return inverted
[docs] def verify_site(site: str) -> Optional[str]: """Check if the passed site is a valid one and returns the three letter site code if found. Otherwise we use fuzzy text matching to suggest sites with similar names. Args: site: Three letter site code or site name Returns: str: Verified three letter site code if valid site """ from openghg.util import load_json from openghg_defs import site_info_file site_data = load_json(path=site_info_file) if site.upper() in site_data: return site.lower() else: site_code = site_code_finder(site_name=site) if site_code is None: logger.warning(f"Unable to find site code for {site}, please provide additional metadata.") return site_code
[docs] def multiple_inlets(site: str) -> bool: """Check if the passed site has more than one inlet Args: site: Three letter site code Returns: bool: True if multiple inlets """ from openghg.util import get_site_info site_data = get_site_info() site = site.upper() network = next(iter(site_data[site])) try: heights = set(site_data[network]["height"]) except KeyError: try: heights = set(site_data[network]["height_name"]) except KeyError: return True return len(heights) > 1
def sort_by_filenames(filepath: Union[multiPathType, Any]) -> list[Path]: """ Sorting time on filename basis Args: filepath: Path to the file Returns: list[Path]: List of sorted paths """ # This code is to stop mypy complaints regarding file types if isinstance(filepath, str): filepath = [Path(filepath)] elif isinstance(filepath, Path): filepath = [filepath] elif isinstance(filepath, (tuple, list)): filepath = [Path(f) for f in filepath] else: raise TypeError(f"Unsupported type for filepath: {type(filepath)}") return sorted(filepath)