Source code for aws_resource_search.items.detail_item

# -*- coding: utf-8 -*-

"""
See :class:`DetailItem`.
"""

import typing as T
import dataclasses
import contextlib

import zelfred.api as zf

try:
    import pyperclip
except ImportError:  # pragma: no cover
    pass

from ..terminal import ShortcutEnum, format_key_value
from ..compat import TypedDict
from .base_item import BaseArsItem, T_ARS_ITEM
from .exception_item import ExceptionItem

if T.TYPE_CHECKING:  # pragma: no cover
    from ..documents.resource_document import ResourceDocument
    from ..ars_def import ARS


[docs]class T_DETAIL_ITEM_VARIABLES(TypedDict): """ .. note:: Copy is more common than url in detail item. """ copy: T.Optional[str] url: T.Optional[str]
[docs]@dataclasses.dataclass class DetailItem(BaseArsItem): """ Represent a detail information of an AWS resource in the dropdown menu. **Why this class** 1. We need to write logic to create ``Item`` in the ``aws_resource_search.res.${aws_service}.py`` source code. This class provides utility methods to make the code clean and descriptive. 2. An ``Item`` usually comes with user action. We need to store the user action argument in ``variables`` and then implement the ``xyz_handler`` function to handle the user action. In this project, we have a very clear mind of user action patterns. This class provides utility methods to enable pre-defined user action smartly based on the input, so we no longer need to implement ``variables`` logics and ``xyz_handler`` functions in most cases. """ variables: T_DETAIL_ITEM_VARIABLES = dataclasses.field(default_factory=dict)
[docs] @classmethod def new( cls, title: str, subtitle: T.Optional[str] = None, copy: T.Optional[str] = None, url: T.Optional[str] = None, uid: T.Optional[str] = None, arg: T.Optional[str] = None, autocomplete: T.Optional[str] = None, ): """ The factory method to create a new ``ArsBaseItem`` instance. :param title: first line of the item. It has a checkbox in front of it to indicate whether it is selected. :param subtitle: second line of the item. :param copy: the text to copy to clipboard. :param url: the url to open in browser. :param uid: item unique id. The UI use this to distinguish different items. :param arg: argument that will be passed to the action. :param autocomplete: the text that will be filled in the input box when user hits ``TAB`` key. """ kwargs = dict( title=title, arg=arg, autocomplete=autocomplete, variables={"url": url, "copy": copy}, ) if uid: kwargs["uid"] = uid # fmt: off if subtitle is None: if url is not None and copy is not None: kwargs[ "subtitle"] = f"🌐 {ShortcutEnum.ENTER} to open url, 📋 {ShortcutEnum.CTRL_A} to copy value, 🔗 {ShortcutEnum.CTRL_U} to copy url." elif url is not None: kwargs[ "subtitle"] = f"🌐 {ShortcutEnum.ENTER} to open url, 📋 {ShortcutEnum.CTRL_A} or 🔗 {ShortcutEnum.CTRL_U} to copy url." elif copy is not None: kwargs["subtitle"] = f"📋 {ShortcutEnum.CTRL_A} or 🔗 {ShortcutEnum.CTRL_U} to copy value" elif autocomplete: kwargs["subtitle"] = f"{ShortcutEnum.TAB} to auto complete." else: kwargs["subtitle"] = "No subtitle" # fmt: on else: kwargs["subtitle"] = subtitle return cls(**kwargs)
[docs] def enter_handler(self, ui: "zf.UI"): # pragma: no cover """ Behavior: - If we have a URL, it will be opened in the browser or print the url to the terminal if the terminal device can not do so. - If not, the Enter key will function similarly to the Tab key, it will autocomplete. """ if self.variables.get("url"): self.open_url_or_print(ui, self.variables["url"]) elif self.autocomplete: ui.line_editor.clear_line() ui.line_editor.enter_text(self.autocomplete) else: pass
[docs] def ctrl_a_handler(self, ui: "zf.UI"): # pragma: no cover """ Behavior: - If we have a copy text, it will copy the text to clipboard or print the text to the terminal if the terminal device can not do so. - If we don't have copy text but have url, then treat url as copy text. """ if self.variables.get("copy"): self.copy_or_print(ui, self.variables["copy"]) elif self.variables.get("url"): self.copy_or_print(ui, self.variables["url"]) else: pass
[docs] def ctrl_u_handler(self, ui: "zf.UI"): # pragma: no cover """ Behavior: - If we have an url, it will copy the url to clipboard or print the url to the terminal if the terminal device can not do so. - If we don't have URL but have copy text, then treat copy text as url. """ if self.variables.get("url"): self.copy_or_print(ui, self.variables["url"]) elif self.variables.get("copy"): self.copy_or_print(ui, self.variables["copy"]) else: pass
[docs] @classmethod def from_detail( cls, key: str, value: T.Any, key_text: T.Optional[str] = None, value_text: T.Optional[str] = None, url: T.Optional[str] = None, copy: T.Optional[str] = None, uid: T.Optional[str] = None, ): """ A utility method to create :class`DetailItem` from structured detail information. This method will be used in :class:`aws_resource_search.searcher.base_document.BaseDocument.get_details` method. A 'detail' is just a simple key value pair. For example, for S3 bucket. the 'bucket_name' is the key and the 'your-bucket-name' is the value. :param key: key of the detail, it's the original data :param value: value of the detail, it's the original data. This is also the text that will be copied to clipboard when user tap `Ctrl + A``. :param key_text: the ``${key} = ${value}`` text to display in the UI. if not provided, use ``key`` as the text. :param value_text: the ``${key} = ${value}`` text to display in the UI. if not provided, use ``value`` as the text. :param url: if specified, user can hit Enter key to open the url in browser, and Ctrl + U to copy it. :param uid: this is for uid """ if key_text is None: key_text = key if value_text is None: value_text = value if copy is None: copy = value return cls.new( title=format_key_value(key_text, value_text), uid=uid, copy=str(copy), url=url, )
[docs] @classmethod def from_env_vars( cls, env_vars: T.Dict[str, str], url: T.Optional[str] = None, ) -> T.List["DetailItem"]: """ A utility method to create many :class`DetailItem` from environment variable key value pairs. """ if env_vars: return [ cls.new( title=f"🎯 env var: {format_key_value(k, v)}", uid=f"env_var-{k}", copy=v, url=url, ) for k, v in env_vars.items() ] else: return [ cls.new( title=f"🏷 env var: 🔴 No environment variable found.", uid=f"no-environment-variable-found", url=url, ) ]
[docs] @classmethod def from_tags( cls, tags: T.Dict[str, str], url: T.Optional[str] = None, ) -> T.List["DetailItem"]: """ Create MANY :class:`DetailItem` from AWS resource tag key value pairs. """ if tags: return [ cls.new( title=f"🏷 tag: {format_key_value(k, v)}", uid=f"tag {k}", copy=v, url=url, ) for k, v in tags.items() ] else: return [ cls.new( title=f"🏷 tag: 🔴 No tag found.", uid=f"no-tag-found", url=url, ) ]
[docs] @classmethod def get_initial_detail_items( cls, doc: "ResourceDocument", ars: "ARS", arn_key: str = "arn", ) -> T.List["DetailItem"]: # pragma: no cover """ Most AWS resource detail should have one ARN item that user can tap "Ctrl A" to copy and tap "Enter" to open url. Only a few AWS resource doesn't support ARN (for example, glue job run). .. note:: This method is to simplify the authoring of the :meth:`aws_resource_search.documents.resource_document.ResourceDocument.get_details` method. Usage example: >>> class S3BucketDocument(ResourceDocument): ... def get_details(self, ars: ARS): ... detail_items = DetailItem.get_initial_detail_items(self, ars) ... ... """ try: return [ DetailItem.from_detail( key=arn_key, value=doc.arn, url=doc.get_console_url(ars.aws_console), ), ] # the ResourceDocument.arn and ResourceDocument.get_console_url # may raise NotImplementedError except NotImplementedError: return []
[docs] @staticmethod @contextlib.contextmanager def error_handling(detail_items: T.List["T_ARS_ITEM"]): """ A context manager to add additional detail items to the list. It automatically captures exception and creates :class:`~aws_resource_search.items.exception_item.ExceptionItem` to explain what went wrong. Usage example: >>> class S3BucketDocument(ResourceDocument): ... def get_details(self, ars: ARS): ... detail_items = DetailItem.get_initial_detail_items(self, ars) ... with DetailItem.error_handling(detail_items): ... res = ars.bsm.s3_client.get_bucket_policy(...) ... detail_items.append(DetailItem.from_detail(...)) .. note:: This method is to simplify the authoring of the :meth:`aws_resource_search.documents.resource_document.ResourceDocument.get_details` method. """ try: yield None except Exception as e: detail_items.append(ExceptionItem.from_error(error=e))