Source code for finalynx.portfolio.render

import inspect
from typing import Any
from typing import Callable
from typing import Dict
from typing import Optional


[docs]class Render: """Abstract class used to transform a render format to the output.""" MAX_ALIAS_DEPTH = 10 """Maximum recursion depth when replacing aliases to prevent infinite loops.""" def __init__( self, aliases: Optional[Dict[str, str]] = None, agents: Optional[Dict[str, Callable[..., str]]] = None ) -> None: """Abstract class used by subclasses to render themselves as string with a customizable format. This class offers a `render` method which takes a format as input and outputs the corresponding string. See [formatting guidelines](https://finalynx.readthedocs.io/en/latest/tutorials/customization.html) for more information. :param aliases: A key:value dictionary, defaults to empty. Specified keywords will be recursively transformed into the value until all keywords don't appear in the text. :param agents: A key:value dictionary, defaults to empty. Keywords will be replaced by what the method given as value will output. """ self._render_aliases: Dict[str, str] = aliases if aliases else {} self._render_agents: Dict[str, Callable[..., str]] = agents if agents else {}
[docs] def render(self, output_format: str = "[console]", **args: Dict[str, Any]) -> str: """Render the instance as a string by following the output format. See [formatting guidelines](https://finalynx.readthedocs.io/en/latest/tutorials/customization.html) for more information. :returns: A string representation of the instance based on the output format. """ # TODO automatically add a space between components instead of hardcoding everywhere? # Utility method used below def safe_len(obj: Any) -> int: return len(obj) if obj else 0 # Recursively replace alias keywords by their correspoding values output_format = self._apply_aliases(output_format) # Call the corresponding methods called "agents" for each keyword for keyword, agent in self._render_agents.items(): # Filter the arguments to what this agent takes as input parameters argspec = inspect.getfullargspec(agent) kwargs = argspec.args[safe_len(argspec.args) - safe_len(argspec.defaults) :] if argspec else [] # noqa filtered_args = {k: v for k, v in args.items() if k in kwargs} # Call the method with the arguments provided by the user (filtered for this method above) output_format = output_format.replace(f"[{keyword}]", agent(**filtered_args)) return output_format
[docs] def _apply_aliases(self, output_format: str) -> str: """Internal method that recursively replaces alias keywords into their values specified in `self._render_aliases`. :returns: A string of the output format with aliases replaced. """ if not output_format: raise ValueError("Render format cannot be empty.") old_format, iterations = "", 0 while old_format is not output_format and iterations < Render.MAX_ALIAS_DEPTH: old_format = output_format for alias, value in self._render_aliases.items(): output_format = output_format.replace(alias, value) iterations += 1 if iterations == Render.MAX_ALIAS_DEPTH: raise ValueError(f"Stopped infinite loop while applying render aliases, {output_format=}") return output_format