Source code for mgnipy.V2.describe
import inspect
import logging
logger = logging.getLogger(__name__)
import os
from copy import deepcopy
from math import ceil
from types import ModuleType
from typing import Any, Optional
from urllib.parse import urlencode
import httpx
from mgnipy._shared_helpers.parsers import get_docstring, parse_docstring
from mgnipy.V2.endpoints import ALL_LIST_ENDPOINTS, PRIVATE_ENDPOINTS
[docs]
class DescribeEmgapiModule:
def __init__(self, endpoint_module: Optional[ModuleType] = None):
self.endpoint_module = endpoint_module
self.default_page_size: int = 25
[docs]
def list_supported_params(self) -> list[str]:
"""
Lists supported keyword arguments for the endpoint module.
Returns
-------
list of str
List of supported keyword argument names.
"""
sig = inspect.signature(self.endpoint_module._get_kwargs)
return list(sig.parameters.keys())
[docs]
def validate_endpoint_kwargs(self, **kwargs) -> dict[str, Any]:
"""
Validates the provided keyword arguments against the supported parameters of the endpoint module.
Parameters
----------
**kwargs
Keyword arguments to validate.
Returns
-------
dict of str to Any
The validated keyword arguments.
Raises
------
ValueError
If any provided keyword argument is not supported by the endpoint module.
"""
return self.endpoint_module._get_kwargs(**kwargs)
@property
def emgapi_resource(self) -> Optional[str]:
"""
Retrieves the name of the endpoint resource based on the endpoint module.
Returns
-------
str or None
The name of the endpoint resource, or None if the endpoint module is not set.
"""
return os.path.basename(os.path.dirname(self.endpoint_module.__file__))
[docs]
def sub_url(self, **kwargs) -> Optional[str]:
"""
Constructs the sub-URL for the endpoint based on the current parameters.
Returns
-------
str or None
The constructed sub-URL, or None if the endpoint module is not set.
"""
_kwargs = self.validate_endpoint_kwargs(**kwargs)
_end_url: str = _kwargs.get(
"url", f"/metagenomics/api/v2/{self.emgapi_resource}/"
).strip("/")
return _end_url
[docs]
def resolve_query_string(self, **kwargs) -> str:
"""
Resolves the query string for the endpoint based on the current parameters.
Parameters
----------
**kwargs
Keyword arguments to validate and include in the query string.
Returns
-------
str
The resolved query string.
"""
_kwargs = self.validate_endpoint_kwargs(**kwargs)
# get validated params if any
params = _kwargs.get("params", {})
# encode params for url
return urlencode(params, doseq=True)
[docs]
def url_path(self, **kwargs) -> str:
"""
Constructs the full URL path for the endpoint based on the current parameters.
Parameters
----------
**kwargs
Keyword arguments to validate and include in the URL construction.
Returns
-------
str
The constructed URL path.
"""
_end_url = self.sub_url(**kwargs)
query_string = self.resolve_query_string(**kwargs)
return f"{_end_url}?{query_string}" if query_string else _end_url
@property
def emgapi_docs(self) -> str:
return get_docstring(self.endpoint_module, "sync")
[docs]
def describe_endpoint(self, 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
----------
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.
"""
return parse_docstring(self.emgapi_docs, as_dict=as_dict)
@property
def is_private(self) -> bool:
"""Checks if the endpoint module corresponds to a private only endpoint."""
return self.endpoint_module in PRIVATE_ENDPOINTS
@property
def is_list_endpoint(self) -> bool:
"""Checks if the endpoint module corresponds to a list endpoint."""
return self.endpoint_module in ALL_LIST_ENDPOINTS
[docs]
def get_num_items(
self, client: httpx.Client, params: Optional[dict] = None
) -> Optional[int]:
_params = deepcopy(params) or {}
if not self.is_list_endpoint:
return 1
_params.update({"page_size": 1})
with client as c:
response = self.endpoint_module.sync_detailed(client=c, **_params)
if response.status_code == 200:
return response.parsed.to_dict().get("count", 0)
else:
logger.warning(
f"Failed to retrieve count for endpoint {self.emgapi_resource}. Status code: {response.status_code}"
)
[docs]
def get_num_pages(
self, num_items: Optional[int], page_size: Optional[int] = None
) -> Optional[int]:
"""Calculates the total number of pages based on the total count and default page size."""
if num_items is None:
return None
ps = page_size or self.default_page_size
return ceil(num_items / ps)
[docs]
def page_param_iter(self, num_pages: int) -> list[dict[str, int]]:
"""Generates a list of parameter dictionaries for each page based on the total number of pages."""
return [{"page": page} for page in range(1, num_pages + 1)]