polydojo / dotsi

Dot-accessible, update-aware Python dicts (& lists). Works recursively, like a charm.
MIT License
31 stars 3 forks source link

Broken hasattr/getattr #3

Open caenrigen opened 1 year ago

caenrigen commented 1 year ago

Hi there! Cool packages, but run into an issue with it right in the beginning...

from dotsi import DotsiDict

assert getattr({}, "a", None) is None  # all good
hasattr(DotsiDict({}), "a") # raises
assert getattr(DotsiDict({}), "a", None) is None  # raises

which raises e.g.

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/var/folders/s5/z9b__9915kq1tpx_kltkywn80000gn/T/ipykernel_40275/4202784131.py in <module>
----> 1 assert getattr(DotsiDict({}), "a", None) is None

KeyError: 'a'

The problem is this line:

    __getattr__ = dict.__getitem__

and the core issue is this:

{}["a"]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/var/folders/s5/z9b__9915kq1tpx_kltkywn80000gn/T/ipykernel_24098/539066286.py in <module>
----> 1 {}["a"]

KeyError: 'a'
{}.a
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/var/folders/s5/z9b__9915kq1tpx_kltkywn80000gn/T/ipykernel_24098/3365273685.py in <module>
----> 1 {}.a

AttributeError: 'dict' object has no attribute 'a'

python relies on catching specific exceptions to make hasattr and getattr(obj, key, default_value) work


Potential solution is something like:


def getattr_func(self, attr: str):
    try:
        return dict.__getitem__(self, attr)
    except KeyError as e:
       # maybe improve messages to mimic the default one
        raise AttributeError(repr(attr)) from e

class DotsiDict(dict):
    "Extends `dict` to support dot-access."

    def __setitem__(self, key, value):  # PRIMARY
        super(DotsiDict, self).__setitem__(key, dotsify(value))

    __setattr__ = __setitem__
    __getattr__ = getattr_func
    __delattr__ = dict.__delitem__

    # ...

PS for context i run into this because I use iPython/JupyterLab + rich.pretty package to visualise objects:

from rich.pretty import install
install()

DotsiDict({}) # raises error because `rich.pretty` checks for `getattr(object_to_be_displayed, "_repr_html_", None)`