Back to top

hikari.internal.collections

Custom data structures used within Hikari's core implementation.

View Source
# -*- coding: utf-8 -*-
# cython: language_level=3
# Copyright (c) 2020 Nekokatt
# Copyright (c) 2021-present davfsa
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Custom data structures used within Hikari's core implementation."""
from __future__ import annotations

__all__: typing.Sequence[str] = (
    "ExtendedMapT",
    "KeyT",
    "ValueT",
    "SnowflakeSet",
    "ExtendedMutableMapping",
    "FreezableDict",
    "LimitedCapacityCacheMap",
    "get_index_or_slice",
)

import abc
import array
import bisect
import itertools
import sys
import typing

from hikari import snowflakes

ExtendedMapT = typing.TypeVar("ExtendedMapT", bound="ExtendedMutableMapping[typing.Any, typing.Any]")
"""Type-hint A type hint used for mapped collection objects."""
KeyT = typing.TypeVar("KeyT", bound=typing.Hashable)
"""Type-hint A type hint used for the type of a mapping's key."""
ValueT = typing.TypeVar("ValueT")
"""Type-hint A type hint used for the type of a mapping's value."""

_LONG_LONG_UNSIGNED: typing.Final[str] = sys.intern("Q")


class ExtendedMutableMapping(typing.MutableMapping[KeyT, ValueT], abc.ABC):
    """The abstract class of mutable mappings used within Hikari.

    These are mutable mappings that have a couple of extra methods to allow
    for further optimised copy operations, as well as the ability to freeze
    the implementation of a mapping to make it read-only.
    """

    __slots__: typing.Sequence[str] = ()

    @abc.abstractmethod
    def copy(self: ExtendedMapT) -> ExtendedMapT:
        """Return a copy of this mapped collection.

        Unlike simply doing `dict(mapping)`, this may rely on internal detail
        around how the data is being stored to allow for a more efficient copy.
        This may look like calling `dict.copy` and wrapping the result in a
        mapped collection.

        .. note::
            Any removal policy on this mapped collection will be copied over.

        Returns
        -------
        MapT[KeyT, ValueT]
            A copy of this mapped collection.
        """

    @abc.abstractmethod
    def freeze(self) -> typing.MutableMapping[KeyT, ValueT]:
        """Return a frozen mapping view of the items in this mapped collection.

        Unlike simply doing `dict(mapping)`, this may rely on internal detail
        around how the data is being stored to allow for a more efficient copy.
        This may look like calling `dict.copy`.

        .. note::
            Unlike `ExtendedMutableMapping.copy`, this should return a pure
            mapping with no removal policy at all.

        Returns
        -------
        typing.MutableMapping[KeyT, ValueT]
            A frozen mapping view of the items in this mapped collection.
        """


class FreezableDict(ExtendedMutableMapping[KeyT, ValueT]):
    """A mapping that wraps a dict, but can also be frozen."""

    __slots__: typing.Sequence[str] = ("_data",)

    def __init__(self, source: typing.Optional[typing.Dict[KeyT, ValueT]] = None, /) -> None:
        self._data = source or {}

    def clear(self) -> None:
        self._data.clear()

    def copy(self) -> FreezableDict[KeyT, ValueT]:
        return FreezableDict(self._data.copy())

    # TODO: name this something different if it is not physically frozen.
    def freeze(self) -> typing.Dict[KeyT, ValueT]:
        return self._data.copy()

    def __delitem__(self, key: KeyT) -> None:
        del self._data[key]

    def __getitem__(self, key: KeyT) -> ValueT:
        return self._data[key]

    def __iter__(self) -> typing.Iterator[KeyT]:
        return iter(self._data)

    def __len__(self) -> int:
        return len(self._data)

    def __setitem__(self, key: KeyT, value: ValueT) -> None:
        self._data[key] = value


class _FrozenDict(typing.MutableMapping[KeyT, ValueT]):
    __slots__: typing.Sequence[str] = ("_source",)

    def __init__(self, source: typing.Dict[KeyT, typing.Tuple[float, ValueT]], /) -> None:
        self._source = source

    def __getitem__(self, key: KeyT) -> ValueT:
        return self._source[key][1]

    def __iter__(self) -> typing.Iterator[KeyT]:
        return iter(self._source)

    def __len__(self) -> int:
        return len(self._source)

    def __delitem__(self, key: KeyT) -> None:
        del self._source[key]

    def __setitem__(self, key: KeyT, value: ValueT) -> None:
        self._source[key] = (0.0, value)


class LimitedCapacityCacheMap(ExtendedMutableMapping[KeyT, ValueT]):
    """Implementation of a capacity-limited most-recently-inserted mapping.

    This will start removing the oldest entries after it's maximum capacity is
    reached as new entries are added.

    Parameters
    ----------
    limit : int
        The limit for how many objects should be stored by this mapping before
        it starts removing the oldest entries.

    Other Parameters
    ----------------
    source : typing.Optional[typing.Dict[KeyT, ValueT]]
        A source dictionary of keys to values to create this from.
    on_expire : typing.Optional[typing.Callable[[ValueT], None]]
        A function to call each time an item is garbage collected from this
        map. This should take one positional argument of the same type stored
        in this mapping as the value and should return `None.

        This will always be called after the entry has been removed.
    """

    __slots__: typing.Sequence[str] = ("_data", "_limit", "_on_expire")

    def __init__(
        self,
        source: typing.Optional[typing.Dict[KeyT, ValueT]] = None,
        /,
        *,
        limit: int,
        on_expire: typing.Optional[typing.Callable[[ValueT], None]] = None,
    ) -> None:
        self._data: typing.Dict[KeyT, ValueT] = source or {}
        self._limit = limit
        self._on_expire = on_expire
        self._garbage_collect()

    def clear(self) -> None:
        self._data.clear()

    def copy(self) -> LimitedCapacityCacheMap[KeyT, ValueT]:
        return LimitedCapacityCacheMap(self._data.copy(), limit=self._limit, on_expire=self._on_expire)

    def freeze(self) -> typing.Dict[KeyT, ValueT]:
        return self._data.copy()

    def _garbage_collect(self) -> None:
        while len(self._data) > self._limit:
            value = self._data.pop(next(iter(self._data)))

            if self._on_expire:
                self._on_expire(value)

    def __delitem__(self, key: KeyT) -> None:
        del self._data[key]

    def __getitem__(self, key: KeyT) -> ValueT:
        return self._data[key]

    def __iter__(self) -> typing.Iterator[KeyT]:
        return iter(self._data)

    def __len__(self) -> int:
        return len(self._data)

    def __setitem__(self, key: KeyT, value: ValueT) -> None:
        self._data[key] = value
        self._garbage_collect()


# TODO: can this be immutable?
class SnowflakeSet(typing.MutableSet[snowflakes.Snowflake]):
    r"""Set of `hikari.snowflakes.Snowflake` objects.

    This internally uses a sorted bisect-array of 64 bit integers to represent
    the information. This reduces the space needed to store these objects
    significantly down to the size of 8 bytes per item.

    In contrast, a regular list would take generally 8 bytes per item just to
    store the memory address, and then a further 28 bytes or more to physically
    store the integral value if it does not get interned by the Python
    implementation you are using. A regular set would consume even more
    space, being a hashtable internally.

    The detail of this implementation has the side effect that searches
    will take $$ \mathcal{O} \left( \log n \right) $$ operations in the worst
    case, and $$ \Omega \left (k \right) $$ in the best case. Average case
    will be $$ \mathcal{O} \left( \log n \right) $$

    Insertions and removals will take $$ \mathcal{O} \left( \log n \right) $$
    operations in the worst case, due to `bisect` using a binary insertion sort
    algorithm internally. Average case will be
    $$ \mathcal{O} \left( \log n \right) $$ and best case will be
    $$ \Omega \left\( k \right) $$

    .. warning::
        This is not thread-safe and must not be iterated across whilst being
        concurrently modified.

    Other Parameters
    ----------------
    *ids : int
        The IDs to fill this table with.
    """

    __slots__: typing.Sequence[str] = ("_ids",)

    def __init__(self, *ids: int) -> None:
        self._ids: typing.MutableSequence[int] = array.array(_LONG_LONG_UNSIGNED)

        if ids:
            self.add_all(ids)

    def add(self, value: int, /) -> None:
        """Add a snowflake to this set."""
        # Always append the first item, as we cannot compare with nothing.
        index = bisect.bisect_left(self._ids, value)
        if index == len(self._ids):
            self._ids.append(value)
        elif self._ids[index] != value:
            self._ids.insert(index, value)

    def add_all(self, sfs: typing.Iterable[int], /) -> None:
        """Add a collection of snowflakes to this set."""
        if not sfs:
            return

        for sf in sfs:
            # Yes, this is repeated code, but we want insertions to be as fast
            # as possible for caching purposes, so reduce the number of function
            # calls as much as possible and reimplement the logic for `add`
            # here.
            index = bisect.bisect_left(self._ids, sf)
            if index == len(self._ids):
                self._ids.append(sf)
            elif self._ids[index] != sf:
                self._ids.insert(index, sf)

    def clear(self) -> None:
        """Clear all items from this collection."""
        # Arrays lack a "clear" method.
        self._ids = array.array(_LONG_LONG_UNSIGNED)

    def discard(self, value: int, /) -> None:
        """Remove a snowflake from this set if it's present."""
        if not self._ids:
            return

        index = bisect.bisect_left(self._ids, value)

        if index < len(self) and self._ids[index] == value:
            del self._ids[index]

    def __contains__(self, value: typing.Any) -> bool:
        if not isinstance(value, int):
            return False

        index = bisect.bisect_left(self._ids, value)

        if index < len(self._ids):
            return self._ids[index] == value
        return False

    def __iter__(self) -> typing.Iterator[snowflakes.Snowflake]:
        return map(snowflakes.Snowflake, self._ids)

    def __len__(self) -> int:
        return len(self._ids)

    def __repr__(self) -> str:
        return type(self).__name__ + "(" + ", ".join(map(repr, self._ids)) + ")"

    def __sizeof__(self) -> int:
        return super().__sizeof__() + sys.getsizeof(self._ids)

    def __str__(self) -> str:
        return "{" + ", ".join(map(repr, self._ids)) + "}"


def get_index_or_slice(
    mapping: typing.Mapping[KeyT, ValueT], index_or_slice: typing.Union[int, slice]
) -> typing.Union[ValueT, typing.Sequence[ValueT]]:
    """Get a mapping's entry at a given index as if it's a sequence of it's values.

    Parameters
    ----------
    mapping : typing.Mapping[KeyT, ValueT]
        The mapping of entries to treat as a sequence.
    index_or_slice : typing.Sequence[KeyT, ValueT]
        The index to get an entry to get or slice of multiple entries to get.

    Returns
    -------
    ValueT or typing.Sequence[ValueT]
        The value found at the given integer index or a sequence of the values
        found based on the given slice's rules.

    Raises
    ------
    TypeError
        If `index_or_slice` isn't a `slice` or `int`.
    IndexError
        If `index_or_slice` is an int and is outside the range of the mapping's
        contents.
    """
    if isinstance(index_or_slice, slice):
        return tuple(itertools.islice(mapping.values(), index_or_slice.start, index_or_slice.stop, index_or_slice.step))

    if isinstance(index_or_slice, int):
        try:
            return next(itertools.islice(mapping.values(), index_or_slice, None))
        except StopIteration:
            raise IndexError(index_or_slice) from None

    raise TypeError(f"sequence indices must be integers or slices, not {type(index_or_slice).__name__}")
#  ExtendedMapT

Type-hint A type hint used for mapped collection objects.

#  class ExtendedMutableMapping(typing.MutableMapping[~KeyT, ~ValueT], abc.ABC):
View Source
class ExtendedMutableMapping(typing.MutableMapping[KeyT, ValueT], abc.ABC):
    """The abstract class of mutable mappings used within Hikari.

    These are mutable mappings that have a couple of extra methods to allow
    for further optimised copy operations, as well as the ability to freeze
    the implementation of a mapping to make it read-only.
    """

    __slots__: typing.Sequence[str] = ()

    @abc.abstractmethod
    def copy(self: ExtendedMapT) -> ExtendedMapT:
        """Return a copy of this mapped collection.

        Unlike simply doing `dict(mapping)`, this may rely on internal detail
        around how the data is being stored to allow for a more efficient copy.
        This may look like calling `dict.copy` and wrapping the result in a
        mapped collection.

        .. note::
            Any removal policy on this mapped collection will be copied over.

        Returns
        -------
        MapT[KeyT, ValueT]
            A copy of this mapped collection.
        """

    @abc.abstractmethod
    def freeze(self) -> typing.MutableMapping[KeyT, ValueT]:
        """Return a frozen mapping view of the items in this mapped collection.

        Unlike simply doing `dict(mapping)`, this may rely on internal detail
        around how the data is being stored to allow for a more efficient copy.
        This may look like calling `dict.copy`.

        .. note::
            Unlike `ExtendedMutableMapping.copy`, this should return a pure
            mapping with no removal policy at all.

        Returns
        -------
        typing.MutableMapping[KeyT, ValueT]
            A frozen mapping view of the items in this mapped collection.
        """

The abstract class of mutable mappings used within Hikari.

These are mutable mappings that have a couple of extra methods to allow for further optimised copy operations, as well as the ability to freeze the implementation of a mapping to make it read-only.

Methods
#  def clear(self):
View Source
    def clear(self):
        'D.clear() -> None.  Remove all items from D.'
        try:
            while True:
                self.popitem()
        except KeyError:
            pass

D.clear() -> None. Remove all items from D.

#  
@abc.abstractmethod
def copy(self: ~ExtendedMapT) -> ~ExtendedMapT:
View Source
    @abc.abstractmethod
    def copy(self: ExtendedMapT) -> ExtendedMapT:
        """Return a copy of this mapped collection.

        Unlike simply doing `dict(mapping)`, this may rely on internal detail
        around how the data is being stored to allow for a more efficient copy.
        This may look like calling `dict.copy` and wrapping the result in a
        mapped collection.

        .. note::
            Any removal policy on this mapped collection will be copied over.

        Returns
        -------
        MapT[KeyT, ValueT]
            A copy of this mapped collection.
        """

Return a copy of this mapped collection.

Unlike simply doing dict(mapping), this may rely on internal detail around how the data is being stored to allow for a more efficient copy. This may look like calling dict.copy and wrapping the result in a mapped collection.

Note: Any removal policy on this mapped collection will be copied over.

Returns
  • MapT[KeyT, ValueT]: A copy of this mapped collection.
#  
@abc.abstractmethod
def freeze(self) -> MutableMapping[~KeyT, ~ValueT]:
View Source
    @abc.abstractmethod
    def freeze(self) -> typing.MutableMapping[KeyT, ValueT]:
        """Return a frozen mapping view of the items in this mapped collection.

        Unlike simply doing `dict(mapping)`, this may rely on internal detail
        around how the data is being stored to allow for a more efficient copy.
        This may look like calling `dict.copy`.

        .. note::
            Unlike `ExtendedMutableMapping.copy`, this should return a pure
            mapping with no removal policy at all.

        Returns
        -------
        typing.MutableMapping[KeyT, ValueT]
            A frozen mapping view of the items in this mapped collection.
        """

Return a frozen mapping view of the items in this mapped collection.

Unlike simply doing dict(mapping), this may rely on internal detail around how the data is being stored to allow for a more efficient copy. This may look like calling dict.copy.

Note: Unlike ExtendedMutableMapping.copy, this should return a pure mapping with no removal policy at all.

Returns
  • typing.MutableMapping[KeyT, ValueT]: A frozen mapping view of the items in this mapped collection.
#  def get(self, key, default=None):
View Source
    def get(self, key, default=None):
        'D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None.'
        try:
            return self[key]
        except KeyError:
            return default

D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.

#  def items(self):
View Source
    def items(self):
        "D.items() -> a set-like object providing a view on D's items"
        return ItemsView(self)

D.items() -> a set-like object providing a view on D's items

#  def keys(self):
View Source
    def keys(self):
        "D.keys() -> a set-like object providing a view on D's keys"
        return KeysView(self)

D.keys() -> a set-like object providing a view on D's keys

#  def pop(self, key, default=<object object>):
View Source
    def pop(self, key, default=__marker):
        '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
          If key is not found, d is returned if given, otherwise KeyError is raised.
        '''
        try:
            value = self[key]
        except KeyError:
            if default is self.__marker:
                raise
            return default
        else:
            del self[key]
            return value

D.pop(k[,d]) -> v, remove specified key and return the corresponding value. If key is not found, d is returned if given, otherwise KeyError is raised.

#  def popitem(self):
View Source
    def popitem(self):
        '''D.popitem() -> (k, v), remove and return some (key, value) pair
           as a 2-tuple; but raise KeyError if D is empty.
        '''
        try:
            key = next(iter(self))
        except StopIteration:
            raise KeyError from None
        value = self[key]
        del self[key]
        return key, value

D.popitem() -> (k, v), remove and return some (key, value) pair as a 2-tuple; but raise KeyError if D is empty.

#  def setdefault(self, key, default=None):
View Source
    def setdefault(self, key, default=None):
        'D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D'
        try:
            return self[key]
        except KeyError:
            self[key] = default
        return default

D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D

#  def update(self, other=(), /, **kwds):
View Source
    def update(self, other=(), /, **kwds):
        ''' D.update([E, ]**F) -> None.  Update D from mapping/iterable E and F.
            If E present and has a .keys() method, does:     for k in E: D[k] = E[k]
            If E present and lacks .keys() method, does:     for (k, v) in E: D[k] = v
            In either case, this is followed by: for k, v in F.items(): D[k] = v
        '''
        if isinstance(other, Mapping):
            for key in other:
                self[key] = other[key]
        elif hasattr(other, "keys"):
            for key in other.keys():
                self[key] = other[key]
        else:
            for key, value in other:
                self[key] = value
        for key, value in kwds.items():
            self[key] = value

D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

#  def values(self):
View Source
    def values(self):
        "D.values() -> an object providing a view on D's values"
        return ValuesView(self)

D.values() -> an object providing a view on D's values

View Source
class FreezableDict(ExtendedMutableMapping[KeyT, ValueT]):
    """A mapping that wraps a dict, but can also be frozen."""

    __slots__: typing.Sequence[str] = ("_data",)

    def __init__(self, source: typing.Optional[typing.Dict[KeyT, ValueT]] = None, /) -> None:
        self._data = source or {}

    def clear(self) -> None:
        self._data.clear()

    def copy(self) -> FreezableDict[KeyT, ValueT]:
        return FreezableDict(self._data.copy())

    # TODO: name this something different if it is not physically frozen.
    def freeze(self) -> typing.Dict[KeyT, ValueT]:
        return self._data.copy()

    def __delitem__(self, key: KeyT) -> None:
        del self._data[key]

    def __getitem__(self, key: KeyT) -> ValueT:
        return self._data[key]

    def __iter__(self) -> typing.Iterator[KeyT]:
        return iter(self._data)

    def __len__(self) -> int:
        return len(self._data)

    def __setitem__(self, key: KeyT, value: ValueT) -> None:
        self._data[key] = value

A mapping that wraps a dict, but can also be frozen.

Methods
#  def __init__(self, source: Optional[Dict[~KeyT, ~ValueT]] = None, /):
View Source
    def __init__(self, source: typing.Optional[typing.Dict[KeyT, ValueT]] = None, /) -> None:
        self._data = source or {}
#  def clear(self) -> None:
View Source
    def clear(self) -> None:
        self._data.clear()

D.clear() -> None. Remove all items from D.

#  def copy(self) -> hikari.internal.collections.FreezableDict[~KeyT, ~ValueT]:
View Source
    def copy(self) -> FreezableDict[KeyT, ValueT]:
        return FreezableDict(self._data.copy())

Return a copy of this mapped collection.

Unlike simply doing dict(mapping), this may rely on internal detail around how the data is being stored to allow for a more efficient copy. This may look like calling dict.copy and wrapping the result in a mapped collection.

Note: Any removal policy on this mapped collection will be copied over.

Returns
  • MapT[KeyT, ValueT]: A copy of this mapped collection.
#  def freeze(self) -> Dict[~KeyT, ~ValueT]:
View Source
    def freeze(self) -> typing.Dict[KeyT, ValueT]:
        return self._data.copy()

Return a frozen mapping view of the items in this mapped collection.

Unlike simply doing dict(mapping), this may rely on internal detail around how the data is being stored to allow for a more efficient copy. This may look like calling dict.copy.

Note: Unlike ExtendedMutableMapping.copy, this should return a pure mapping with no removal policy at all.

Returns
  • typing.MutableMapping[KeyT, ValueT]: A frozen mapping view of the items in this mapped collection.
#  def get(self, key, default=None):
View Source
    def get(self, key, default=None):
        'D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None.'
        try:
            return self[key]
        except KeyError:
            return default

D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.

#  def items(self):
View Source
    def items(self):
        "D.items() -> a set-like object providing a view on D's items"
        return ItemsView(self)

D.items() -> a set-like object providing a view on D's items

#  def keys(self):
View Source
    def keys(self):
        "D.keys() -> a set-like object providing a view on D's keys"
        return KeysView(self)

D.keys() -> a set-like object providing a view on D's keys

#  def pop(self, key, default=<object object>):
View Source
    def pop(self, key, default=__marker):
        '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
          If key is not found, d is returned if given, otherwise KeyError is raised.
        '''
        try:
            value = self[key]
        except KeyError:
            if default is self.__marker:
                raise
            return default
        else:
            del self[key]
            return value

D.pop(k[,d]) -> v, remove specified key and return the corresponding value. If key is not found, d is returned if given, otherwise KeyError is raised.

#  def popitem(self):
View Source
    def popitem(self):
        '''D.popitem() -> (k, v), remove and return some (key, value) pair
           as a 2-tuple; but raise KeyError if D is empty.
        '''
        try:
            key = next(iter(self))
        except StopIteration:
            raise KeyError from None
        value = self[key]
        del self[key]
        return key, value

D.popitem() -> (k, v), remove and return some (key, value) pair as a 2-tuple; but raise KeyError if D is empty.

#  def setdefault(self, key, default=None):
View Source
    def setdefault(self, key, default=None):
        'D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D'
        try:
            return self[key]
        except KeyError:
            self[key] = default
        return default

D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D

#  def update(self, other=(), /, **kwds):
View Source
    def update(self, other=(), /, **kwds):
        ''' D.update([E, ]**F) -> None.  Update D from mapping/iterable E and F.
            If E present and has a .keys() method, does:     for k in E: D[k] = E[k]
            If E present and lacks .keys() method, does:     for (k, v) in E: D[k] = v
            In either case, this is followed by: for k, v in F.items(): D[k] = v
        '''
        if isinstance(other, Mapping):
            for key in other:
                self[key] = other[key]
        elif hasattr(other, "keys"):
            for key in other.keys():
                self[key] = other[key]
        else:
            for key, value in other:
                self[key] = value
        for key, value in kwds.items():
            self[key] = value

D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

#  def values(self):
View Source
    def values(self):
        "D.values() -> an object providing a view on D's values"
        return ValuesView(self)

D.values() -> an object providing a view on D's values

#  KeyT

Type-hint A type hint used for the type of a mapping's key.

View Source
class LimitedCapacityCacheMap(ExtendedMutableMapping[KeyT, ValueT]):
    """Implementation of a capacity-limited most-recently-inserted mapping.

    This will start removing the oldest entries after it's maximum capacity is
    reached as new entries are added.

    Parameters
    ----------
    limit : int
        The limit for how many objects should be stored by this mapping before
        it starts removing the oldest entries.

    Other Parameters
    ----------------
    source : typing.Optional[typing.Dict[KeyT, ValueT]]
        A source dictionary of keys to values to create this from.
    on_expire : typing.Optional[typing.Callable[[ValueT], None]]
        A function to call each time an item is garbage collected from this
        map. This should take one positional argument of the same type stored
        in this mapping as the value and should return `None.

        This will always be called after the entry has been removed.
    """

    __slots__: typing.Sequence[str] = ("_data", "_limit", "_on_expire")

    def __init__(
        self,
        source: typing.Optional[typing.Dict[KeyT, ValueT]] = None,
        /,
        *,
        limit: int,
        on_expire: typing.Optional[typing.Callable[[ValueT], None]] = None,
    ) -> None:
        self._data: typing.Dict[KeyT, ValueT] = source or {}
        self._limit = limit
        self._on_expire = on_expire
        self._garbage_collect()

    def clear(self) -> None:
        self._data.clear()

    def copy(self) -> LimitedCapacityCacheMap[KeyT, ValueT]:
        return LimitedCapacityCacheMap(self._data.copy(), limit=self._limit, on_expire=self._on_expire)

    def freeze(self) -> typing.Dict[KeyT, ValueT]:
        return self._data.copy()

    def _garbage_collect(self) -> None:
        while len(self._data) > self._limit:
            value = self._data.pop(next(iter(self._data)))

            if self._on_expire:
                self._on_expire(value)

    def __delitem__(self, key: KeyT) -> None:
        del self._data[key]

    def __getitem__(self, key: KeyT) -> ValueT:
        return self._data[key]

    def __iter__(self) -> typing.Iterator[KeyT]:
        return iter(self._data)

    def __len__(self) -> int:
        return len(self._data)

    def __setitem__(self, key: KeyT, value: ValueT) -> None:
        self._data[key] = value
        self._garbage_collect()

Implementation of a capacity-limited most-recently-inserted mapping.

This will start removing the oldest entries after it's maximum capacity is reached as new entries are added.

Parameters
  • limit (int): The limit for how many objects should be stored by this mapping before it starts removing the oldest entries.
Other Parameters
  • source (typing.Optional[typing.Dict[KeyT, ValueT]]): A source dictionary of keys to values to create this from.
  • on_expire (typing.Optional[typing.Callable[[ValueT], None]]): A function to call each time an item is garbage collected from this map. This should take one positional argument of the same type stored in this mapping as the value and should return `None.

    This will always be called after the entry has been removed.

Methods
#  def __init__(
   self,
   source: Optional[Dict[~KeyT, ~ValueT]] = None,
   /,
   *,
   limit: int,
   on_expire: Optional[Callable[[~ValueT], NoneType]] = None
):
View Source
    def __init__(
        self,
        source: typing.Optional[typing.Dict[KeyT, ValueT]] = None,
        /,
        *,
        limit: int,
        on_expire: typing.Optional[typing.Callable[[ValueT], None]] = None,
    ) -> None:
        self._data: typing.Dict[KeyT, ValueT] = source or {}
        self._limit = limit
        self._on_expire = on_expire
        self._garbage_collect()
#  def clear(self) -> None:
View Source
    def clear(self) -> None:
        self._data.clear()

D.clear() -> None. Remove all items from D.

#  def copy(
   self
) -> hikari.internal.collections.LimitedCapacityCacheMap[~KeyT, ~ValueT]:
View Source
    def copy(self) -> LimitedCapacityCacheMap[KeyT, ValueT]:
        return LimitedCapacityCacheMap(self._data.copy(), limit=self._limit, on_expire=self._on_expire)

Return a copy of this mapped collection.

Unlike simply doing dict(mapping), this may rely on internal detail around how the data is being stored to allow for a more efficient copy. This may look like calling dict.copy and wrapping the result in a mapped collection.

Note: Any removal policy on this mapped collection will be copied over.

Returns
  • MapT[KeyT, ValueT]: A copy of this mapped collection.
#  def freeze(self) -> Dict[~KeyT, ~ValueT]:
View Source
    def freeze(self) -> typing.Dict[KeyT, ValueT]:
        return self._data.copy()

Return a frozen mapping view of the items in this mapped collection.

Unlike simply doing dict(mapping), this may rely on internal detail around how the data is being stored to allow for a more efficient copy. This may look like calling dict.copy.

Note: Unlike ExtendedMutableMapping.copy, this should return a pure mapping with no removal policy at all.

Returns
  • typing.MutableMapping[KeyT, ValueT]: A frozen mapping view of the items in this mapped collection.
#  def get(self, key, default=None):
View Source
    def get(self, key, default=None):
        'D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None.'
        try:
            return self[key]
        except KeyError:
            return default

D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.

#  def items(self):
View Source
    def items(self):
        "D.items() -> a set-like object providing a view on D's items"
        return ItemsView(self)

D.items() -> a set-like object providing a view on D's items

#  def keys(self):
View Source
    def keys(self):
        "D.keys() -> a set-like object providing a view on D's keys"
        return KeysView(self)

D.keys() -> a set-like object providing a view on D's keys

#  def pop(self, key, default=<object object>):
View Source
    def pop(self, key, default=__marker):
        '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
          If key is not found, d is returned if given, otherwise KeyError is raised.
        '''
        try:
            value = self[key]
        except KeyError:
            if default is self.__marker:
                raise
            return default
        else:
            del self[key]
            return value

D.pop(k[,d]) -> v, remove specified key and return the corresponding value. If key is not found, d is returned if given, otherwise KeyError is raised.

#  def popitem(self):
View Source
    def popitem(self):
        '''D.popitem() -> (k, v), remove and return some (key, value) pair
           as a 2-tuple; but raise KeyError if D is empty.
        '''
        try:
            key = next(iter(self))
        except StopIteration:
            raise KeyError from None
        value = self[key]
        del self[key]
        return key, value

D.popitem() -> (k, v), remove and return some (key, value) pair as a 2-tuple; but raise KeyError if D is empty.

#  def setdefault(self, key, default=None):
View Source
    def setdefault(self, key, default=None):
        'D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D'
        try:
            return self[key]
        except KeyError:
            self[key] = default
        return default

D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D

#  def update(self, other=(), /, **kwds):
View Source
    def update(self, other=(), /, **kwds):
        ''' D.update([E, ]**F) -> None.  Update D from mapping/iterable E and F.
            If E present and has a .keys() method, does:     for k in E: D[k] = E[k]
            If E present and lacks .keys() method, does:     for (k, v) in E: D[k] = v
            In either case, this is followed by: for k, v in F.items(): D[k] = v
        '''
        if isinstance(other, Mapping):
            for key in other:
                self[key] = other[key]
        elif hasattr(other, "keys"):
            for key in other.keys():
                self[key] = other[key]
        else:
            for key, value in other:
                self[key] = value
        for key, value in kwds.items():
            self[key] = value

D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. If E present and has a .keys() method, does: for k in E: D[k] = E[k] If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v

#  def values(self):
View Source
    def values(self):
        "D.values() -> an object providing a view on D's values"
        return ValuesView(self)

D.values() -> an object providing a view on D's values

#  class SnowflakeSet(typing.MutableSet[hikari.snowflakes.Snowflake]):
View Source
class SnowflakeSet(typing.MutableSet[snowflakes.Snowflake]):
    r"""Set of `hikari.snowflakes.Snowflake` objects.

    This internally uses a sorted bisect-array of 64 bit integers to represent
    the information. This reduces the space needed to store these objects
    significantly down to the size of 8 bytes per item.

    In contrast, a regular list would take generally 8 bytes per item just to
    store the memory address, and then a further 28 bytes or more to physically
    store the integral value if it does not get interned by the Python
    implementation you are using. A regular set would consume even more
    space, being a hashtable internally.

    The detail of this implementation has the side effect that searches
    will take $$ \mathcal{O} \left( \log n \right) $$ operations in the worst
    case, and $$ \Omega \left (k \right) $$ in the best case. Average case
    will be $$ \mathcal{O} \left( \log n \right) $$

    Insertions and removals will take $$ \mathcal{O} \left( \log n \right) $$
    operations in the worst case, due to `bisect` using a binary insertion sort
    algorithm internally. Average case will be
    $$ \mathcal{O} \left( \log n \right) $$ and best case will be
    $$ \Omega \left\( k \right) $$

    .. warning::
        This is not thread-safe and must not be iterated across whilst being
        concurrently modified.

    Other Parameters
    ----------------
    *ids : int
        The IDs to fill this table with.
    """

    __slots__: typing.Sequence[str] = ("_ids",)

    def __init__(self, *ids: int) -> None:
        self._ids: typing.MutableSequence[int] = array.array(_LONG_LONG_UNSIGNED)

        if ids:
            self.add_all(ids)

    def add(self, value: int, /) -> None:
        """Add a snowflake to this set."""
        # Always append the first item, as we cannot compare with nothing.
        index = bisect.bisect_left(self._ids, value)
        if index == len(self._ids):
            self._ids.append(value)
        elif self._ids[index] != value:
            self._ids.insert(index, value)

    def add_all(self, sfs: typing.Iterable[int], /) -> None:
        """Add a collection of snowflakes to this set."""
        if not sfs:
            return

        for sf in sfs:
            # Yes, this is repeated code, but we want insertions to be as fast
            # as possible for caching purposes, so reduce the number of function
            # calls as much as possible and reimplement the logic for `add`
            # here.
            index = bisect.bisect_left(self._ids, sf)
            if index == len(self._ids):
                self._ids.append(sf)
            elif self._ids[index] != sf:
                self._ids.insert(index, sf)

    def clear(self) -> None:
        """Clear all items from this collection."""
        # Arrays lack a "clear" method.
        self._ids = array.array(_LONG_LONG_UNSIGNED)

    def discard(self, value: int, /) -> None:
        """Remove a snowflake from this set if it's present."""
        if not self._ids:
            return

        index = bisect.bisect_left(self._ids, value)

        if index < len(self) and self._ids[index] == value:
            del self._ids[index]

    def __contains__(self, value: typing.Any) -> bool:
        if not isinstance(value, int):
            return False

        index = bisect.bisect_left(self._ids, value)

        if index < len(self._ids):
            return self._ids[index] == value
        return False

    def __iter__(self) -> typing.Iterator[snowflakes.Snowflake]:
        return map(snowflakes.Snowflake, self._ids)

    def __len__(self) -> int:
        return len(self._ids)

    def __repr__(self) -> str:
        return type(self).__name__ + "(" + ", ".join(map(repr, self._ids)) + ")"

    def __sizeof__(self) -> int:
        return super().__sizeof__() + sys.getsizeof(self._ids)

    def __str__(self) -> str:
        return "{" + ", ".join(map(repr, self._ids)) + "}"

Set of hikari.snowflakes.Snowflake objects.

This internally uses a sorted bisect-array of 64 bit integers to represent the information. This reduces the space needed to store these objects significantly down to the size of 8 bytes per item.

In contrast, a regular list would take generally 8 bytes per item just to store the memory address, and then a further 28 bytes or more to physically store the integral value if it does not get interned by the Python implementation you are using. A regular set would consume even more space, being a hashtable internally.

The detail of this implementation has the side effect that searches will take $$ \mathcal{O} \left( \log n \right) $$ operations in the worst case, and $$ \Omega \left (k \right) $$ in the best case. Average case will be $$ \mathcal{O} \left( \log n \right) $$

Insertions and removals will take $$ \mathcal{O} \left( \log n \right) $$ operations in the worst case, due to bisect using a binary insertion sort algorithm internally. Average case will be $$ \mathcal{O} \left( \log n \right) $$ and best case will be $$ \Omega \left( k \right) $$

Warning: This is not thread-safe and must not be iterated across whilst being concurrently modified.

Other Parameters
  • *ids (int): The IDs to fill this table with.
Methods
#  def __init__(self, *ids: int):
View Source
    def __init__(self, *ids: int) -> None:
        self._ids: typing.MutableSequence[int] = array.array(_LONG_LONG_UNSIGNED)

        if ids:
            self.add_all(ids)
#  def add(self, value: int, /) -> None:
View Source
    def add(self, value: int, /) -> None:
        """Add a snowflake to this set."""
        # Always append the first item, as we cannot compare with nothing.
        index = bisect.bisect_left(self._ids, value)
        if index == len(self._ids):
            self._ids.append(value)
        elif self._ids[index] != value:
            self._ids.insert(index, value)

Add a snowflake to this set.

#  def add_all(self, sfs: Iterable[int], /) -> None:
View Source
    def add_all(self, sfs: typing.Iterable[int], /) -> None:
        """Add a collection of snowflakes to this set."""
        if not sfs:
            return

        for sf in sfs:
            # Yes, this is repeated code, but we want insertions to be as fast
            # as possible for caching purposes, so reduce the number of function
            # calls as much as possible and reimplement the logic for `add`
            # here.
            index = bisect.bisect_left(self._ids, sf)
            if index == len(self._ids):
                self._ids.append(sf)
            elif self._ids[index] != sf:
                self._ids.insert(index, sf)

Add a collection of snowflakes to this set.

#  def clear(self) -> None:
View Source
    def clear(self) -> None:
        """Clear all items from this collection."""
        # Arrays lack a "clear" method.
        self._ids = array.array(_LONG_LONG_UNSIGNED)

Clear all items from this collection.

#  def discard(self, value: int, /) -> None:
View Source
    def discard(self, value: int, /) -> None:
        """Remove a snowflake from this set if it's present."""
        if not self._ids:
            return

        index = bisect.bisect_left(self._ids, value)

        if index < len(self) and self._ids[index] == value:
            del self._ids[index]

Remove a snowflake from this set if it's present.

#  def isdisjoint(self, other):
View Source
    def isdisjoint(self, other):
        'Return True if two sets have a null intersection.'
        for value in other:
            if value in self:
                return False
        return True

Return True if two sets have a null intersection.

#  def pop(self):
View Source
    def pop(self):
        """Return the popped value.  Raise KeyError if empty."""
        it = iter(self)
        try:
            value = next(it)
        except StopIteration:
            raise KeyError from None
        self.discard(value)
        return value

Return the popped value. Raise KeyError if empty.

#  def remove(self, value):
View Source
    def remove(self, value):
        """Remove an element. If not a member, raise a KeyError."""
        if value not in self:
            raise KeyError(value)
        self.discard(value)

Remove an element. If not a member, raise a KeyError.

#  ValueT

Type-hint A type hint used for the type of a mapping's value.

#  def get_index_or_slice(
   mapping: Mapping[~KeyT, ~ValueT],
   index_or_slice: Union[int, slice]
) -> Union[~ValueT, Sequence[~ValueT]]:
View Source
def get_index_or_slice(
    mapping: typing.Mapping[KeyT, ValueT], index_or_slice: typing.Union[int, slice]
) -> typing.Union[ValueT, typing.Sequence[ValueT]]:
    """Get a mapping's entry at a given index as if it's a sequence of it's values.

    Parameters
    ----------
    mapping : typing.Mapping[KeyT, ValueT]
        The mapping of entries to treat as a sequence.
    index_or_slice : typing.Sequence[KeyT, ValueT]
        The index to get an entry to get or slice of multiple entries to get.

    Returns
    -------
    ValueT or typing.Sequence[ValueT]
        The value found at the given integer index or a sequence of the values
        found based on the given slice's rules.

    Raises
    ------
    TypeError
        If `index_or_slice` isn't a `slice` or `int`.
    IndexError
        If `index_or_slice` is an int and is outside the range of the mapping's
        contents.
    """
    if isinstance(index_or_slice, slice):
        return tuple(itertools.islice(mapping.values(), index_or_slice.start, index_or_slice.stop, index_or_slice.step))

    if isinstance(index_or_slice, int):
        try:
            return next(itertools.islice(mapping.values(), index_or_slice, None))
        except StopIteration:
            raise IndexError(index_or_slice) from None

    raise TypeError(f"sequence indices must be integers or slices, not {type(index_or_slice).__name__}")

Get a mapping's entry at a given index as if it's a sequence of it's values.

Parameters
  • mapping (typing.Mapping[KeyT, ValueT]): The mapping of entries to treat as a sequence.
  • index_or_slice (typing.Sequence[KeyT, ValueT]): The index to get an entry to get or slice of multiple entries to get.
Returns
  • ValueT or typing.Sequence[ValueT]: The value found at the given integer index or a sequence of the values found based on the given slice's rules.
Raises
  • TypeError: If index_or_slice isn't a slice or int.
  • IndexError: If index_or_slice is an int and is outside the range of the mapping's contents.