1"""Logic for our ratelimit result."""
2import datetime
3import typing
4
5import attr
6
7
8@attr.s(frozen=True)
9class RateLimitResult:
10    """A result of checking a ratelimit.
11
12    The attributes on this object are:
13
14    .. attribute:: limit
15
16        The integer limit that was checked against, e.g., if the user's
17        ratelimit is 10,000 this value will be 10,000 regardless of how much
18        they have consumed.
19
20    .. attribute:: limited
21
22        Whether or not the user should be ratelimited (a.k.a., throttled).
23
24    .. attribute:: remaining
25
26        The integer representing how much of the user's ratelimit is left.
27        This should be the number of requests made during the time period,
28        ``N``, subtracted from the limit, ``L``, or ``L - N``.
29
30    .. attribute:: reset_after
31
32        This will be a :class:`~datetime.timedelta` representing how much time
33        is left until the ratelimit resets. For example if the ratelimit will
34        reset in 800ms then this might look like:
35
36        .. code-block:: python
37
38            datetime.timedelta(0, 0, 800000)
39            # == datetime.timedelta(milliseconds=800)
40
41    .. attribute:: retry_after
42
43        This will be a :class:`~datetime.timedelta` representing the length of
44        time after which a retry can be made.
45
46    """
47
48    limit: int = attr.ib()
49    limited: bool = attr.ib()
50    remaining: int = attr.ib()
51    reset_after: datetime.timedelta = attr.ib()
52    retry_after: datetime.timedelta = attr.ib()
53
54    @staticmethod
55    def _now() -> datetime.datetime:
56        return datetime.datetime.now(datetime.timezone.utc)
57
58    def resets_at(
59        self, from_when: typing.Optional[datetime.datetime] = None
60    ) -> datetime.datetime:
61        """Calculate the reset time from UTC now.
62
63        :returns:
64            The UTC timezone-aware datetime representing when the limit
65            resets.
66        """
67        if from_when is None:
68            from_when = self._now()
69        return from_when + self.reset_after
70
71    def retry_at(
72        self, from_when: typing.Optional[datetime.datetime] = None
73    ) -> datetime.datetime:
74        """Calculate the retry time from UTC now.
75
76        :returns:
77            The UTC timezone-aware datetime representing when the user
78            can retry.
79        """
80        if from_when is None:
81            from_when = self._now()
82        return from_when + self.retry_after
83