# -*- coding: utf-8 -*-
"""This module implements some exception classes and related features."""
# import...
# ...from standard-library
import enum
import importlib
import types
# ...from HydPy
from hydpy.core import objecttools
from hydpy.core.typingtools import *
[docs]
class HydPyDeprecationWarning(DeprecationWarning):
"""Warning for deprecated *HydPy* features."""
[docs]
class AttributeNotReady(RuntimeError):
"""The attribute is principally defined but so far unprepared."""
[docs]
class AttributeNotReadyWarning(Warning):
"""The attribute is principally defined but so far unprepared."""
[docs]
def attrready(obj: Any, name: str) -> bool:
"""Return |False| when trying the access the attribute of the given object
results in an |AttributeNotReady| error and otherwise return |True|.
In *HydPy*, some properties raise an |AttributeNotReady| error when one
tries to access them before they are correctly set. You can use method
|attrready| to find out the current state of such properties without
doing the related exception handling on your own:
>>> from hydpy import attrready
>>> from hydpy.core.parametertools import Parameter
>>> class Par(Parameter):
... NDIM, TYPE = 1, float
>>> par = Par(None)
>>> attrready(par, "NDIM")
True
>>> attrready(par, "ndim")
Traceback (most recent call last):
...
AttributeError: 'Par' object has no attribute 'ndim'
>>> attrready(par, "shape")
False
>>> par.shape = 2
>>> attrready(par, "shape")
True
"""
try:
getattr(obj, name)
except AttributeNotReady:
return False
return True
[docs]
def hasattr_(obj: Any, name: str) -> bool:
"""Return |True| or |False| whether the object has an attribute with the
given name.
In *HydPy*, some properties raise an |AttributeNotReady| error when one
tries to access them before they are correctly set, which also happens
when one applies function |hasattr| to find out if the related object
handles the property at all. Function |hasattr_| extends function
|hasattr| by also catching |AttributeNotReady| errors:
>>> from hydpy import hasattr_
>>> from hydpy.core.parametertools import Parameter
>>> class Par(Parameter):
... NDIM, TYPE = 1, float
>>> par = Par(None)
>>> hasattr_(par, "NDIM")
True
>>> hasattr_(par, "ndim")
False
>>> hasattr_(par, "shape")
True
>>> par.shape = 2
>>> attrready(par, "shape")
True
"""
try:
return hasattr(obj, name)
except AttributeNotReady:
return True
class _Enum(enum.Enum):
GETATTR_NO_DEFAULT = 1
@overload
def getattr_(obj: Any, name: str) -> Any: ...
@overload
def getattr_(obj: Any, name: str, default: None) -> Optional[Any]: ...
@overload
def getattr_(obj: Any, name: str, default: T) -> T: ...
@overload
def getattr_(obj: Any, name: str, *, type_: type[T]) -> T: ...
@overload
def getattr_(obj: Any, name: str, default: None, type_: type[T]) -> Optional[T]: ...
@overload
def getattr_(obj: Any, name: str, default: T1, type_: type[T2]) -> Union[T1, T2]: ...
[docs]
def getattr_(
obj: Any,
name: str,
default: Union[T, _Enum] = _Enum.GETATTR_NO_DEFAULT,
type_: Optional[type[T]] = None,
) -> Any:
"""Return the attribute with the given name or, if it does not exist,
the default value, if available.
In *HydPy*, some properties raise an |AttributeNotReady| error when one
tries to access them before they are correctly set, which also happens
when one applies function |getattr| with default values. Function
|getattr_| extends function |getattr| by also returning the default
value when an |AttributeNotReady| error occurs:
>>> from hydpy import getattr_
>>> from hydpy.core.parametertools import Parameter
>>> class Par(Parameter):
... NDIM, TYPE = 1, float
>>> par = Par(None)
>>> getattr_(par, "NDIM")
1
>>> getattr_(par, "NDIM", 2)
1
>>> getattr_(par, "ndim")
Traceback (most recent call last):
...
AttributeError: 'Par' object has no attribute 'ndim'
>>> getattr_(par, "ndim", 2)
2
>>> getattr_(par, "shape")
Traceback (most recent call last):
...
hydpy.core.exceptiontools.AttributeNotReady: Shape information for \
variable `par` can only be retrieved after it has been defined.
>>> getattr_(par, "shape", (4,))
(4,)
>>> par.shape = 2
>>> getattr_(par, "shape")
(2,)
>>> getattr_(par, "shape", (4,))
(2,)
Function |getattr_| allows to specify the expected return type and raises
an |AssertionError| if necessary:
>>> getattr_(par, "NDIM", type_=int)
1
>>> getattr_(par, "NDIM", type_=str)
Traceback (most recent call last):
...
AssertionError: returned type: int, allowed type: str
Additionally, it infers the expected return type from the default argument
automatically:
>>> getattr_(par, "NDIM", "wrong")
Traceback (most recent call last):
...
AssertionError: returned type: int, allowed type(s): str
If the attribute type differs from the default value type, you need to define
the attribute type explicitly:
>>> getattr_(par, "NDIM", "wrong", int)
1
"""
if default is _Enum.GETATTR_NO_DEFAULT:
value = getattr(obj, name)
if type_ is not None and not isinstance(value, type_):
raise AssertionError(
f"returned type: {type(value).__name__}, "
f"allowed type: {type_.__name__}"
)
return value
try:
value = getattr(obj, name, default)
except AttributeNotReady:
return default
types_ = ((type_,) if type_ else ()) + ((type(default),) if default else ())
if types_ and not isinstance(value, types_):
raise AssertionError(
f"returned type: {type(value).__name__}, allowed type(s): "
f"{objecttools.enumeration(type_.__name__ for type_ in types_)}"
)
return value
[docs]
class OptionalModuleNotAvailable(ImportError):
"""A `HydPy` function requiring an optional module is called, but this
module is not available."""
[docs]
class OptionalImport(types.ModuleType):
"""Imports the first found module "lazily".
>>> from hydpy.core.exceptiontools import OptionalImport
>>> numpy = OptionalImport(
... "numpy", ["numpie", "numpy", "os"], locals())
>>> numpy.nan
nan
If no module could be imported at all, |OptionalImport| returns a
dummy object which raises a |OptionalModuleNotAvailable| each time
a one tries to access a member of the original module.
>>> numpie = OptionalImport("numpie", ["numpie"], locals())
>>> numpie.nan
Traceback (most recent call last):
...
hydpy.core.exceptiontools.OptionalModuleNotAvailable: HydPy could not \
load one of the following modules: `numpie`. These modules are no general \
requirement but installing at least one of them is necessary for some \
specific functionalities.
Note the very special case that |OptionalImport| raises a plain
|AttributeError| when asked for the attribute `__wrapped__` (to avoid
trouble when applying function `wrapt` of module |inspect|):
>>> numpie.__wrapped__
Traceback (most recent call last):
...
AttributeError
"""
def __init__(
self, name: str, modules: list[str], namespace: dict[str, Any]
) -> None:
self._name = name
self._modules = modules
self._namespace = namespace
def __getattr__(self, name: str) -> Any:
if name == "__wrapped__":
raise AttributeError
module = None
for modulename in self._modules:
try:
module = importlib.import_module(modulename)
self._namespace[self._name] = module
break
except ImportError:
pass
if module:
return getattr(module, name)
raise OptionalModuleNotAvailable(
f"HydPy could not load one of the following modules: "
f"`{objecttools.enumeration(self._modules)}`. These modules are "
f"no general requirement but installing at least one of them "
f"is necessary for some specific functionalities."
)