Source code for binp.action
from dataclasses import dataclass
from logging import getLogger
from typing import List, Callable, Awaitable, Optional, Dict
from pydantic.main import BaseModel
@dataclass
class ActionHandler:
name: str
description: str
handler: Callable[[], Awaitable]
async def __call__(self):
return await self.handler()
class ActionInfo(BaseModel):
name: str
description: str
[docs]class Action:
"""
Expose user-defined action as 'button' in UI
Expose async function as button to ui.
It will not be automatically journaled: it's up to you
add ``@binp.journal`` annotation or not.
:Example:
.. code-block:: python
from binp import BINP
from asyncio import sleep
binp = BINP()
@binp.action
async def invoke():
'''
Do something
'''
await sleep(3) # emulate some work
print("done")
By default, action will be exposed with name equal to fully-qualified
function name and description from doc-string (if exists).
Exposed name could by optionally defined manually.
.. code-block:: python
from binp import BINP
from asyncio import sleep
binp = BINP()
@binp.action(name='Do Something', description='Emulate some heavy work')
async def invoke():
await sleep(3)
print("done")
:Conflicts:
Actions are indexed by name. If multiple actions defined with the same name - the latest one will be used.
"""
def __init__(self):
self.__actions: Dict[str, ActionHandler] = {}
def __call__(self, func: Optional[Callable[[], Awaitable]] = None, *, name: Optional[str] = None,
description: Optional[str] = None):
"""
Decorator that expose function as an action in UI (ex: button)
"""
def trace_operation(fn: Callable[[], Awaitable]):
nonlocal name
nonlocal description
if name is None:
name = fn.__qualname__
if description is None:
description = "\n".join(line.strip() for line in (fn.__doc__ or '').splitlines()).strip()
if name in self.__actions:
old = self.__actions[name]
getLogger(self.__class__.__qualname__).warning("redefining UI action %r: %s => %s", name,
old.handler.__qualname__, fn.__qualname__)
self.__actions[name] = ActionHandler(name=name, description=description, handler=fn)
return fn
if func is None:
return trace_operation
return trace_operation(func)
[docs] async def invoke(self, name: str) -> bool:
"""
Invoke action by name or ignore. If handler will raise an error, the error will NOT be suppressed.
:param name: action name
:return: true if action invoked
"""
handler = self.__actions.get(name)
if handler is None:
getLogger(self.__class__.__qualname__).warning("attempt to invoke unknown action %r", name)
return False
await handler()
return True
@property
def actions(self) -> List[ActionInfo]:
"""
Copy of list of defined actions prepared for serialization.
"""
return [ActionInfo(name=x.name, description=x.description) for x in self.__actions.values()]