# 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
from typing import TypeGuard
from typing import Any, Callable, Optional

# %% ../nbs/01_core.ipynb 5
from chck import iscall, isnone, notnone
from nlit import HAS, __DOC__, __NAME__, __QUALNAME__, __MODULE__, __ANNOTATIONS__

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

# %% ../nbs/01_core.ipynb 8
def hasr(
    obj: object, attr: str, check: bool = True, 
    guard: Optional[Callable] = notnone, default = None, 
    otype: Optional[Callable] = 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.

    otype : Optional[Callable], optional
        The type of the object to check for, by default None.
        If specified, the function will check if the object is of the specified type 
        before checking for the attribute. If this fails, the function will return `default`.

    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`.
    '''
    if all((check, notnone(otype), iscall(otype))) and not otype(obj): return default
    has = hasattr(obj, attr)
    if all((has, check, iscall(guard))):
        try: return has and guard(getattr(obj, attr, default))
        except: False
    return has

# %% ../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.

    otype : Optional[Callable], optional
        The type of the object to check for, by default None.
        If specified, the function will check if the object is of the specified type 
        before checking for the attribute. If this fails, the function will return `default`.

    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}.

            otype : Optional[Callable], optional
                The type of the object to check for, by default None.
                If specified, the function will check if the object is of the specified type 
                before checking for the attribute. If this fails, the function will return `default`.
                
            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, otype: Optional[Callable] = None,
        ) -> TypeGuard[object]:
            return hasr(obj, attr, check=check, guard=guard, default=default, otype=otype)
        wrap.__doc__ = doc
        wrap = name(wrap, attr)        
        return wrap
    return deco
