Module deeporigin.src.pocket_finder

Classes

class PocketFinderClient
Expand source code
class PocketFinderClient(Client):
    """
    A client class for finding and analyzing protein pockets using a pocket finding service.
    This class provides functionality to identify potential binding pockets in protein structures,
    process the results, and visualize them. It supports both synchronous and asynchronous
    operations for pocket finding requests.

    Attributes:
        None

    Methods:
        get_protein_data_object(protein: Protein) -> dict:
        pocket_finder_request(logger: Logger, protein_data) -> tuple:
        update_progress_bar(logger: Logger, progress: ProgressView, body: dict):
        async_find_pockets(protein: Union[str, Protein], results_dir: Optional[str]) -> str:
            Asynchronously finds pockets in a protein structure.
        async_get_find_pockets_response(request_id: str) -> tuple:
        wait_for_pocket_finder_response(logger: Logger, request_id: str, progress_view: ProgressView, interval: float) -> tuple:
        process_pocket_finder_request(logger: Logger, protein: Protein) -> tuple:
            Process a request to find pockets in a protein structure.
        preprocess(protein, results_dir: str) -> Protein:
        find_pockets(protein: Union[str, Protein], results_dir: Optional[str]) -> Tuple[Protein, List[Pocket]]:
            Find pockets in a protein structure using PocketFinder algorithm.
        show_pockets(protein: Protein, pockets: List[Pocket], protein_surface_alpha: float, pocket_surface_alpha: float) -> str:

        >>> client = PocketFinderClient()
        >>> protein = Protein("example.pdb")
        >>> protein, pockets = client.find_pockets(protein, "results")
        >>> client.show_pockets(protein, pockets)
    """
    def __init__(self):
        super().__init__()

    def get_protein_data_object(self, protein: Protein):
        """
        Creates a dictionary containing protein data information.

        Args:
            protein (Protein): A Protein object containing block type and content.

        Returns:
            dict: A dictionary with two keys:
                - 'extension': The block type of the protein
                - 'content': The block content of the protein
        """
        return {"extension": protein.block_type, "content": protein.block_content}

    def pocket_finder_request(self, logger: Logger, protein_data):
        """
        Sends a request to find pockets in a protein structure.

        This method communicates with a pocket finding service by sending protein data and processing
        the response to identify potential binding pockets in the protein structure.

        Args:
            logger (Logger): Logger object to track the execution flow and record events
            protein_data: Protein structure data to analyze for pockets

        Returns:
            tuple: A tuple containing:
                - data: The processed pocket finder results if successful, None otherwise
                - success (bool): True if the request was successful, False otherwise

        Raises:
            May raise exceptions related to HTTP requests or JSON processing

        Notes:
            - Handles 503 errors specifically for worker availability issues
            - Uses both console logging and provided logger for different logging purposes
            - Maintains logging depth for hierarchical logging structure
        """
        console_logger = Logger("INFO", None)

        logger.log_info("PocketFinder Request - Preparing pocket finder request.")
        logger.add_depth()

        body = {"protein_data": protein_data}

        data, success = None, False
        try:
            logger.log_info("PocketFinder Request - Sending pocket finder request.")
            response = self.post_request(
                endpoint="pocket_finder",
                logger=logger,
                data=body,
            )

            logger.log_info("PocketFinder Request - Received pocket finder response.")
            logger.sub_depth()

            data, success = response.json(), True
        except Exception as e:
            if response.status_code == 503:
                console_logger.log_error(WARNING_NUMBER_OF_AVAILABLE_WORKERS)
            else:
                console_logger.log_warning(WARNING_MESSAGE)

            logger.log_error(
                f"PocketFinder Request - Unexpected status code: {response.status_code}. \nMessage: {response.text}"
            )
            logger.sub_depth()

            data, success = None, False

        return data, success

    def update_progress_bar(self, logger: Logger, progress: ProgressView, body):
        """
        Updates the progress bar based on the received response body.

        Args:
            logger (Logger): Logger instance to log progress information
            progress (ProgressView): Progress bar view instance to be updated
            body (dict): Response body containing progress information with keys:
                - description (str): Description of current progress state
                - percentage (int/float): Percentage completion value

        Updates the progress bar's description and percentage based on the body content.
        When "Completed" status is received, updates with completion values.
        For ongoing progress, only updates description if different from current,
        and logs when progress increases.
        """
        if body.get("description", "") == "Completed":
            progress.update(new_desc=body["description"], new_percentage=body["percentage"])
            progress.display()
        else:
            new_percentage = int(body["percentage"])
            new_description = body["description"]

            if new_description == progress.description:
                new_description = None

            if new_percentage > progress.bar.n:
                logger.log_info("PocketFinder progress achieved.")

            progress.update(new_desc=new_description, new_percentage=new_percentage)
            progress.display()

    def async_find_pockets(self, protein: Union[str, Protein], results_dir: Optional[str] = ""):
        """
        Asynchronously finds pockets in a protein structure using the pocket finder service.

        Args:
            protein (Union[str, Protein]): Either a path to a protein structure file or a Protein object.
            results_dir (Optional[str], optional): Directory to store results. Defaults to "".

        Returns:
            str: Request ID for tracking the asynchronous pocket finding process.

        Raises:
            TypeError: If protein parameter is neither a string path nor a Protein object.
        """
        if not isinstance(protein, Protein):
            protein = Protein(file_path=protein)

        logger = Logger("INFO", os.getenv("LOG_BIOSIM_CLIENT"))

        protein = self.preprocess(protein, results_dir)
        protein_data = self.get_protein_data_object(protein)

        request_id, _ = self.pocket_finder_request(logger, protein_data)
        return request_id

    def async_get_find_pockets_response(self, request_id: str):
        """
        Asynchronously retrieves the response for a pocket finding request.

        This method queries the server for the status and results of a previously submitted
        pocket finding job identified by the request_id. It interprets various HTTP status
        codes to determine the current state of the request.

        Args:
            request_id (str): The unique identifier for the pocket finding request.

        Returns:
            tuple: A tuple containing two elements:
                - First element: JSON response data if successful (status 200 or 202), None if failed
                - Second element: String indicating status ("Completed", "Running", or "Error")

        Status Codes:
            - 200: Request completed successfully
            - 202: Request is still processing
            - 429: Rate limit exceeded
            - Other: Unexpected error

        Example:
            >>> response_data, status = client.async_get_find_pockets_response("request123")
            >>> if status == "Completed":
            ...     process_results(response_data)
        """
        response = None
        logger = Logger("INFO", os.getenv("LOG_BIOSIM_CLIENT"))

        response = self.get_request(endpoint=f"pocket_finder/{request_id}", logger=logger)

        match response.status_code:
            case 200:
                logger.log_info(f"PocketFinder request successfully completed: status_code = {response.status_code}")

                return response.json(), "Completed"
            case 202:
                return response.json(), "Running"
            case 429:
                logger.log_warning(WARNING_MESSAGE)
                logger.log_error(
                    f"PocketFinder request failed: status_code = {response.status_code}, message: {response.text}"
                )
                return None, "Error"
            case _:
                logger.log_warning(WARNING_MESSAGE)
                logger.log_error("PocketFinder request unexpected status code.")
                return None, "Error"

    def wait_for_pocket_finder_response(self, logger: Logger, request_id: str, progress_view: ProgressView, interval=0.5):
        """
        Waits for and handles responses from the pocket finder service with progress updates.

        This method continuously polls the pocket finder service until a final response is received,
        updating a progress bar during the process.

        Args:
            logger (Logger): Logger instance for tracking the process
            request_id (str): Unique identifier for the pocket finder request
            progress_view (ProgressView): Progress bar view instance to update status
            interval (float, optional): Polling interval in seconds. Defaults to 0.5

        Returns:
            tuple: A tuple containing:
                - data (dict|None): Response data if successful, None otherwise
                - success (bool): True if request completed successfully, False otherwise

        Status Codes:
            - 200: Request completed successfully
            - 202: Request is still processing
            - 429: Rate limit exceeded
            - Other: Unexpected error

        Note:
            The method will update the progress bar during processing and close it upon completion.
            It also handles logging at different levels based on the response status.
        """
        logger.add_depth()

        console_logger = Logger("INFO", None)
        logger.log_info("Waiting for pocket_finder service.")

        data, success = None, False
        while True:
            time.sleep(interval)
            response = self.get_request(endpoint=f"pocket_finder/{request_id}", logger=logger)

            match response.status_code:
                case 200:
                    logger.log_info(
                        f"PocketFinder request successfully completed: status_code = {response.status_code}"
                    )

                    data, success = response.json(), True
                    break
                case 202:
                    body = response.json()
                    self.update_progress_bar(logger, progress_view, body)
                case 429:
                    console_logger.log_warning(WARNING_MESSAGE)
                    logger.log_error(
                        f"PocketFinder request failed: status_code = {response.status_code}, message: {response.text}"
                    )

                    data, success = None, False
                    break
                case _:
                    console_logger.log_warning(WARNING_MESSAGE)
                    logger.log_error("PocketFinder request unexpected status code.")

                    data, success = None, False
                    break

        if success:
            self.update_progress_bar(logger, progress_view, {"description": "Completed", "percentage": 100})
            progress_view.close()

            logger.log_info("PocketFinder completed: Proceeding to the next step")
        logger.sub_depth()

        return data, success

    def process_pocket_finder_request(self, logger: Logger, protein: Protein):
        """Process a request to find pockets in a protein structure.

        This method handles the complete workflow of pocket finding, including making the initial request
        and waiting for the response from the pocket finder service.

        Args:
            logger (Logger): Logger instance to track the process and record any issues
            protein (Protein): Protein object containing the structure to analyze

        Returns:
            tuple: A tuple containing:
                - response: The server response containing pocket information if successful, None otherwise
                - completed (bool): Flag indicating if the process completed successfully

        Raises:
            Exception: If there are server-side errors (HTTP 503 for no available workers, or other server issues)

        Note:
            The method handles server errors internally, logging appropriate warnings and errors.
            It uses a ProgressView to track the request progress and will return (None, False)
            if any errors occur during processing.
        """
        console_logger = Logger("INFO", None)

        response, completed = None, False

        logger.log_info("PocketFinder: Starting pocket finder.")
        logger.add_depth()

        protein_data = self.get_protein_data_object(protein)

        progress_view = ProgressView()

        try:
            request_id, completed = self.pocket_finder_request(logger, protein_data)
            if completed:
                response, completed = self.wait_for_pocket_finder_response(logger, request_id, progress_view)
                if not completed:
                    console_logger.log_warning(WARNING_MESSAGE_SERVER)
                    logger.log_error(
                        "PocketFinder: Something went wrong. For more information please check backend service logs."
                    )
        except Exception as e:
            if response.status_code == 503:
                console_logger.log_error(WARNING_NUMBER_OF_AVAILABLE_WORKERS)
            else:
                console_logger.log_warning(WARNING_MESSAGE_SERVER)

            logger.log_error(f"Something went wrong on server side - {str(e)}")
            response, completed = None, False

        return response, completed

    def preprocess(self, protein, results_dir=""):
        """
        Preprocesses the input protein data for pocket finding.

        This method validates the results directory and creates it if necessary, and ensures
        the input protein is in the correct format (either a Protein object or a valid protein file path).

        Args:
            protein (Union[Protein, str]): Either a Protein object or a path to a protein file.
            results_dir (str, optional): Directory path for storing results. Defaults to empty string.

        Returns:
            Protein: A valid Protein object ready for pocket finding analysis.

        Raises:
            Exception: If results_dir is non-empty.
            Exception: If protein is neither a Protein object nor a valid file path.
            Exception: If protein file cannot be processed into a Protein object.
        """
        if len(results_dir) > 0:
            if os.path.exists(results_dir) and len(os.listdir(results_dir)) > 0:
                raise Exception("Non empty directory is given as results directory.")

            if not os.path.exists(results_dir):
                os.makedirs(results_dir)

        if not isinstance(protein, Protein):
            if not isinstance(protein, str):
                raise Exception("Find pockets can only be performed either on protein object or protein file.")

            try:
                protein = Protein(file_path=protein)
            except Exception as e:
                raise Exception(f"Failed to create protein object from the given file. \n Exception: {str(e)}")

        return protein

    def find_pockets(self, protein: Union[str, Protein], results_dir: Optional[str] = "pocket_results") -> Tuple[Protein, List[Pocket]]:
        """Find pockets in a protein structure using PocketFinder algorithm.
        This method processes a protein structure to identify and analyze potential binding pockets,
        creates visualization files, and generates a comprehensive report of the findings.
        Args:
            protein (Union[str, Protein]): Input protein structure, either as a file path string or Protein object
            results_dir (Optional[str]): Directory to store pocket finder results (default: "pocket_results")
        Returns:
            Tuple[Protein, List[Pocket]]: A tuple containing:
                - The processed protein object
                - A PocketFinderReport object containing the identified pockets and their properties
        Raises:
            Exception: If there is an error during pocket finding or result processing
        Notes:
            - Creates a unique directory for each protein analysis
            - Converts pocket atoms to Xenon for visualization purposes
            - Generates PDB files for each identified pocket
            - Creates a properties CSV file with pocket metrics
            - Uses custom color schemes for pocket visualization
        """
        console_logger = Logger("INFO", None)
        logger = Logger("INFO", os.getenv("LOG_BIOSIM_CLIENT"))
        logger.log_info("Executing pocket finder request.")

        base_path = Path(str(results_dir)) / protein.file_path.stem
        if not base_path.exists():
            base_path.mkdir(parents=True)
        else:
            number = len(list(Path(str(results_dir)).glob(f"{protein.file_path.stem}*")))
            base_path = Path(str(results_dir)) / f"{protein.file_path.stem}_#{number}"
            base_path.mkdir(parents=True)

        protein = self.preprocess(protein, str(base_path))
        props_file_csv = base_path / f"{protein.file_path.stem}_pocket_finder_props.csv"
        pocket_report = PocketFinderReport(protein, props_file_csv)
        pocket_dicts, completed = self.process_pocket_finder_request(logger, protein)
        if completed:
            try:
                logger.log_info("Parsing pocket finder results.")

                surface_colors = ProteinViewer("", format="pdb").get_pocket_visualization_config().surface_colors
                for index, pocket_dict in enumerate(pocket_dicts):
                    color = surface_colors[index % len(surface_colors)]
                    pocket = Pocket(
                        color=color,
                        block_type="pdb",
                        index=index,
                        name=f"{protein.file_path.stem}_pocket_{index}_color_{color}",
                        block_content=pocket_dict["content"],
                        props=pocket_dict.get("props", None),
                    )

                    pocket_path = base_path / f"{pocket.name}.pdb"

                    atoms = []
                    for atom in pocket.structure:
                        atom.atom_name = "Xe"
                        atom.element = "Xe"
                        atoms.append(atom)

                    pocket.structure = struc.array(atoms)
                    pocket.write_to_file(str(pocket_path))
                    pocket_report.add_pocket(pocket)

                pocket_report.save_props()
                logger.log_info("PocketFinder request successfully completed.")
            except Exception as e:
                error = str(e)
                logger.log_error("Something went wrong - " + error)
                console_logger.log_error(f"Something went wrong - Error: {error}")
        else:
            logger.log_error("Pocket finder terminated.")

        return protein, pocket_report


    @classmethod
    @jupyter_visualization
    def show_pockets(cls, protein: Protein, pockets: List[Pocket], protein_surface_alpha: float = 0, pocket_surface_alpha: float = 0.7):
        """
        Visualizes protein and its pockets in a 3D viewer.
        This class method creates an interactive 3D visualization of a protein structure
        along with its identified pockets using ProteinViewer.
        Args:
            protein (Protein): The protein object containing structural information.
            pockets (List[Pocket]): List of pocket objects to be visualized.
            protein_surface_alpha (float, optional): Transparency of the protein surface.
                0 is fully transparent, 1 is fully opaque. Defaults to 0.
            pocket_surface_alpha (float, optional): Transparency of the pocket surfaces.
                0 is fully transparent, 1 is fully opaque. Defaults to 0.7.
        Returns:
            str: HTML content containing the interactive 3D visualization that can be
                displayed in a web browser or notebook environment.
        Example:
            >>> protein = Protein("1abc.pdb")
            >>> pockets = [Pocket1, Pocket2]
            >>> html = PocketFinder.show_pockets(protein, pockets)
        """
        protein_viewer = ProteinViewer(data=str(protein.file_path), format=str(protein.block_type))
        pocket_paths = [str(pocket.file_path) for pocket in pockets]

        # Retrieve and customize pocket visualization configuration
        pocket_config = protein_viewer.get_pocket_visualization_config()
        pocket_config.surface_alpha = pocket_surface_alpha

        protein_config = protein_viewer.get_protein_visualization_config()
        protein_config.style_type = "cartoon"
        protein_config.surface_alpha = protein_surface_alpha
        pocket_config.surface_colors = [pocket.color for pocket in pockets]

        # Render the protein with pockets
        html_content = protein_viewer.render_protein_with_pockets(
            pocket_paths=pocket_paths, pocket_config=pocket_config, protein_config=protein_config
        )

        # Display the rendered HTML content
        return html_content

A client class for finding and analyzing protein pockets using a pocket finding service. This class provides functionality to identify potential binding pockets in protein structures, process the results, and visualize them. It supports both synchronous and asynchronous operations for pocket finding requests.

Attributes

None

Methods

get_protein_data_object(protein: Protein) -> dict: pocket_finder_request(logger: Logger, protein_data) -> tuple: update_progress_bar(logger: Logger, progress: ProgressView, body: dict): async_find_pockets(protein: Union[str, Protein], results_dir: Optional[str]) -> str: Asynchronously finds pockets in a protein structure. async_get_find_pockets_response(request_id: str) -> tuple: wait_for_pocket_finder_response(logger: Logger, request_id: str, progress_view: ProgressView, interval: float) -> tuple: process_pocket_finder_request(logger: Logger, protein: Protein) -> tuple: Process a request to find pockets in a protein structure. preprocess(protein, results_dir: str) -> Protein: find_pockets(protein: Union[str, Protein], results_dir: Optional[str]) -> Tuple[Protein, List[Pocket]]: Find pockets in a protein structure using PocketFinder algorithm. show_pockets(protein: Protein, pockets: List[Pocket], protein_surface_alpha: float, pocket_surface_alpha: float) -> str:

>>> client = PocketFinderClient()
>>> protein = Protein("example.pdb")
>>> protein, pockets = client.find_pockets(protein, "results")
>>> client.show_pockets(protein, pockets)

Ancestors

Static methods

def show_pockets(*args, **kwargs)

Methods

def async_find_pockets(self,
protein: str | Protein,
results_dir: str | None = '')
Expand source code
def async_find_pockets(self, protein: Union[str, Protein], results_dir: Optional[str] = ""):
    """
    Asynchronously finds pockets in a protein structure using the pocket finder service.

    Args:
        protein (Union[str, Protein]): Either a path to a protein structure file or a Protein object.
        results_dir (Optional[str], optional): Directory to store results. Defaults to "".

    Returns:
        str: Request ID for tracking the asynchronous pocket finding process.

    Raises:
        TypeError: If protein parameter is neither a string path nor a Protein object.
    """
    if not isinstance(protein, Protein):
        protein = Protein(file_path=protein)

    logger = Logger("INFO", os.getenv("LOG_BIOSIM_CLIENT"))

    protein = self.preprocess(protein, results_dir)
    protein_data = self.get_protein_data_object(protein)

    request_id, _ = self.pocket_finder_request(logger, protein_data)
    return request_id

Asynchronously finds pockets in a protein structure using the pocket finder service.

Args

protein : Union[str, Protein]
Either a path to a protein structure file or a Protein object.
results_dir : Optional[str], optional
Directory to store results. Defaults to "".

Returns

str
Request ID for tracking the asynchronous pocket finding process.

Raises

TypeError
If protein parameter is neither a string path nor a Protein object.
def async_get_find_pockets_response(self, request_id: str)
Expand source code
def async_get_find_pockets_response(self, request_id: str):
    """
    Asynchronously retrieves the response for a pocket finding request.

    This method queries the server for the status and results of a previously submitted
    pocket finding job identified by the request_id. It interprets various HTTP status
    codes to determine the current state of the request.

    Args:
        request_id (str): The unique identifier for the pocket finding request.

    Returns:
        tuple: A tuple containing two elements:
            - First element: JSON response data if successful (status 200 or 202), None if failed
            - Second element: String indicating status ("Completed", "Running", or "Error")

    Status Codes:
        - 200: Request completed successfully
        - 202: Request is still processing
        - 429: Rate limit exceeded
        - Other: Unexpected error

    Example:
        >>> response_data, status = client.async_get_find_pockets_response("request123")
        >>> if status == "Completed":
        ...     process_results(response_data)
    """
    response = None
    logger = Logger("INFO", os.getenv("LOG_BIOSIM_CLIENT"))

    response = self.get_request(endpoint=f"pocket_finder/{request_id}", logger=logger)

    match response.status_code:
        case 200:
            logger.log_info(f"PocketFinder request successfully completed: status_code = {response.status_code}")

            return response.json(), "Completed"
        case 202:
            return response.json(), "Running"
        case 429:
            logger.log_warning(WARNING_MESSAGE)
            logger.log_error(
                f"PocketFinder request failed: status_code = {response.status_code}, message: {response.text}"
            )
            return None, "Error"
        case _:
            logger.log_warning(WARNING_MESSAGE)
            logger.log_error("PocketFinder request unexpected status code.")
            return None, "Error"

Asynchronously retrieves the response for a pocket finding request.

This method queries the server for the status and results of a previously submitted pocket finding job identified by the request_id. It interprets various HTTP status codes to determine the current state of the request.

Args

request_id : str
The unique identifier for the pocket finding request.

Returns

tuple
A tuple containing two elements: - First element: JSON response data if successful (status 200 or 202), None if failed - Second element: String indicating status ("Completed", "Running", or "Error")

Status Codes: - 200: Request completed successfully - 202: Request is still processing - 429: Rate limit exceeded - Other: Unexpected error

Example

>>> response_data, status = client.async_get_find_pockets_response("request123")
>>> if status == "Completed":
...     process_results(response_data)
def find_pockets(self,
protein: str | Protein,
results_dir: str | None = 'pocket_results') ‑> Tuple[Protein, List[Pocket]]
Expand source code
def find_pockets(self, protein: Union[str, Protein], results_dir: Optional[str] = "pocket_results") -> Tuple[Protein, List[Pocket]]:
    """Find pockets in a protein structure using PocketFinder algorithm.
    This method processes a protein structure to identify and analyze potential binding pockets,
    creates visualization files, and generates a comprehensive report of the findings.
    Args:
        protein (Union[str, Protein]): Input protein structure, either as a file path string or Protein object
        results_dir (Optional[str]): Directory to store pocket finder results (default: "pocket_results")
    Returns:
        Tuple[Protein, List[Pocket]]: A tuple containing:
            - The processed protein object
            - A PocketFinderReport object containing the identified pockets and their properties
    Raises:
        Exception: If there is an error during pocket finding or result processing
    Notes:
        - Creates a unique directory for each protein analysis
        - Converts pocket atoms to Xenon for visualization purposes
        - Generates PDB files for each identified pocket
        - Creates a properties CSV file with pocket metrics
        - Uses custom color schemes for pocket visualization
    """
    console_logger = Logger("INFO", None)
    logger = Logger("INFO", os.getenv("LOG_BIOSIM_CLIENT"))
    logger.log_info("Executing pocket finder request.")

    base_path = Path(str(results_dir)) / protein.file_path.stem
    if not base_path.exists():
        base_path.mkdir(parents=True)
    else:
        number = len(list(Path(str(results_dir)).glob(f"{protein.file_path.stem}*")))
        base_path = Path(str(results_dir)) / f"{protein.file_path.stem}_#{number}"
        base_path.mkdir(parents=True)

    protein = self.preprocess(protein, str(base_path))
    props_file_csv = base_path / f"{protein.file_path.stem}_pocket_finder_props.csv"
    pocket_report = PocketFinderReport(protein, props_file_csv)
    pocket_dicts, completed = self.process_pocket_finder_request(logger, protein)
    if completed:
        try:
            logger.log_info("Parsing pocket finder results.")

            surface_colors = ProteinViewer("", format="pdb").get_pocket_visualization_config().surface_colors
            for index, pocket_dict in enumerate(pocket_dicts):
                color = surface_colors[index % len(surface_colors)]
                pocket = Pocket(
                    color=color,
                    block_type="pdb",
                    index=index,
                    name=f"{protein.file_path.stem}_pocket_{index}_color_{color}",
                    block_content=pocket_dict["content"],
                    props=pocket_dict.get("props", None),
                )

                pocket_path = base_path / f"{pocket.name}.pdb"

                atoms = []
                for atom in pocket.structure:
                    atom.atom_name = "Xe"
                    atom.element = "Xe"
                    atoms.append(atom)

                pocket.structure = struc.array(atoms)
                pocket.write_to_file(str(pocket_path))
                pocket_report.add_pocket(pocket)

            pocket_report.save_props()
            logger.log_info("PocketFinder request successfully completed.")
        except Exception as e:
            error = str(e)
            logger.log_error("Something went wrong - " + error)
            console_logger.log_error(f"Something went wrong - Error: {error}")
    else:
        logger.log_error("Pocket finder terminated.")

    return protein, pocket_report

Find pockets in a protein structure using PocketFinder algorithm. This method processes a protein structure to identify and analyze potential binding pockets, creates visualization files, and generates a comprehensive report of the findings.

Args

protein : Union[str, Protein]
Input protein structure, either as a file path string or Protein object
results_dir : Optional[str]
Directory to store pocket finder results (default: "pocket_results")

Returns

Tuple[Protein, List[Pocket]]
A tuple containing: - The processed protein object - A PocketFinderReport object containing the identified pockets and their properties

Raises

Exception
If there is an error during pocket finding or result processing

Notes

  • Creates a unique directory for each protein analysis
  • Converts pocket atoms to Xenon for visualization purposes
  • Generates PDB files for each identified pocket
  • Creates a properties CSV file with pocket metrics
  • Uses custom color schemes for pocket visualization
def get_protein_data_object(self,
protein: Protein)
Expand source code
def get_protein_data_object(self, protein: Protein):
    """
    Creates a dictionary containing protein data information.

    Args:
        protein (Protein): A Protein object containing block type and content.

    Returns:
        dict: A dictionary with two keys:
            - 'extension': The block type of the protein
            - 'content': The block content of the protein
    """
    return {"extension": protein.block_type, "content": protein.block_content}

Creates a dictionary containing protein data information.

Args

protein : Protein
A Protein object containing block type and content.

Returns

dict
A dictionary with two keys: - 'extension': The block type of the protein - 'content': The block content of the protein
def pocket_finder_request(self,
logger: Logger,
protein_data)
Expand source code
def pocket_finder_request(self, logger: Logger, protein_data):
    """
    Sends a request to find pockets in a protein structure.

    This method communicates with a pocket finding service by sending protein data and processing
    the response to identify potential binding pockets in the protein structure.

    Args:
        logger (Logger): Logger object to track the execution flow and record events
        protein_data: Protein structure data to analyze for pockets

    Returns:
        tuple: A tuple containing:
            - data: The processed pocket finder results if successful, None otherwise
            - success (bool): True if the request was successful, False otherwise

    Raises:
        May raise exceptions related to HTTP requests or JSON processing

    Notes:
        - Handles 503 errors specifically for worker availability issues
        - Uses both console logging and provided logger for different logging purposes
        - Maintains logging depth for hierarchical logging structure
    """
    console_logger = Logger("INFO", None)

    logger.log_info("PocketFinder Request - Preparing pocket finder request.")
    logger.add_depth()

    body = {"protein_data": protein_data}

    data, success = None, False
    try:
        logger.log_info("PocketFinder Request - Sending pocket finder request.")
        response = self.post_request(
            endpoint="pocket_finder",
            logger=logger,
            data=body,
        )

        logger.log_info("PocketFinder Request - Received pocket finder response.")
        logger.sub_depth()

        data, success = response.json(), True
    except Exception as e:
        if response.status_code == 503:
            console_logger.log_error(WARNING_NUMBER_OF_AVAILABLE_WORKERS)
        else:
            console_logger.log_warning(WARNING_MESSAGE)

        logger.log_error(
            f"PocketFinder Request - Unexpected status code: {response.status_code}. \nMessage: {response.text}"
        )
        logger.sub_depth()

        data, success = None, False

    return data, success

Sends a request to find pockets in a protein structure.

This method communicates with a pocket finding service by sending protein data and processing the response to identify potential binding pockets in the protein structure.

Args

logger : Logger
Logger object to track the execution flow and record events
protein_data
Protein structure data to analyze for pockets

Returns

tuple
A tuple containing: - data: The processed pocket finder results if successful, None otherwise - success (bool): True if the request was successful, False otherwise

Raises

May raise exceptions related to HTTP requests or JSON processing

Notes

  • Handles 503 errors specifically for worker availability issues
  • Uses both console logging and provided logger for different logging purposes
  • Maintains logging depth for hierarchical logging structure
def preprocess(self, protein, results_dir='')
Expand source code
def preprocess(self, protein, results_dir=""):
    """
    Preprocesses the input protein data for pocket finding.

    This method validates the results directory and creates it if necessary, and ensures
    the input protein is in the correct format (either a Protein object or a valid protein file path).

    Args:
        protein (Union[Protein, str]): Either a Protein object or a path to a protein file.
        results_dir (str, optional): Directory path for storing results. Defaults to empty string.

    Returns:
        Protein: A valid Protein object ready for pocket finding analysis.

    Raises:
        Exception: If results_dir is non-empty.
        Exception: If protein is neither a Protein object nor a valid file path.
        Exception: If protein file cannot be processed into a Protein object.
    """
    if len(results_dir) > 0:
        if os.path.exists(results_dir) and len(os.listdir(results_dir)) > 0:
            raise Exception("Non empty directory is given as results directory.")

        if not os.path.exists(results_dir):
            os.makedirs(results_dir)

    if not isinstance(protein, Protein):
        if not isinstance(protein, str):
            raise Exception("Find pockets can only be performed either on protein object or protein file.")

        try:
            protein = Protein(file_path=protein)
        except Exception as e:
            raise Exception(f"Failed to create protein object from the given file. \n Exception: {str(e)}")

    return protein

Preprocesses the input protein data for pocket finding.

This method validates the results directory and creates it if necessary, and ensures the input protein is in the correct format (either a Protein object or a valid protein file path).

Args

protein : Union[Protein, str]
Either a Protein object or a path to a protein file.
results_dir : str, optional
Directory path for storing results. Defaults to empty string.

Returns

Protein
A valid Protein object ready for pocket finding analysis.

Raises

Exception
If results_dir is non-empty.
Exception
If protein is neither a Protein object nor a valid file path.
Exception
If protein file cannot be processed into a Protein object.
def process_pocket_finder_request(self,
logger: Logger,
protein: Protein)
Expand source code
def process_pocket_finder_request(self, logger: Logger, protein: Protein):
    """Process a request to find pockets in a protein structure.

    This method handles the complete workflow of pocket finding, including making the initial request
    and waiting for the response from the pocket finder service.

    Args:
        logger (Logger): Logger instance to track the process and record any issues
        protein (Protein): Protein object containing the structure to analyze

    Returns:
        tuple: A tuple containing:
            - response: The server response containing pocket information if successful, None otherwise
            - completed (bool): Flag indicating if the process completed successfully

    Raises:
        Exception: If there are server-side errors (HTTP 503 for no available workers, or other server issues)

    Note:
        The method handles server errors internally, logging appropriate warnings and errors.
        It uses a ProgressView to track the request progress and will return (None, False)
        if any errors occur during processing.
    """
    console_logger = Logger("INFO", None)

    response, completed = None, False

    logger.log_info("PocketFinder: Starting pocket finder.")
    logger.add_depth()

    protein_data = self.get_protein_data_object(protein)

    progress_view = ProgressView()

    try:
        request_id, completed = self.pocket_finder_request(logger, protein_data)
        if completed:
            response, completed = self.wait_for_pocket_finder_response(logger, request_id, progress_view)
            if not completed:
                console_logger.log_warning(WARNING_MESSAGE_SERVER)
                logger.log_error(
                    "PocketFinder: Something went wrong. For more information please check backend service logs."
                )
    except Exception as e:
        if response.status_code == 503:
            console_logger.log_error(WARNING_NUMBER_OF_AVAILABLE_WORKERS)
        else:
            console_logger.log_warning(WARNING_MESSAGE_SERVER)

        logger.log_error(f"Something went wrong on server side - {str(e)}")
        response, completed = None, False

    return response, completed

Process a request to find pockets in a protein structure.

This method handles the complete workflow of pocket finding, including making the initial request and waiting for the response from the pocket finder service.

Args

logger : Logger
Logger instance to track the process and record any issues
protein : Protein
Protein object containing the structure to analyze

Returns

tuple
A tuple containing: - response: The server response containing pocket information if successful, None otherwise - completed (bool): Flag indicating if the process completed successfully

Raises

Exception
If there are server-side errors (HTTP 503 for no available workers, or other server issues)

Note

The method handles server errors internally, logging appropriate warnings and errors. It uses a ProgressView to track the request progress and will return (None, False) if any errors occur during processing.

def update_progress_bar(self,
logger: Logger,
progress: ProgressView,
body)
Expand source code
def update_progress_bar(self, logger: Logger, progress: ProgressView, body):
    """
    Updates the progress bar based on the received response body.

    Args:
        logger (Logger): Logger instance to log progress information
        progress (ProgressView): Progress bar view instance to be updated
        body (dict): Response body containing progress information with keys:
            - description (str): Description of current progress state
            - percentage (int/float): Percentage completion value

    Updates the progress bar's description and percentage based on the body content.
    When "Completed" status is received, updates with completion values.
    For ongoing progress, only updates description if different from current,
    and logs when progress increases.
    """
    if body.get("description", "") == "Completed":
        progress.update(new_desc=body["description"], new_percentage=body["percentage"])
        progress.display()
    else:
        new_percentage = int(body["percentage"])
        new_description = body["description"]

        if new_description == progress.description:
            new_description = None

        if new_percentage > progress.bar.n:
            logger.log_info("PocketFinder progress achieved.")

        progress.update(new_desc=new_description, new_percentage=new_percentage)
        progress.display()

Updates the progress bar based on the received response body.

Args

logger : Logger
Logger instance to log progress information
progress : ProgressView
Progress bar view instance to be updated
body : dict
Response body containing progress information with keys: - description (str): Description of current progress state - percentage (int/float): Percentage completion value

Updates the progress bar's description and percentage based on the body content. When "Completed" status is received, updates with completion values. For ongoing progress, only updates description if different from current, and logs when progress increases.

def wait_for_pocket_finder_response(self,
logger: Logger,
request_id: str,
progress_view: ProgressView,
interval=0.5)
Expand source code
def wait_for_pocket_finder_response(self, logger: Logger, request_id: str, progress_view: ProgressView, interval=0.5):
    """
    Waits for and handles responses from the pocket finder service with progress updates.

    This method continuously polls the pocket finder service until a final response is received,
    updating a progress bar during the process.

    Args:
        logger (Logger): Logger instance for tracking the process
        request_id (str): Unique identifier for the pocket finder request
        progress_view (ProgressView): Progress bar view instance to update status
        interval (float, optional): Polling interval in seconds. Defaults to 0.5

    Returns:
        tuple: A tuple containing:
            - data (dict|None): Response data if successful, None otherwise
            - success (bool): True if request completed successfully, False otherwise

    Status Codes:
        - 200: Request completed successfully
        - 202: Request is still processing
        - 429: Rate limit exceeded
        - Other: Unexpected error

    Note:
        The method will update the progress bar during processing and close it upon completion.
        It also handles logging at different levels based on the response status.
    """
    logger.add_depth()

    console_logger = Logger("INFO", None)
    logger.log_info("Waiting for pocket_finder service.")

    data, success = None, False
    while True:
        time.sleep(interval)
        response = self.get_request(endpoint=f"pocket_finder/{request_id}", logger=logger)

        match response.status_code:
            case 200:
                logger.log_info(
                    f"PocketFinder request successfully completed: status_code = {response.status_code}"
                )

                data, success = response.json(), True
                break
            case 202:
                body = response.json()
                self.update_progress_bar(logger, progress_view, body)
            case 429:
                console_logger.log_warning(WARNING_MESSAGE)
                logger.log_error(
                    f"PocketFinder request failed: status_code = {response.status_code}, message: {response.text}"
                )

                data, success = None, False
                break
            case _:
                console_logger.log_warning(WARNING_MESSAGE)
                logger.log_error("PocketFinder request unexpected status code.")

                data, success = None, False
                break

    if success:
        self.update_progress_bar(logger, progress_view, {"description": "Completed", "percentage": 100})
        progress_view.close()

        logger.log_info("PocketFinder completed: Proceeding to the next step")
    logger.sub_depth()

    return data, success

Waits for and handles responses from the pocket finder service with progress updates.

This method continuously polls the pocket finder service until a final response is received, updating a progress bar during the process.

Args

logger : Logger
Logger instance for tracking the process
request_id : str
Unique identifier for the pocket finder request
progress_view : ProgressView
Progress bar view instance to update status
interval : float, optional
Polling interval in seconds. Defaults to 0.5

Returns

tuple
A tuple containing: - data (dict|None): Response data if successful, None otherwise - success (bool): True if request completed successfully, False otherwise

Status Codes: - 200: Request completed successfully - 202: Request is still processing - 429: Rate limit exceeded - Other: Unexpected error

Note

The method will update the progress bar during processing and close it upon completion. It also handles logging at different levels based on the response status.

Inherited members