Source code for topgg.data

# The MIT License (MIT)

# Copyright (c) 2021 Norizon

# 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.

__all__ = ["data", "DataContainerMixin"]

import inspect
import typing as t

from topgg.errors import TopGGException

T = t.TypeVar("T")
DataContainerT = t.TypeVar("DataContainerT", bound="DataContainerMixin")


[docs]def data(type_: t.Type[T]) -> T: """ Represents the injected data. This should be set as the parameter's default value. Args: `type_` (:obj:`type` [ :obj:`T` ]) The type of the injected data. Returns: :obj:`T`: The injected data of type T. :Example: .. code-block:: python import topgg # In this example, we fetch the stats from a Discord client instance. client = Client(...) dblclient = topgg.DBLClient(TOKEN).set_data(client) autopost: topgg.AutoPoster = dblclient.autopost() @autopost.stats() def get_stats(client: Client = topgg.data(Client)): return topgg.StatsWrapper(guild_count=len(client.guilds), shard_count=len(client.shards)) """ return t.cast(T, Data(type_))
class Data(t.Generic[T]): __slots__ = ("type",) def __init__(self, type_: t.Type[T]) -> None: self.type: t.Type[T] = type_
[docs]class DataContainerMixin: """ A class that holds data. This is useful for injecting some data so that they're available as arguments in your functions. """ __slots__ = ("_data",) def __init__(self) -> None: self._data: t.Dict[t.Type, t.Any] = {type(self): self}
[docs] def set_data( self: DataContainerT, data_: t.Any, *, override: bool = False ) -> DataContainerT: """ Sets data to be available in your functions. Args: `data_` (:obj:`typing.Any`) The data to be injected. override (:obj:`bool`) Whether or not to override another instance that already exists. Raises: :obj:`~.errors.TopGGException` If override is False and another instance of the same type exists. """ type_ = type(data_) if not override and type_ in self._data: raise TopGGException( f"{type_} already exists. If you wish to override it, pass True into the override parameter." ) self._data[type_] = data_ return self
@t.overload def get_data(self, type_: t.Type[T]) -> t.Optional[T]: ... @t.overload def get_data(self, type_: t.Type[T], default: t.Any = None) -> t.Any: ...
[docs] def get_data(self, type_: t.Any, default: t.Any = None) -> t.Any: """Gets the injected data.""" return self._data.get(type_, default)
async def _invoke_callback( self, callback: t.Callable[..., T], *args: t.Any, **kwargs: t.Any ) -> T: parameters: t.Mapping[str, inspect.Parameter] try: parameters = inspect.signature(callback).parameters except (ValueError, TypeError): parameters = {} signatures: t.Dict[str, Data] = { k: v.default for k, v in parameters.items() if v.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD and isinstance(v.default, Data) } for k, v in signatures.items(): signatures[k] = self._resolve_data(v.type) res = callback(*args, **{**signatures, **kwargs}) if inspect.isawaitable(res): return await res return res def _resolve_data(self, type_: t.Type[T]) -> T: return self._data[type_]