# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_core.ipynb.

# %% auto 0
__all__ = ['hasr', 'qual', 'name', 'make']

# %% ../nbs/01_core.ipynb 4
from functools import wraps, update_wrapper
from typing import TypeGuard
from typing import Any, Callable, Optional, ParamSpec, TypeVar, overload, get_type_hints, get_overloads

# %% ../nbs/01_core.ipynb 5
from chck import isnone, notnone

# %% ../nbs/01_core.ipynb 7
from .atyp import P, R

# %% ../nbs/01_core.ipynb 8
def hasr(
    obj: object, attr: str, check: bool = True, 
    guard: Optional[Callable] = notnone, default = None
) -> TypeGuard[object]:
    '''Check if an object has a specified attribute with an optional value check.

    This function tests if an object has an attribute with the option to validate 
    the attribute's value using a guard function. The guard function can be any callable 
    that takes a single argument and returns a boolean. If the attribute is not present, 
    a default value can be considered for the guard check.

    Parameters
    ----------
    obj : object
        The object to check for the attribute.

    attr : str
        The name of the attribute to check for.

    check : bool, optional
        If True, the function performs a guard check on the attribute's value, by default True.

    guard : Optional[Callable], default: `notnone`.
        The guard function used to validate the attribute's value, by default notnone.

    default : Any, optional
        The default value to use if the attribute is not found, by default None.

    Returns
    -------
    TypeGuard[object]
        Returns True if the object has the attribute (and passes the guard check if `check` is True),
        otherwise False.

    Examples
    --------
    >>> d = dict(a=1, b=2, c=3)
    >>> hasr(d, 'keys')
    True

    >>> hasr(d, 'missing', default=10)
    False

    See Also
    --------
    chck.notnone : A guard function that checks if a value is not None i.e. `val is not None`.
    '''
    has = hasattr(obj, attr)
    got = getattr(obj, attr, default)
    if check and callable(guard):
        try: return has and guard(got)
        except: False
    return has and notnone(got)

# %% ../nbs/01_core.ipynb 9
def qual(attr: str, base: str = 'has', prefix: str = '', suffix: str = '') -> str:
    '''Produce a '__qualname__' for a function based on an attribute name.'''
    if isnone(prefix): prefix = ''
    if isnone(suffix): suffix = ''
    return f'{prefix}{base}{attr}{suffix}'
    
def name(fn: Callable, attr: str, base: str = 'has', prefix: str = None, suffix: str = None) -> Callable:
    for a in ('__name__', '__qualname__'): setattr(fn, a, qual(attr, base, prefix, suffix))
    return fn

# %% ../nbs/01_core.ipynb 10
def make(
    attr: str, check: bool = True, 
    guard: Optional[Callable] = notnone, 
    default = None
) -> Callable[[R], R]:
    '''Create a decorator for generating attribute-checking functions.

    This decorator factory returns a decorator that, when applied to a function,
    creates a new function which checks if an object has a specified attribute (named `attr`).
    Additionally, it can validate the attribute's value using a guard function `guard`.
    The decorator updates the original function's docstring to include the new parameters.

    Parameters
    ----------
    attr : str
        The name of the attribute to check for in the decorated function.

    check : bool, default: True
        If True, the decorated function performs a guard check on the attribute's value.

    guard : Optional[Callable], default: notnone
        The guard function used to validate the attribute's value in the decorated function. 
        Defaults to `notnone`, which checks that the value is not None.

    default : Any, optional
        The default value to use if the attribute is not found in the decorated function.

    Returns
    -------
    Callable[[R], R]
        A decorator that, when applied to a function, creates a new attribute-checking function.
        This new function includes additional parameters and updated docstring.

    Examples
    --------
    >>> @make('keys', guard=callable)
    ... def haskeys(obj) -> TypeGuard[Callable]: 
    ...     """Checks if `obj.keys` is a callable"""

    >>> d = dict(a=1)
    >>> haskeys(d), haskeys([])
    (True, False)
    '''
    def deco(fn: Callable[..., TypeGuard[object]]):
        fndoc = getattr(fn, '__doc__', None)
        if isnone(fndoc): fndoc = f'Checks if an object has {attr!r} i.e. `obj.{attr}`.'
        doc = f'''
            {fndoc}

            Parameters
            ----------
            obj : object
                The object to check for {attr!r} i.e. `obj.{attr}`.
                
            check : bool, default: {check}
                If True, the function performs a typeguard check on the `obj.{attr}` value.
        
            guard : Optional[Callable], default: `notnone`.
                The guard function used to validate `obj.{attr}`'s value if `check == True`.
        
            default : Any, optional
                The default value to use if `obj` does not have {attr!r}..
        
            Returns
            -------
            TypeGuard[object]
                Returns True if the object has {attr!r} (i.e. `obj.{attr}` and passes the guard check if `check` is True),
                otherwise False.
        
            Examples
            --------
            >>> d = dict(a=1, b=2, c=3)
            >>> hasr(d, 'keys')
            True
        
            >>> hasr(d, 'missing', default=10)
            False
        
            See Also
            --------
            chck.notnone : A guard function that checks if a value is not None i.e. `val is not None`.
            '''
        @wraps(fn, assigned=('__module__', '__name__', '__qualname__', ))
        def wrap(
            obj: object, check: bool = check, 
            guard: Optional[Callable] = guard, 
            default = default
        ) -> TypeGuard[object]:
            return hasr(obj, attr, check=check, guard=guard, default=default)
        wrap.__doc__ = doc
        wrap = name(wrap, attr)        
        return wrap
    return deco
