Browse Source

chore: Update ScheduledEventRecurrenceRule implementation

pull/9685/head
DA-344 5 months ago
parent
commit
74a459f465
  1. 408
      discord/scheduled_event.py
  2. 2
      discord/types/scheduled_event.py

408
discord/scheduled_event.py

@ -71,6 +71,7 @@ if TYPE_CHECKING:
GuildScheduledEventPayload = Union[BaseGuildScheduledEventPayload, GuildScheduledEventWithUserCountPayload] GuildScheduledEventPayload = Union[BaseGuildScheduledEventPayload, GuildScheduledEventWithUserCountPayload]
Week = Literal[1, 2, 3, 4, 5] Week = Literal[1, 2, 3, 4, 5]
NWeekday = Tuple[Week, ScheduledEventRecurrenceWeekday] NWeekday = Tuple[Week, ScheduledEventRecurrenceWeekday]
WeekDay = Literal[0, 1, 2, 3, 4, 5, 6]
# fmt: off # fmt: off
__all__ = ( __all__ = (
@ -81,343 +82,210 @@ __all__ = (
class ScheduledEventRecurrenceRule: class ScheduledEventRecurrenceRule:
"""The recurrence rule for a scheduled event. """Represents a :class:`ScheduledEvent`'s recurrence rule.
.. versionadded:: 2.5
Parameters Parameters
---------- ----------
start: :class:`datetime.datetime` start_date: :class:`datetime.datetime`
The datetime when the recurrence interval starts. When will this recurrence rule start.
frequency: :class:`ScheduledEventRecurrenceFrequency` frequency: :class:`ScheduledEventRecurrenceFrequency`
How often the event occurs. The frequency on which the event will recur.
interval: :class:`int` interval: :class:`int`
The spacing between the events, defined by ``frequency``. The spacing between events, defined by ``frequency``.
weekdays: List[:class:`int`]
This must be ``1``, except when ``frequency`` is :attr:`ScheduledEventRecurrenceFrequency.weekly`, The days within a week the event will recur on. Must be between
in which case it can be set to ``2``. 0 (Monday) and 6 (Sunday).
weekdays: List[:class:`ScheduledEventRecurrenceWeekday`]
The weekdays the event will recur on.
Currently this only allows the following sets:
If ``frequency`` is :attr`ScheduledEventRecurrenceFrequency.daily`:
- Monday to Friday
- Tuesday to Saturday
- Sunday to Thursday
- Friday & Saturday
- Saturday & Sunday
- Sunday & Monday
If ``frequency`` is :attr:`ScheduledEventRecurrenceFrequency.weekly`:
The list length can only be up to 1, every day is valid.
n_weekdays: List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]] n_weekdays: List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]]
A (week, weekday) tuple list of the N weekdays the event will recur on. A (week, weekday) pairs list that represent the specific day within a
month_days: List[:class:`datetime.date`] specific week the event will recur on.
The months and month days the scheduled event will recur on.
Examples
--------
Creating a recurrence rule that repeats every weekday ::
recurrence_rule = discord.ScheduledEventRecurrenceRule( ``week`` must be between 1 and 5, representing the first and last week of a month
start=datetime.datetime(...), respectively.
frequency=discord.ScheduledEventRecurrenceFrequency.daily, ``weekday`` must be a :class:`ScheduledEventRecurrenceWeekday` enum member.
interval=1, month_days: List[:class:`datetime.date`]
weekdays=[...], # Pass any valid set of weekdays in here. The specific days and months in which the event will recur on. The year will be ignored.
)
Creating a recurrence rule that repeats every (other) Wednesday ::
recurrence_rule = discord.ScheduledEventRecurrenceRule(
start=datetime.datetime(...),
frequency=discord.ScheduledEventRecurrenceFrequency.weekly,
interval=..., # Here you can either pass 1 or 2, if you pass 1
# then the recurrence rule is "Every Wednesday", if you pass 2
# then the recurrence rule is "Every other Wednesday"
weekdays=[...], # Only 1 item is allowed as frequency is weekly
)
Creating a recurrence rule that repeats monthly on the fourth Wednesday ::
recurrence_rule = discord.ScheduledEventRecurrenceRule(
start=datetime.datetime(...),
frequency=discord.ScheduledEventRecurrenceFrequency.monthly,
interval=1,
n_weekdays=[(4, discord.ScheduledEventRecurrenceWeekday.wednesday)],
)
Creating a recurrence rule that repeats anually on July 24 :: Attributes
----------
recurrence_rule = discord.ScheduledEventRecurrenceRule( end_date: Optional[:class:`datetime.datetime`]
start=datetime.datetime(...), The date on which this recurrence rule will stop.
frequency=discord.ScheduledEventRecurrenceFrequency.yearly, count: Optional[:class:`int`]
interval=1, The amount of times the event will recur before stopping. Will be ``None``
month_days=[ if :attr:`ScheduledEventRecurrenceRule.end_date` is ``None``.
datetime.date(
1900, # This is a placeholder year, it is ignored so any value is valid
7, # July
24, # 24th
)
]
)
""" """
__slots__ = (
# Attributes user can set:
'start',
'frequency',
'interval',
'_weekdays',
'_n_weekdays',
'_month_days',
# Attributes that are returned by API only:
'_count',
'_end',
'_year_days',
)
@overload
def __init__(
self,
/,
start: datetime,
frequency: Literal[
ScheduledEventRecurrenceFrequency.daily,
ScheduledEventRecurrenceFrequency.weekly,
],
interval: int,
*,
weekdays: List[ScheduledEventRecurrenceWeekday],
) -> None:
...
@overload
def __init__(
self,
/,
start: datetime,
frequency: Literal[ScheduledEventRecurrenceFrequency.monthly],
interval: int,
*,
n_weekdays: List[NWeekday],
) -> None:
...
@overload
def __init__(
self,
/,
start: datetime,
frequency: Literal[ScheduledEventRecurrenceFrequency.yearly],
interval: int,
*,
month_days: List[date],
) -> None:
...
def __init__( def __init__(
self, self,
/, start_date: datetime,
start: datetime,
frequency: ScheduledEventRecurrenceFrequency, frequency: ScheduledEventRecurrenceFrequency,
interval: int, interval: Literal[1, 2],
*, *,
weekdays: List[ScheduledEventRecurrenceWeekday] = MISSING, weekdays: List[WeekDay] = MISSING,
n_weekdays: List[NWeekday] = MISSING, n_weekdays: List[NWeekday] = MISSING,
month_days: List[date] = MISSING, month_days: List[date] = MISSING,
) -> None: ) -> None:
self.start: datetime = start self.start_date: datetime = start_date
self.frequency: ScheduledEventRecurrenceFrequency = frequency self.frequency: ScheduledEventRecurrenceFrequency = frequency
self.interval: int = interval self.interval: Literal[1, 2] = interval
self._count: Optional[int] = None
self._end: Optional[datetime] = None
self._year_days: Optional[List[int]] = None
# We will be keeping the MISSING values for future use in _to_dict()
self._weekdays: Optional[List[ScheduledEventRecurrenceWeekday]] = weekdays
self._n_weekdays: Optional[List[NWeekday]] = n_weekdays
self._month_days: Optional[List[date]] = month_days
def __repr__(self) -> str: self.count: Optional[int] = None
return f'<ScheduledEventRecurrenceRule start={self.start!r} frequency={self.frequency} interval={self.interval!r}>' self.end_date: Optional[datetime] = None
self._weekdays: List[WeekDay] = weekdays
self._n_weekdays: List[NWeekday] = n_weekdays
self._month_days: List[date] = month_days
self._year_days: List[int] = MISSING
@property @property
def weekdays(self) -> Optional[List[ScheduledEventRecurrenceWeekday]]: def weekdays(self) -> List[WeekDay]:
"""Optional[List[:class:`ScheduledEventRecurrenceWeekday`]]: Returns a read-only list of the """List[:class:`int`]: Returns a read-only list containing all the specific
weekdays this event recurs on, or ``None``. days within a week the event will recur on.
""" """
if self._weekdays in (MISSING, None): if self._weekdays is MISSING:
return None return []
return self._weekdays.copy() return self._weekdays.copy()
@weekdays.setter
def weekdays(self, new: Optional[List[ScheduledEventRecurrenceWeekday]]) -> None:
self._weekdays = new
@property @property
def n_weekdays(self) -> Optional[List[NWeekday]]: def n_weekdays(self) -> List[NWeekday]:
"""Optional[List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]]]: Returns a """List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]]: Returns
read-only list of the N weekdays this event recurs on, or ``None``. a read-only list containing all the specific days within a specific week the
event will recur on.
""" """
if self._n_weekdays in (MISSING, None): if self._n_weekdays is MISSING:
return None return []
return self._n_weekdays.copy() return self._n_weekdays.copy()
@n_weekdays.setter
def n_weekdays(self, new: Optional[List[NWeekday]]) -> None:
self._n_weekdays = new
@property @property
def month_days(self) -> Optional[List[date]]: def month_days(self) -> List[date]:
"""Optional[List[:class:`datetime.date`]]: Returns a read-only list of the month days this """List[:class:`datetime.date`]: Returns a read-only list containing all the
event recurs on, or ``None``. specific days within a specific month the event will recur on.
""" """
if self._month_days in (MISSING, None): if self._month_days is MISSING:
return None return []
return self._month_days.copy() return self._month_days.copy()
@month_days.setter
def month_days(self, new: Optional[List[date]]) -> None:
self._month_days = new
@property
def end(self) -> Optional[datetime]:
"""Optional[:class:`datetime.datetime`]: The ending time of the recurrence interval,
or ``None``.
"""
return self._end
@property
def count(self) -> Optional[int]:
"""Optional[:class:`int`]: The amount of times the event will recur before stopping,
or ``None`` if it recurs forever.
"""
return self._count
@property @property
def year_days(self) -> Optional[List[int]]: def year_days(self) -> List[int]:
"""Optional[List[:class:`int`]]: Returns a read-only list of the year days this """List[:class:`int`]: Returns a read-only list containing the year days on which
event recurs on, or ``None``. this event will recur on (1-364).
""" """
if self._year_days is None: if self._year_days is MISSING:
return None return []
return self._year_days.copy() return self._year_days.copy()
def replace( def edit(
self, self,
*, *,
weekdays: Optional[List[ScheduledEventRecurrenceWeekday]] = MISSING, weekdays: Optional[List[WeekDay]] = MISSING,
n_weekdays: Optional[List[NWeekday]] = MISSING, n_weekdays: Optional[List[NWeekday]] = MISSING,
month_days: Optional[List[date]] = MISSING, month_days: Optional[List[date]] = MISSING,
) -> Self: ) -> Self:
"""Replaces and returns the recurrence rule with the same values except for the """Edits the current recurrence rule.
ones that are changed.
Parameters Parameters
---------- ----------
weekdays: Optional[List[:class:`ScheduledEventRecurrenceWeekday`]] weekdays: List[:class:`int`]
The new weekdays for the event to recur on. The weekdays the event will recur on. Must be between 0 (Monday) and 6 (Sunday).
n_weekdays: Optional[List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]]] n_weekdays: List[Tuple[:class:`int`, :class:`ScheduledEventRecurrenceWeekday`]]
The new set of specific days within a week for the event to recur on. A (week, weekday) pairs list that the event will recur on.
month_days: Optional[List[:class:`datetime.date`]] month_days: List[:class:`datetime.date`]
The new set of month and month days for the event to recur on. A list of :class:`datetime.date` objects that represent a specific day on a month
when the event will recur on. The year is ignored.
Returns Returns
------- -------
:class:`ScheduledEventRecurrenceRule` :class:`ScheduledEventRecurrenceRule`
The recurrence rule with the replaced values. The updated recurrence rule.
""" """
if weekdays is not MISSING: if weekdays is not MISSING:
self._weekdays = weekdays if weekdays is None:
self._weekdays = MISSING
else:
self._weekdays = weekdays
if n_weekdays is not MISSING: if n_weekdays is not MISSING:
self._n_weekdays = n_weekdays if n_weekdays is None:
self._n_weekdays = MISSING
else:
self._n_weekdays = n_weekdays
if month_days is not MISSING: if month_days is not MISSING:
self._month_days = month_days if month_days is None:
self._month_days = MISSING
else:
self._month_days = month_days
return self return self
def _to_dict(self) -> ScheduledEventRecurrenceRulePayload: def _get_month_days_payload(self) -> Tuple[List[int], List[int]]:
months, days = map(list, zip(*((m.month, m.day) for m in self._month_days)))
return months, days
def _parse_month_days_payload(self, months: List[int], days: List[int]) -> List[date]:
return [
date(1900, month, day)
for month, day in zip(months, days)
]
by_weekday: Optional[List[int]] = None @classmethod
by_n_weekday: Optional[List[NWeekdayPayload]] = None def from_data(cls, data: Optional[ScheduledEventRecurrenceRulePayload]) -> Optional[Self]:
by_month: Optional[List[int]] = None if data is None:
by_month_day: Optional[List[int]] = None return None
by_year_day: Optional[List[int]] = None
start = parse_time(data['start'])
end = parse_time(data.get('end'))
self = cls(
start_date=start,
frequency=try_enum(ScheduledEventRecurrenceFrequency, data['frequency']),
interval=int(data['interval']), # type: ignore
)
self.end_date = end
if self._weekdays not in (MISSING, None): weekdays = data.get('by_weekday', MISSING) or MISSING
by_weekday = [w.value for w in self._weekdays] self._weekdays = weekdays
if self._n_weekdays not in (MISSING, None): n_weekdays = data.get('by_n_weekday', []) or []
by_n_weekday = [{'n': n, 'day': day} for n, day in self._n_weekdays] # type: ignore self._n_weekdays = [
(data['n'], try_enum(ScheduledEventRecurrenceWeekday, data['day']))
for data in n_weekdays
]
if self._month_days not in (MISSING, None): months = data.get('by_month')
by_month = [] month_days = data.get('by_month_day')
by_month_day = []
for dt in self._month_days: if months and month_days:
by_month.append(dt.month) self._month_days = self._parse_month_days_payload(months, month_days)
by_month_day.append(dt.day)
if self.year_days is not None: return self
by_year_day = self.year_days
return { def to_dict(self):
'start': self.start.isoformat(), payload = {
'end': self._end.isoformat() if self._end is not None else None, 'start': self.start_date.isoformat(),
'frequency': self.frequency.value, 'frequency': self.frequency.value,
'interval': self.interval, 'interval': self.interval,
'by_weekday': by_weekday, 'by_weekday': None,
'by_n_weekday': by_n_weekday, 'by_n_weekday': None,
'by_month': by_month, 'by_month': None,
'by_month_day': by_month_day, 'by_month_day': None,
'by_year_day': by_year_day,
'count': self.count,
} }
@classmethod if self._weekdays is not MISSING:
def _from_data(cls, data: ScheduledEventRecurrenceRulePayload, /) -> Self: payload['by_weekday'] = self._weekdays
self = cls(
start=parse_time(data['start']),
frequency=try_enum(ScheduledEventRecurrenceFrequency, data['frequency']),
interval=data['interval'],
) # type: ignore
self._count = data.get('count')
self._year_days = data.get('by_year_day')
end = data.get('end')
if end is not None:
self._end = parse_time(end)
wd_conv = partial(try_enum, ScheduledEventRecurrenceWeekday)
raw_weekdays = data.get('by_weekday')
if raw_weekdays is not None:
self._weekdays = list(map(wd_conv, raw_weekdays))
raw_n_weekdays = data.get('by_n_weekday')
if raw_n_weekdays is not None:
self._n_weekdays = [(n['n'], wd_conv(n['day'])) for n in raw_n_weekdays]
raw_months = data.get('by_month')
raw_month_days = data.get('by_month_day')
if raw_months is not None and raw_month_days is not None:
self._month_days = [
date(
1900, # Using this as a placeholder year, ignored anyways
month,
day,
)
for month, day in zip(raw_months, raw_month_days)
]
return self if self._n_weekdays is not MISSING:
payload['by_n_weekday'] = list(
map(
lambda nw: {'n': nw[0], 'day': nw[1].value},
self._n_weekdays,
),
)
if self._month_days is not MISSING:
months, month_days = self._get_month_days_payload()
payload['by_month'] = months
payload['by_month_day'] = month_days
class ScheduledEvent(Hashable): class ScheduledEvent(Hashable):
@ -527,8 +395,8 @@ class ScheduledEvent(Hashable):
self.user_count: int = data.get('user_count', 0) self.user_count: int = data.get('user_count', 0)
self.creator_id: Optional[int] = _get_as_snowflake(data, 'creator_id') self.creator_id: Optional[int] = _get_as_snowflake(data, 'creator_id')
recurrence_rule = data.get('recurrence_rule') rrule = data.get('recurrence_rule')
self.recurrence_rule = ScheduledEventRecurrenceRule._from_data(recurrence_rule) if recurrence_rule else None self.recurrence_rule = ScheduledEventRecurrenceRule.from_data(rrule)
sku_ids = data.get('sku_ids', []) sku_ids = data.get('sku_ids', [])
self.sku_ids: List[Object] = list(map(Object, sku_ids)) self.sku_ids: List[Object] = list(map(Object, sku_ids))
@ -952,7 +820,7 @@ class ScheduledEvent(Hashable):
if recurrence_rule is not MISSING: if recurrence_rule is not MISSING:
if recurrence_rule is not None: if recurrence_rule is not None:
payload['recurrence_rule'] = recurrence_rule._to_dict() payload['recurrence_rule'] = recurrence_rule.to_dict()
else: else:
payload['recurrence_rule'] = None payload['recurrence_rule'] = None

2
discord/types/scheduled_event.py

@ -45,7 +45,7 @@ class ScheduledEventRecurrenceRule(TypedDict):
end: Optional[str] end: Optional[str]
frequency: ScheduledEventRecurrenceRuleFrequency frequency: ScheduledEventRecurrenceRuleFrequency
interval: int interval: int
by_weekday: Optional[List[int]] by_weekday: Optional[List[Literal[0, 1, 2, 3, 4, 5, 6]]]
by_n_weekday: Optional[List[_NWeekday]] by_n_weekday: Optional[List[_NWeekday]]
by_month: Optional[List[int]] by_month: Optional[List[int]]
by_month_day: Optional[List[int]] by_month_day: Optional[List[int]]

Loading…
Cancel
Save