# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/09_meta.ipynb.

# %% auto 0
__all__ = ['ABCSpecies', 'ABCDuck', 'DuckProtocolMeta', 'DuckModuleMeta']

# %% ../nbs/09_meta.ipynb 6
import sys, copy, itertools
from abc import ABCMeta, abstractmethod
from functools import partial
from inspect import isclass
from importlib import import_module
from importlib.util import (module_from_spec)
from importlib.machinery import ModuleSpec

# %% ../nbs/09_meta.ipynb 8
from types import ModuleType, NoneType, FunctionType
from typing import (Any, Self, Callable, TypeGuard, ClassVar, _ProtocolMeta)

# %% ../nbs/09_meta.ipynb 10
#| export

# %% ../nbs/09_meta.ipynb 12
#| export

# %% ../nbs/09_meta.ipynb 14
from quac.cons import (
    ANNOYING_ATTRS, MODULE_TYPE_ATTRS,
    _DUCK, ALL_ATTRS,
    __BASES__, __PROPS__, __MODULE__, __QUALNAME__, __ANNOTATIONS__,  
    __HUSH__, __DUCK__, __ALTS__, __DUCKMODULE__, __DUCKTYPES__,
    __GENERATE__, __DUCKTYPE__,
    CALL_INIT_ATTRS
)
from .type import T, O, P, Ts
from .solo import EmptyModule
from .prot import NotProtocol, OptProtocol
from .enum import Species, LoaderState
from .errs import DuckException, DuckNameError, DuckImportError, DuckAttributeError, DuckModuleNotFoundError
from .grds import isnone, notnone, isalias, isduck, isduckspec, isiter, iscls, isclstype
from .misc import find_last_spec, prepare_types_for_instancecheck, prepare_ducktypes, prepare_isinstance_types

# %% ../nbs/09_meta.ipynb 17
class ABCSpecies(ABCMeta):
    __species__: ClassVar[Species] = Species.REG
    '''The specific "species" of the protocol. Defaults to `REG` (regular).'''
        
    def __reg__(cls) -> Self:
        '''Set the "species" to `REG` (regular).'''
        cls.__species__ = Species.REG
        return cls
    
    def __not__(cls) -> Self:
        '''Set the "species" to `NOT` (negated).'''
        cls.__species__ = Species.NOT
        return cls
    
    def __opt__(cls) -> Self:
        '''Set the "species" to `OPT` (optional).'''
        cls.__species__ = Species.OPT
        return cls
    
    def __migrate__(cls, species: Species) -> Self:
        '''Migrate the protocol to a different "species".'''
        match species:
            case Species.REG: return cls.__reg__()
            case Species.NOT: return cls.__not__()
            case Species.OPT: return cls.__opt__()
            case _: return cls # NOTE: fail silently
        return cls
    
    def __hash__(self) -> int:
        return super().__hash__()

# %% ../nbs/09_meta.ipynb 19
class ABCDuck(_ProtocolMeta, ABCSpecies, type):
    __ducktype__: ClassVar[tuple[type[T], ...]] = tuple()
    '''The specific types that the protocol is duck-typed to.''';
    
    __expected__: ClassVar[dict[str, Any]] = dict()
    '''Dictionary of attributes and their expected values to check in the object.''';
    
    __required__: ClassVar[tuple[str, ...]] = tuple()
    '''Iterable of attribute names that the object must have.''';
    
    __allequal__: ClassVar[tuple[str, ...]] = tuple(ALL_ATTRS)
    '''Iterable of attributes to check if their values are all the same in an iterable object.''';

    __extrafns__: ClassVar[dict[str, Callable[[O, T], TypeGuard[T]]]] = dict()
    '''Mapping of attribute names to guard functions for additional checks.''';
    
    __usealias__: ClassVar[bool] = True
    '''Whether or not to use the `isalias` function for instance checking or `__guard__`.''';
    
    # __initiate__, __makefunc__
    __generate__: ClassVar[Callable[P, T] | T | None] = None
    '''A factory function or default value to use to initialize a valid isstance
    of the duck type.''';
    
    __areinrow__: ClassVar[bool] = False
    
    
    def __types__(cls) -> tuple[type, ...]:
        '''Return the types that the protocol is duck-typed to.'''
        curtypes = list(cls.__ducktype__)
        unducked = prepare_ducktypes(cls)
        # print('__types__', cls, cls.__name__, curtypes, unducked, cls.__areinrow__)
        cls.__ducktype__ = unducked
        return unducked
    
    def __typesq__(cls) -> tuple[type, ...]:
        '''Return the types that the protocol is duck-typed to, including NoneType.'''
        # print('__typesq__')
        return cls.__types__() + (NoneType, )
    
    def __usetypes__(cls) -> tuple[type, ...]:
        '''Return the types that the protocol is duck-typed and to use.'''
        # print('__usetypes__')
        if cls.__species__ == Species.OPT: return cls.__typesq__()
        return cls.__types__()
    
    def __guard__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Return a type guard for the protocol.'''
        # print('__guard__')
        return isinstance(inst, prepare_isinstance_types(cls.__types__()))
    
    def __guardq__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Return a type guard for the protocol, including NoneType.'''
        # print('__guardq__')
        return isinstance(inst, prepare_isinstance_types(cls.__typesq__()))
    
    def __alias__(cls, inst: Any) -> TypeGuard[type[T]]:
        isfn: FunctionType = partial(isalias, 
            attrs = cls.__expected__, 
            hasattrs = cls.__required__, 
            allattrs = cls.__allequal__, 
            mapattrs = cls.__extrafns__
        )
        # print('calling alias')
        match cls.__species__:
            case Species.REG: return isfn(inst, types=cls.__types__())
            case Species.NOT: return not isfn(inst, types=cls.__types__())
            case Species.OPT: return isfn(inst, types=cls.__typesq__())
        return isalias(
            inst, 
            types = cls.__types__(), 
            guards = (cls.__guard__, ),
            attrs = cls.__expected__, 
            hasattrs = cls.__required__, 
            allattrs = cls.__allequal__, 
            mapattrs = cls.__extrafns__
        )
    
    def __instancecheck__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Instance check for the protocol using custom `__guard__` method
        which enables duck-typing and inheritance.'''
        # print('calling __instancecheck__')
        if cls.__usealias__:  
            return cls.__alias__(inst)
        match cls.__species__:
            case Species.REG: return cls.__guard__(inst)
            case Species.NOT: return cls.__notinstance__(inst)
            case Species.OPT: return cls.__optinstance__(inst)
            case _: return cls.__guard__(inst)
        return cls.__guard__(inst)
    
    def __subclasscheck__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Instance check for the protocol using custom `__guard__` method
        which enables duck-typing and inheritance.'''
        # print('subclass check', cls, type(cls), inst, type(inst), )
        types = cls.__usetypes__()
        isin = inst in types
        # bases = []
        # for t in types:
        #     b = list(getattr(t, '__bases__', ()))
        #     bases.extend(b)
        # bases = tuple(list(filter(isclstype, bases)))
        
        match cls.__species__:
            case Species.REG: return (isin or issubclass(inst, types))
            case Species.NOT: return not (isin or issubclass(inst, types))
            case Species.OPT: return (isin or issubclass(inst, types))
        return issubclass(inst, types)
    
    def __notinstance__(cls, inst: Any) -> TypeGuard[NotProtocol[T]]:
        '''Return a type guard for NOT the protocol.'''
        return not cls.__instancecheck__(inst)

    def __optinstance__(cls, inst: Any) -> TypeGuard[OptProtocol[T]]:
        '''Return a type guard for the protocol or NoneType.'''
        return cls.__guardq__(inst)

    def __invert__(cls) -> NotProtocol[T]:
        '''Return the NOT type guard for the protocol.'''
        return cls.__notinstance__

    def __memberguard__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Member check for the protocol using custom `__types__` method
        which enables duck-typing and inheritance.'''
        types = cls.__types__()
        if cls.__species__ == Species.OPT: types = cls.__typesq__()
        def checkmember(inst: Any) -> TypeGuard[type[T]]:
            return bool((inst in types) ^ (cls.__species__ == Species.NOT))
        return checkmember

    def __membercheck__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Member check for the protocol using custom `__types__` method
        which enables duck-typing and inheritance.'''
        match cls.__species__:
            case Species.REG: return inst in cls.__types__()
            case Species.NOT: return not inst in cls.__types__()
            case Species.OPT: return inst in cls.__typesq__()
            case _: return inst in cls.__types__()

    def __contains__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Instance check for the protocol using custom `__guard__` method
        which enables duck-typing and inheritance.'''
        return cls.__membercheck__(inst)
    
    def __eq__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Equality check for the protocol using custom `__guard__` method'''
        # print('__eq__', cls, inst)
        is_implemented = False
        try:
            is_not_implemented = (super().__eq__(inst) == NotImplemented)
            is_implemented = not (is_not_implemented == True)
        except: ...
        try: 
            if cls.__instancecheck__(inst): 
                return True
        except TypeError as inserror: ...
        try:
            if cls.__subclasscheck__(inst):
                return True
        except TypeError as suberror: ...
        try: 
            if inst in cls.__types__(): return True
        except TypeError as inerror: ...
        try: 
            if inst is cls: return True
        except TypeError as iserror: ...
        return is_implemented
    
    def __repr__(cls) -> TypeGuard[type[T]]:
        name = cls.__name__
        match cls.__species__:
            case Species.REG: return f'<duck {name}>'
            case Species.NOT: return f'<!duck {name}>'
            case Species.OPT: return f'<?duck {name}>'
            
    def __maketype__(cls, __type: T) -> tuple[T, bool]:
        '''Try and make the ducktype using a given type.'''
        if isnone(__type): return __type, False
        
        # NOTE: __type is not none so unduck duckspecs and other ducktypes
        
        # NOTE: if the item is a duckspec try to load the asset stored 
        # in `duckspec.duck` or fallback to `duckspec.default`
        ins = __type
        if cls.__instancecheck__(ins): return ins, True
        
        if isduckspec(ins):
            ins = cls.__makespec__(ins)
            if cls.__instancecheck__(ins): return ins, True
        
        # NOTE: __type is a duck type (recurse / invoke) it's 
        # own `__initiate__` until resolved.
        if isduck(ins):
            ins = cls.__makeduck__(ins)
            if cls.__instancecheck__(ins): return ins, True
            
            
        # NOTE: __type is a callable so try to invoke the instance
        if not iscls(ins) and callable(ins):
            ins = cls.__makefunc__(ins)
            if cls.__instancecheck__(ins): return ins, True
    
        # NOTE: __type is not a class or iterable of classes but an instance already, 
        # so try to copy it
        if not iscls(ins) and not isiter(ins):
            ins = cls.__makecopy__(ins)
            if cls.__instancecheck__(ins): return ins, True
        
        if callable(ins):
            ins = cls.__makefunc__(ins)
            if cls.__instancecheck__(ins): return ins, True
            
        if iscls(ins):
            if cls.__instancecheck__(ins): return ins, True
        
        if cls.__instancecheck__(ins): return ins, True
        return __type, False
    
    def __makeiter__(cls, __iter: T) -> tuple[T, bool]:
        '''Try and make the ducktype by iterating over multiple
        types and seeing if any can be initialized into a valid instance.'''
        if isnone(__iter): return __iter, False
        # NOTE: __iter is an iterable of classes so try to 
        # unduck duckspecs, other ducktypes, invoke functions, etc
        if isiter(__iter):
            for kls in __iter:
                ins, val = cls.__maketype__(kls)
                if val: return ins, True
            return __iter, False
        return __iter, False
    
    def __makecopy__(cls, __inst: T) -> T:
        '''Try and make the ducktype by copying the given instance of said type.'''
        if isnone(__inst): return __inst
        try: return copy.copy(__inst)
        except: ...
        return __inst
    
    def __makespec__(cls, __spec: T) -> T:
        '''Try and make the ducktype by importing it via the duckspec or using its default.'''
        if isnone(__spec): return __spec
        # unduckspec input
        if isduckspec(__spec):
            try: return __spec.duck
            except: return __spec.default
        return __spec
    
    def __makeduck__(cls, __duck: T) -> T:
        '''Try and make this ducktype by recursing another ducktype.'''
        if isnone(__duck): return __duck
        # unduck input
        if isduck(__duck):
            try: return __duck.__initiate__()
            except: ...
        return __duck
     
    def __makefunc__(cls, __func: Callable[P, T]) -> T:
        '''Try and make the ducktype via a callable function.'''
        if isnone(__func): return __func
        # NOTE: __func is a callable so try to invoke the instance
        if callable(__func):
            try: return __func()
            except: ... 
        return __func
    
    def __makesupr__(cls) -> T:
        '''Try to make the ducktype by utilizing it's superclass.'''
        gen = getattr(cls, __GENERATE__, None)
        dck = getattr(cls, __DUCKTYPE__, None)
        if isnone(gen) or isnone(dck):
            try: return super().__initiate__()
            except: ...
        return None

        
    def __initiate__(cls) -> type[T] | None:
        '''Make the ducktype based on either a function, type, or iterable 
        of types, returning None if the result is not an instance of this
        duck-type or upon any type of failure.
        '''
        opt = cls.__species__ == Species.OPT
        gen = getattr(cls, __GENERATE__, None)
        dck = getattr(cls, __DUCKTYPE__, None)
        # dck = sorted(dck, key = lambda x: iscls(x), reverse=True)
        sup = None
    
        # print(gen, dck)
        # NOTE: __generator__ is provided try to invoke the instance
        # via either a function, default value, or iterable of types.
        if notnone(gen):
            if isiter(gen):
                # print('gen.isiter')
                ins, val = cls.__makeiter__(gen)
                if val: 
                    try: return ins() if iscls(ins) else ins
                    except: return ins
            else: 
                # print('gen.single')
                ins, val = cls.__maketype__(gen)
                if val: 
                    try: return ins() if iscls(ins) else ins
                    except: return None if opt else ins
            

        # NOTE: __generator__ failed to find a valid instance fallback to 
        # __ducktype__ to try and initiate the instance.
        if notnone(dck):
            if isiter(dck):
                # print('dck.isiter')
                ins, val = cls.__makeiter__(dck)
                if val: 
                    try: return ins() if iscls(ins) else ins
                    except: return None if opt else ins
            else: 
                # print('dck.single')
                ins, val = cls.__maketype__(dck)
                if val: 
                    try: return ins() if iscls(ins) else ins
                    except: return None if opt else ins
        
        
        # NOTE: both __generator__ and __ducktype__ failed to invoke
        # the instance, try to use the super class __initiate__ to
        # get the default value or duck type.
        sup = cls.__makesupr__()
        if cls.__instancecheck__(sup): return sup
        return None
            
    def __call__(cls, *args, **kwargs) -> TypeGuard[type[T]]:
        '''Return the protocol class itself.'''
        gen = any(kwargs.pop(key, False) for key in CALL_INIT_ATTRS)
        noargs = (not bool(args) and not bool(kwargs))
        if gen or noargs:
            try: return cls.__initiate__()
            except: ...
            
        fn = cls.__instancecheck__
        results = [fn(arg) for arg in args]
        if len(results) == 1: return results[0]
        return tuple(results)

    def __hash__(self) -> int:
        return super().__hash__()
    
    def __new__(mcls, name, bases, namespace, **kwargs):
        '''Create a new protocol class.'''
        # print('meta.__new__\t', mcls, name, bases, namespace, kwargs)
        new = super().__new__(mcls, name, bases, namespace)
        new = mcls.__metahook__(new, name, bases, namespace, **kwargs)
        return new
        
    @classmethod
    def __metahook__(mcls, cls, name, bases, namespace, **kwargs):
        '''Hook for the protocol metaclass.'''
        # print('meta.__metahook__\t', mcls, cls, name, bases, namespace, kwargs)
        hook = getattr(cls, '__metahook__', None)
        if callable(hook): 
            adj = hook(mcls, name, bases, namespace, **kwargs)
        return cls
    
    def __init__(cls, *args, **kargs):
        ...
        # print('__init__\t', cls, args, kargs)
        cls.__areinrow__ = False
    
    def __init_subclass__(cls, *args, **kwargs):
        super().__init_subclass__()
        # print('meta.__init_subclass__\t', cls, args, kwargs)
        return 

# %% ../nbs/09_meta.ipynb 40
class DuckProtocolMeta(_ProtocolMeta, ABCSpecies, ABCMeta, type):
    __ducktype__: ClassVar[tuple[type[T], ...]] = tuple()
    '''The specific types that the protocol is duck-typed to.''';
    
    _duck: ClassVar[tuple[type[T], ...]]
    @property
    def __duck__(cls) -> tuple[type[T], ...]:
        if getattr(cls, _DUCK, None) is None:
            unducked = prepare_types_for_instancecheck(cls.__ducktype__)
            setattr(cls, _DUCK, unducked)
        return cls._duck
    
    @abstractmethod
    def __types__(cls) -> tuple[type, ...]:
        '''Return the types that the protocol is duck-typed to.'''
        return prepare_types_for_instancecheck(cls.__duck__)
        duck = list(cls._duck)
        for i, d in enumerate(duck):
            if not (isinstance(d, type) or isclass(d)):
                duck[i] = type(d)
        return tuple(duck)

    @abstractmethod
    def __typesq__(cls) -> tuple[type, ...]: 
        '''Return the types that the protocol is duck-typed to, including NoneType.'''
        return cls.__types__() + (NoneType, )
        
    @abstractmethod
    def __guard__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Return a type guard for the protocol.'''
        return isinstance(inst, cls.__types__())
    
    @abstractmethod
    def __guardq__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Return a type guard for the protocol, including NoneType.'''
        return isinstance(inst, cls.__typesq__())
        
    def __instancecheck__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Instance check for the protocol using custom `__guard__` method
        which enables duck-typing and inheritance.'''
        match cls.__species__:
            case Species.REG: return cls.__guard__(inst)
            case Species.NOT: return cls.__notinstance__(inst)
            case Species.OPT: return cls.__optinstance__(inst)
        return cls.__guard__(inst)
    
    def __subclasscheck__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Instance check for the protocol using custom `__guard__` method
        which enables duck-typing and inheritance.'''
        match cls.__species__:
            case Species.REG: return issubclass(inst, cls.__types__())
            case Species.NOT: return not issubclass(inst, cls.__types__())
            case Species.OPT: return issubclass(inst, cls.__typesq__())
        return issubclass(inst, cls.__types__())
    
    @abstractmethod
    def __notinstance__(cls, inst: Any) -> TypeGuard[NotProtocol[T]]:
        '''Return a type guard for NOT the protocol.'''
        return not cls.__instancecheck__(inst)
    
    @abstractmethod
    def __optinstance__(cls, inst: Any) -> TypeGuard[OptProtocol[T]]:
        '''Return a type guard for the protocol or NoneType.'''
        return cls.__guardq__(inst)
    
    @abstractmethod
    def __invert__(cls) -> NotProtocol[T]:
        '''Return the NOT type guard for the protocol.'''
        return cls.__notinstance__
    
    @abstractmethod
    def __membercheck__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Member check for the protocol using custom `__types__` method
        which enables duck-typing and inheritance.'''
        match cls.__species__:
            case Species.REG: return inst in cls.__types__()
            case Species.NOT: return not inst in cls.__types__()
            case Species.OPT: return inst in cls.__typesq__()
        return inst in cls.__types__()
    
    def __contains__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Instance check for the protocol using custom `__guard__` method
        which enables duck-typing and inheritance.'''
        return cls.__membercheck__(inst)
    
    @abstractmethod
    def __eq__(cls, inst: Any) -> TypeGuard[type[T]]: 
        '''Equality check for the protocol using custom `__guard__` method'''
        try: 
            if cls.__instancecheck__(inst): 
                return True
        except TypeError as inserror: ...
        try: 
            if cls.__subclasscheck__(inst):
                return True
        except TypeError as suberror: ...
        try: 
            if inst in cls.__types__():
                return True
        except TypeError as inerror: ...
        try: 
            if inst is cls:
                return True
        except TypeError as iserror: ...
        return False
    
    def __hash__(self) -> int:
        return super().__hash__()

# %% ../nbs/09_meta.ipynb 42
class DuckModuleMeta(ABCMeta, type):
    __duckmodule__: ClassVar[str]
    '''The name of the module to duck.''';
    
    __hush__: ClassVar[bool] = True
    '''Whether or not to raise AttributeError when an attribute is not found.''';
    
    __alts__: ClassVar[dict[str, type]] = {}
    '''Alternatives to use when an attribute is not found.''';
    
    def __instancecheck__(cls: type[T], obj: Any) -> bool:
        '''Check if the object is an instance of the class.'''
        issubbed = isinstance(obj, (ModuleType, ))
        samename = object.__getattribute__(cls, __DUCKMODULE__) == obj.__name__
        hasattrs = all(hasattr(obj, attr) for attr in MODULE_TYPE_ATTRS)
        hasprivs = all(hasattr(obj, attr) for attr in (__DUCKMODULE__, __HUSH__, __ALTS__))
        if issubbed and samename and (hasattrs or hasprivs): return True
        return False
     
    
    def __getattr__(cls: type[T], name: str) -> Any:
        hush = object.__getattribute__(cls, __HUSH__)
        try:
            if name in ANNOYING_ATTRS: 
                try:
                    # return super().__getattribute__(name)
                    raise AttributeError(f"Module '{cls.__name__}' has no attribute '{name}'.")
                except:
                    if object.__getattribute__(cls, __HUSH__): return EmptyModule
                    raise AttributeError(f'''Module '{cls.__name__}' has no attribute '{name}'.''')
            
            # base module name
            base = object.__getattribute__(cls, __DUCKMODULE__)
            # full module name
            full = f'{base}.{name}' if cls.__name__ != 'DuckModule' else name
            
            mkey = full # key to use for sys.modules
            spec: ModuleSpec | None = None # module spec
            stub: str | None = None
            
            # <base.name> not in sys modules
            if full not in sys.modules:            
                # try using importlib to load the module
                try:
                    module = import_module(full)
                    sys.modules[full] = module
                    mkey = full
                except ModuleNotFoundError: ...
                    
                # try using the last found module spec
                try:
                    spec, stub = find_last_spec(full)
                    module: ModuleType = module_from_spec(spec)
                    sys.modules[stub] = module
                    mkey = stub
                    
                except:
                    if object.__getattribute__(cls, __HUSH__): return EmptyModule
                    raise AttributeError(f'''Module '{cls.__name__}' has no attribute '{name}'.''')
                
            # <base.name> or <stub> in sys.modules
            module = sys.modules[mkey]
            
            # create a DuckModule subclass for the submodule if needed            
            # @wraps(cls, assigned=('__name__', '__qualname__', '__doc__', '__annotations__'), updated=())
            # class ducked(cls):
            #     __duckmodule__ = mkey
                
            ducked = type(mkey, (cls, ), dict(__duckmodule__=mkey, __alts__=cls.__alts__, __hush__=cls.__hush__,))
            # ducked = ducked(module.__name__, module.__doc__)
            # ducked.__dict__ = {**module.__dict__, **ducked.__dict__}
            # ducked.__dict__.update({ **module.__dict__, **ducked.__dict__,**dict(_duck=mkey, __alts__=cls.__alts__, __hush__=cls.__hush__,)})

            # Try to get the attribute from the module if it exists
            try: 
                attr = object.__getattribute__(module, name)
                if LoaderState.get(module) not in [LoaderState.NONE, LoaderState.DUCKED]:
                    sys.modules[mkey] = LoaderState.set(module, LoaderState.EXECED)
                return attr
            except AttributeError: ...
            
            # Try actually loading the module if it hasn't been loaded yet
            try: 
                sys.modules[mkey] = LoaderState.exec(module)
                return object.__getattribute__(module, name)
            except AttributeError: ...
            
            # Handle fallbacks if defined
            if hasattr(cls, __ALTS__) and name in cls.__alts__: return cls.__alts__[name]
            
            return ducked
        
        except NameError as e:
            if not hush: raise DuckNameError(*e.args)
        
        except ModuleNotFoundError as e:
            if not hush: raise DuckModuleNotFoundError(e.args, e.name, e.path)
        
        except ImportError as e:
            if not hush: raise DuckImportError(e.args, e.name, e.path)
        
        except AttributeError as e:
            if not hush: raise DuckAttributeError(e.args, e.name, e.obj)
        
        except Exception as e:
            if not hush: raise DuckException(f'NameError: {e}')
        
        return EmptyModule
    
    def __hash__(self) -> int:
        return super().__hash__()
