Source code for mgnipy.mgnipy

import logging

logger = logging.getLogger(__name__)
from typing import Optional

from mgnipy._models.config import MGnipyConfig, to_mgnipy_config
from mgnipy._models.constants.CONSTANTS import SupportedEndpoints
from mgnipy.V2.proxies import (
    V2_ENDPOINT_DETAIL_PROXIES,
    V2_ENDPOINT_LIST_PROXIES,
)

V2_ALL_PROXIES = V2_ENDPOINT_DETAIL_PROXIES | V2_ENDPOINT_LIST_PROXIES


[docs] class MGnipy: """ Main class for interacting with the MGnify API. Provides methods to access different resources (e.g., studies, samples, analyses) and their details, as well as utility methods for listing resources and describing endpoints. Parameters ---------- config : MGnipyConfig or dict, optional Configuration for MGnipy, either as an MGnipyConfig instance or a dictionary of configuration parameters (default is None). **config_kwargs Additional keyword arguments to pass to the MGnipyConfig constructor if config is not provided. For example, cache_dir can be specified as a keyword argument. e.g. MGnipy(cache_dir="/path/to/cache") Examples -------- >>> MG = MGnipy(cache_dir="/path/to/cache") >>> print(MG.cache_dir) /path/to/cache """ def __init__( self, config: Optional[MGnipyConfig | dict] = None, **config_kwargs, ): self._config: MGnipyConfig = ( to_mgnipy_config(config) if config else MGnipyConfig(**config_kwargs) ) self._endpoints = self.list_resources() def __getattr__(self, name: str): if name == "cache_dir": return self._config.cache_dir endpoint = SupportedEndpoints.validate(name) if endpoint in V2_ENDPOINT_LIST_PROXIES: list_cls = V2_ENDPOINT_LIST_PROXIES[endpoint] return list_cls(config=self._config) if endpoint in V2_ENDPOINT_DETAIL_PROXIES: detail_cls = V2_ENDPOINT_DETAIL_PROXIES[endpoint] # Return a callable so required args like accession/biome_lineage # are provided when user calls MG.study(...), MG.biome(...), etc. def _detail_factory(id: Optional[str] = None, **kwargs): return detail_cls(id=id, config=self._config, **kwargs) return _detail_factory raise AttributeError(f"{type(self).__name__} has no attribute {name!r}")
[docs] def list_resources(self): return SupportedEndpoints.as_list()
[docs] def describe_resource( self, resource: str, as_dict: bool = False ) -> dict[str, str] | None: """ Provides a description of the endpoint from the openapi documentation i.e., https://www.ebi.ac.uk/metagenomics/api/v2/openapi.json Parameters ---------- resource : str The name of the resource to describe. as_dict : bool, optional Whether to return the description as a dictionary mapping parameter names to their descriptions (default is False). Returns ------- dict of str to str or None A dictionary mapping parameter names to their descriptions if as_dict is True, otherwise None. """ try: endpoint = SupportedEndpoints.validate(resource) except ValueError: logger.error( f"Resource '{resource}' is not supported. Supported resources are: {', '.join(self.list_resources())}" ) return None proxy_cls = V2_ALL_PROXIES[endpoint] logger.debug(f"Using proxy class {proxy_cls} to describe resource '{resource}'") proxy = proxy_cls(config=self._config) return proxy.emgapi_handler.describe_endpoint(as_dict=as_dict)
[docs] def describe_resources( self, resource: Optional[str] = None, as_dict: bool = False ) -> dict[str, str] | None: """ Provides a description of the endpoint from the openapi documentation i.e., https://www.ebi.ac.uk/metagenomics/api/v2/openapi.json Parameters ---------- resource : str, optional The name of the resource to describe. as_dict : bool, optional Whether to return the description as a dictionary mapping parameter names to their descriptions (default is False). Returns ------- dict of str to str or None A dictionary mapping parameter names to their descriptions if as_dict is True, otherwise None. """ if resource is not None: return self.describe_resource(resource, as_dict=as_dict) descriptions = {} for endpoint in SupportedEndpoints: desc = self.describe_resource(endpoint.value, as_dict=as_dict) if desc is not None: descriptions[endpoint.value] = desc return descriptions
[docs] def clear_subcaches(self) -> None: """ Clear the cache for a specific resource or all resources. Parameters ---------- resource : str, optional The name of the resource to clear the cache for. If None, clears the cache for all resources (default is None). """ if self.cache_dir is None: return if self.cache_dir.exists(): logger.warning(f"Clearing ALL cache subdirectories in {self.cache_dir}") # for each cache subdir for cache_key in self.cache_dir.iterdir(): if cache_key.is_dir(): logger.debug(f"Processing cache subdirectory {cache_key}") # !!!! additional check of folder setup just in case? # !!!! to avoid accidentally deleting something important if cache_dir is misconfigured # all files in cache should either be named 'mgnipy_manifest.json' or 'mgnipy_page_*.json' for page results if not all( cache_file.name == "mgnipy_manifest.json" or ( cache_file.name.startswith("mgnipy_page_") and cache_file.suffix == ".json" ) for cache_file in cache_key.iterdir() ): raise RuntimeError( f"Cache subdirectory {cache_key} contains unexpected files, " "aborting cache clearing to avoid potential data loss. " "Please check the cache directory configuration and contents. " f"Please check the given cache_dir path and file contents: {self.cache_dir}" ) for cache_file in cache_key.iterdir(): # try clearning file by file # extra check just in case if cache_file.name == "mgnipy_manifest.json" or ( cache_file.name.startswith("mgnipy_page_") and cache_file.suffix == ".json" ): try: logger.debug(f"Clearing cache file {cache_file}") cache_file.unlink() except Exception: logger.warning( f"Failed to delete cache file: {cache_file}" ) # now try to delete subdir itself try: cache_key.rmdir() except Exception: logger.warning(f"Failed to delete cache directory: {cache_key}") else: # don't delete the auth token cache which is not in a dir logger.info(f"Skipping non-directory cache item: {cache_key}") else: logger.info( f"Cache directory {self.cache_dir} does not exist, nothing to clear." )
def __str__(self): return f"MGnipy(config={self._config})"