"""
domonic.decorators
====================================
"""
import functools
import inspect
import io
import logging
import warnings
from typing import Callable
def _invoke_before(before, function):
result = before()
params = list(inspect.signature(function).parameters.values())
can_receive_value = any(
param.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD)
for param in params
) or any(param.kind == inspect.Parameter.VAR_POSITIONAL for param in params)
return result, can_receive_value
[docs]
def el(element="div", string: bool = False):
"""[wraps the results of a function in an element]"""
return_string = string or isinstance(element, str)
if isinstance(element, str):
from domonic.dom import Document
element = Document.createElement(element).__class__
def decorator(function):
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
if result is None:
result = ""
if return_string is False:
return element(result)
else:
return str(element(result))
return wrapper
return decorator
# @el(div)
# @el(span)
# def doctype(doctype):
# """
# @doctype('html')
# @doctype('xhtml')
# @doctype('xml')
# """
# def decorator(f):
# @wraps(f)
# def wrapper(*args, **kwargs):
# return f(*args, **kwargs)
# return wrapper
# return decorator
[docs]
def called(before=None, error: Callable[[Exception], None] = None):
"""Call a hook before a function runs, with light support for legacy callback sugar."""
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
if before:
_, _ = _invoke_before(before, f)
try:
return f(*args, **kwargs)
except Exception as e:
if error:
error(e)
raise
if before is None:
wrapper()
return wrapper
params = inspect.signature(f).parameters
if params:
try:
result, can_receive_value = _invoke_before(before, f)
if can_receive_value:
f(result)
else:
f()
except Exception as e:
if error:
error(e)
else:
raise
return wrapper
return decorator
iife = called # pass None for an iife
# def static(endpoint, update="11101"):
# '''
# render the endpoint to a cron timestamp. when user vists that function.
# it will load the rendered version instead of executing the function.
# '''
# def dont_do_it(f):
# return None
# return dont_do_it
# https://www.python.org/dev/peps/pep-0318/
# https://stackoverflow.com/questions/15299878/how-to-use-python-decorators-to-check-function-arguments
def accepts(*types):
def check_accepts(f):
assert len(types) == f.__code__.co_argcount
def new_f(*args, **kwds):
for (a, t) in zip(args, types):
assert isinstance(a, t), "arg %r does not match %s" % (a, t)
return f(*args, **kwds)
new_f.__name__ = f.__name__
return new_f
return check_accepts
# @accepts(int)
# CACHED = {}
# def memoize(f):
# """
# @memoize
# def fib(n):
# if n in (0, 1):
# return n
# return fib(n - 1) + fib(n - 2)
# """
# @functools.wraps(f)
# def new_f(*args):
# if args in CACHED:
# return CACHED[args]
# else:
# result = f(*args)
# CACHED[args] = result
# return result
# return new_f
# def requires_websocket(f): # decorate all functions that require websockets to warn user
[docs]
def silence(*args, **kwargs):
"""Suppress stdout for the wrapped function.
Supports both ``@silence`` and ``@silence()``.
"""
def decorator(f):
@functools.wraps(f)
def wrapper(*f_args, **f_kwargs):
import contextlib
with contextlib.redirect_stdout(io.StringIO()):
return f(*f_args, **f_kwargs)
return wrapper
if args and callable(args[0]) and len(args) == 1 and not kwargs:
return decorator(args[0])
return decorator
[docs]
def check(f):
"""Log entry and exit around a function call."""
logger = logging.getLogger(__name__)
@functools.wraps(f)
def new_f(*args, **kwargs):
logger.info("Entering %s", f.__name__)
result = f(*args, **kwargs)
logger.info("Exited %s", f.__name__)
return result
return new_f
[docs]
def log(logger, level="info"):
"""@log(logging.getLogger('main'), level='warning')"""
def log_decorator(fn):
@functools.wraps(fn)
def wrapper(*a, **kwa):
getattr(logger, level)(fn.__name__)
return fn(*a, **kwa)
return wrapper
return log_decorator
[docs]
def instead(somethingelse):
"""Return a fallback value when the wrapped function raises."""
def decorator(f):
@functools.wraps(f)
def new_f(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception:
return somethingelse
return new_f
return decorator
[docs]
def deprecated(func):
"""
marks a function as deprecated.
"""
@functools.wraps(func)
def new_func(*args, **kwargs):
raise Warning("Call to deprecated function {}.".format(func.__name__))
return func(*args, **kwargs)
return new_func
[docs]
def as_json(func):
"""decorate any function to return json instead of a python obj
note - used by JSON.py
"""
def JSON_decorator(*args, **kwargs):
import json
return json.dumps(func(*args, **kwargs))
return JSON_decorator
'''
def aka(names):
""" @aka(*mylist) """
def aka_decorator(fn):
@functools.wraps(fn)
def wrapper(*a, **kwa):
return fn(*a, **kwa)
return wrapper
return log_decorator
'''
'''
import functools
# def DIV(func):
# @functools.wraps(func)
# def wrapper(*args, **kwargs):
# result = func(*args, **kwargs)
# return div(result)
# return wrapper
# Create dynamic decorators for each tag
def create_html_decorator(tag):
def decorator(func=None, **kwargs):
# If no function is provided, we have been called with keyword arguments
if func is None:
return functools.partial(decorator, **kwargs)
# Convert underscores in attributes to valid HTML (e.g., _class to class)
updated_kwargs = {key.lstrip('_'): value for key, value in kwargs.items()}
# Define the wrapper function that is called when the decorated function is called
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Call the original function to get the content
content = func(*args, **kwargs)
# Generate attributes for the HTML tag
attrs = ''
for key, value in updated_kwargs.items():
attrs += f' {key}="{value}"'
# Return the HTML wrapped content
return f"<{tag}{attrs}>{content}</{tag}>"
return wrapper
return decorator
# from domonic.html import html_tags -- circular reference
# Dynamically create decorators for all HTML tags
for tag in html_tags:
globals()[tag.upper()] = create_html_decorator(tag)
# @DIV(_id="cool", _class"awesome")
# @SPAN
# def somecontent():
# return 'content only'
'''