'''Decorators for modifying functions'''
__author__ = 'Timotej Bernat'
__email__ = 'timotej.bernat@colorado.edu'
from typing import Callable, Concatenate, Iterable, Iterator, Optional, ParamSpec, TypeVar, Union
T = TypeVar('T')
Params = ParamSpec('Params')
from inspect import signature, Parameter
from functools import wraps, partial
from copy import deepcopy
from .meta import extend_to_methods
from .signatures import insert_parameter_at_index, get_index_after_positionals
from ..fileutils.pathutils import allow_string_paths, allow_pathlib_paths # imported for namespace backwards-compatibility
[docs]
@extend_to_methods
def optional_in_place(funct : Callable[[Concatenate[object, Params]], None]) -> Callable[[Concatenate[object, Params]], Optional[object]]:
'''Decorator function for allowing in-place (writeable) functions which modify object attributes
to be not performed in-place (i.e. read-only), specified by a boolean flag'''
# TODO : add assertion that the wrapped function has at least one arg AND that the first arg is of the desired (limited) type
old_sig = signature(funct)
@wraps(funct) # for preserving docstring and type annotations / signatures
def in_place_wrapper(obj : object, *args : Params.args, in_place : bool=False, **kwargs : Params.kwargs) -> Optional[object]: # read-only by default
'''If not in-place, create a clone on which the method is executed''' # NOTE : old_sig.bind screws up arg passing
if in_place:
funct(obj, *args, **kwargs) # default call to writeable method - implicitly returns None
else:
copy_obj = deepcopy(obj) # clone object to avoid modifying original
funct(copy_obj, *args, **kwargs)
return copy_obj # return the new object
# ADD IN-PLACE PARAMETER TO FUNCTION SIGNATURE
new_sig = insert_parameter_at_index(
old_sig,
new_param=Parameter(
name='in_place',
default=False,
annotation=bool,
kind=Parameter.KEYWORD_ONLY
),
index=get_index_after_positionals(old_sig)
)
# ANNOTATE MODIFED OBJECT RETURN TYPE AS Optional[<type>]
mod_type = tuple(old_sig.parameters.values())[0].annotation # get annotation of the object being modified
in_place_wrapper.__signature__ = new_sig.replace(return_annotation=Optional[mod_type]) # replace signature with appropriate modifications
return in_place_wrapper
# TODO : implement support for extend_to_methods (current mechanism is broken by additional deocrator parameters)