Source code for cawdrey.nonelessdict

#!/usr/bin/env python
#
#  nonelessdict.py
"""
Provides dictionaries that cannot contain :py:obj:`None`.
"""
#
#  Copyright © 2020,2022 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#  Copyright © Jeremy Mayeres
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#

# stdlib
import operator
from collections import OrderedDict
from functools import reduce
from typing import Optional, TypeVar

# 3rd party
from domdf_python_tools.doctools import prettify_docstrings

# this package
from .base import KT, VT, MutableBase

__all__ = ["NonelessDict", "NonelessOrderedDict", "_ND", "_NOD"]

_ND = TypeVar("_ND", bound="NonelessDict")
_NOD = TypeVar("_NOD", bound="NonelessOrderedDict")


[docs]@prettify_docstrings class NonelessDict(MutableBase[KT, VT]): """ A wrapper around dict that will check if a value is :py:obj:`None`/empty/:py:obj:`False`, and not add the key in that case. Use the :meth:`~.NonelessDict.set_with_strict_none_check` method to check only for :py:obj:`None`. .. autosummary-widths:: 1/2 """ # noqa: D400 dict_cls = dict # type: ignore def __init__(self, *args, **kwargs): if hasattr(self, "_dict"): raise TypeError(f"`{self.__class__}` can only be initialised once.") super().__init__(*args, **kwargs)
[docs] def copy(self: _ND, **add_or_replace: VT) -> _ND: # type: ignore[override] """ Return a copy of the dictionary. """ return self.__class__(self, **add_or_replace)
def __hash__(self) -> int: if self._hash is None: h = 0 for key, value in self._dict.items(): h ^= hash((key, value)) self._hash = h return self._hash
[docs] def set_with_strict_none_check(self, key: KT, value: Optional[VT]) -> None: """ Set ``key`` in the dictionary to ``value``, but skipping :py:obj:`None` values. :param key: :param value: """ if value is not None: self._dict[key] = value
[docs] def __setitem__(self, key: KT, value: Optional[VT]): if value: return super().__setitem__(key, value)
[docs]@prettify_docstrings class NonelessOrderedDict(MutableBase[KT, VT]): """ A wrapper around OrderedDict that will check if a value is None/empty/False, and not add the key in that case. Use the set_with_strict_none_check function to check only for None """ # noqa: D400 dict_cls = OrderedDict # type: ignore def __init__(self, *args, **kwargs): if hasattr(self, "_dict"): raise TypeError(f"`{self.__class__}` can only be initialised once.") super().__init__(*args, **kwargs)
[docs] def copy(self: _NOD, *args, **kwargs) -> _NOD: """ Return a copy of the dictionary. """ new_dict = self._dict.copy() if args or kwargs: new_dict.update(OrderedDict(*args, **kwargs)) return self.__class__(new_dict)
def __hash__(self) -> int: if self._hash is None: self._hash = reduce(operator.xor, map(hash, self.items()), 0) return self._hash
[docs] def set_with_strict_none_check(self, key: KT, value: Optional[VT]) -> None: """ Set ``key`` in the dictionary to ``value``, but skipping :py:obj:`None` values. :param key: :param value: """ if value is not None: self._dict[key] = value
[docs] def __setitem__(self, key: KT, value: Optional[VT]): if value: return super().__setitem__(key, value)