Skip to content

Commit 888467a

Browse files
committed
Merge branch 'master' into state-rewrite
2 parents b113113 + c4e93c4 commit 888467a

18 files changed

+1236
-1115
lines changed

.github/workflows/codeql.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ jobs:
2828
- name: "Checkout repository"
2929
uses: actions/checkout@v5
3030
- name: "Initialize CodeQL"
31-
uses: github/codeql-action/init@v3
31+
uses: github/codeql-action/init@v4
3232
with:
3333
languages: ${{ matrix.language }}
3434
- name: "Autobuild"
35-
uses: github/codeql-action/autobuild@v3
35+
uses: github/codeql-action/autobuild@v4
3636
- name: "Perform CodeQL Analysis"
37-
uses: github/codeql-action/analyze@v3
37+
uses: github/codeql-action/analyze@v4

.github/workflows/docs-checks.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ jobs:
4040
- name: "Setup Python"
4141
uses: actions/setup-python@v6
4242
with:
43-
python-version: "3.13"
43+
python-version: "3.14"
4444
- name: "Install uv"
45-
uses: astral-sh/setup-uv@v6
45+
uses: astral-sh/setup-uv@v7
4646
with:
4747
enable-cache: true
4848
- name: Sync dependencies

.github/workflows/docs-localization-download.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ jobs:
1919
- name: "Setup Python"
2020
uses: actions/setup-python@v6
2121
with:
22-
python-version: "3.13"
22+
python-version: "3.14"
2323
- name: "Install uv"
24-
uses: astral-sh/setup-uv@v6
24+
uses: astral-sh/setup-uv@v7
2525
with:
2626
enable-cache: true
2727
- name: Sync dependencies

.github/workflows/docs-localization-upload.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ jobs:
2424
- name: "Setup Python"
2525
uses: actions/setup-python@v6
2626
with:
27-
python-version: "3.13"
27+
python-version: "3.14"
2828
- name: "Install uv"
29-
uses: astral-sh/setup-uv@v6
29+
uses: astral-sh/setup-uv@v7
3030
with:
3131
enable-cache: true
3232
- name: Sync dependencies

.github/workflows/lib-checks.yml

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@ on:
44
push:
55
paths:
66
- "discord/**"
7-
- "requirements/**"
7+
- "examples/**"
8+
- "scripts/**"
9+
- "tests/**"
810
- "*.toml"
911
- "*.py"
1012
- ".*"
1113
branches: [master]
1214
pull_request:
1315
paths:
1416
- "discord/**"
15-
- "requirements/**"
17+
- "examples/**"
18+
- "scripts/**"
19+
- "tests/**"
1620
- "*.toml"
1721
- "*.py"
1822
- ".*"
@@ -36,9 +40,9 @@ jobs:
3640
- name: "Setup Python"
3741
uses: actions/setup-python@v6
3842
with:
39-
python-version: "3.13"
43+
python-version: "3.14"
4044
- name: "Install uv"
41-
uses: astral-sh/setup-uv@v6
45+
uses: astral-sh/setup-uv@v7
4246
with:
4347
enable-cache: true
4448
- name: Sync dependencies
@@ -55,17 +59,17 @@ jobs:
5559
- name: "Setup Python"
5660
uses: actions/setup-python@v6
5761
with:
58-
python-version: "3.13"
62+
python-version: "3.14"
5963
- name: "Install uv"
60-
uses: astral-sh/setup-uv@v6
64+
uses: astral-sh/setup-uv@v7
6165
with:
6266
enable-cache: true
6367
- name: Sync dependencies
6468
run: uv sync --no-python-downloads --group dev
6569
- name: "Run ruff linter check"
66-
run: uv run ruff check discord/
70+
run: uv run ruff check .
6771
- name: "Run ruff formatter check"
68-
run: uv run ruff format --check discord/
72+
run: uv run ruff format --check .
6973
mypy:
7074
if: ${{ github.event_name != 'schedule' }}
7175
runs-on: ubuntu-latest
@@ -75,9 +79,9 @@ jobs:
7579
- name: "Setup Python"
7680
uses: actions/setup-python@v6
7781
with:
78-
python-version: "3.13"
82+
python-version: "3.14"
7983
- name: "Install uv"
80-
uses: astral-sh/setup-uv@v6
84+
uses: astral-sh/setup-uv@v7
8185
with:
8286
enable-cache: true
8387
- name: Sync dependencies

.github/workflows/sync-guild-features.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ jobs:
2323
- name: "Setup Python"
2424
uses: actions/setup-python@v6
2525
with:
26-
python-version: "3.13"
26+
python-version: "3.14"
2727
- name: "Install uv"
28-
uses: astral-sh/setup-uv@v6
28+
uses: astral-sh/setup-uv@v7
2929
with:
3030
enable-cache: true
3131
- name: Sync dependencies

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ repos:
1010
- id: end-of-file-fixer
1111
exclude: \.(po|pot|yml|yaml)$
1212
- repo: https://github.com/astral-sh/ruff-pre-commit
13-
rev: v0.13.0
13+
rev: v0.14.0
1414
hooks:
1515
- id: ruff
1616
args: [ --fix ]

CHANGELOG-V3.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ release.
99

1010
### Changed
1111

12+
- Removed the custom `enums.Enum` implementation in favor of a stdlib `enum.Enum` subclass.
13+
1214
### Deprecated
1315

1416
### Removed

discord/client.py

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@
6363
from .ui.view import View
6464
from .user import ClientUser, User
6565
from .utils import MISSING
66-
from .utils.private import SequenceProxy, bytes_to_base64_data, resolve_invite, resolve_template
66+
from .utils.private import (
67+
SequenceProxy,
68+
bytes_to_base64_data,
69+
resolve_invite,
70+
resolve_template,
71+
)
6772
from .voice_client import VoiceClient
6873
from .webhook import Webhook
6974
from .widget import Widget
@@ -621,7 +626,10 @@ async def login(self, token: str) -> None:
621626
data = await self.http.static_login(token.strip())
622627
self._connection.user = ClientUser(state=self._connection, data=data)
623628

624-
print_banner(bot_name=self._connection.user.display_name, module=self._banner_module or "discord")
629+
print_banner(
630+
bot_name=self._connection.user.display_name,
631+
module=self._banner_module or "discord",
632+
)
625633
start_logging(self._flavor, debug=self._debug)
626634

627635
async def connect(self, *, reconnect: bool = True) -> None:
@@ -1123,24 +1131,6 @@ async def get_all_members(self) -> AsyncGenerator[Member]:
11231131
for member in guild.members:
11241132
yield member
11251133

1126-
async def get_or_fetch_user(self, id: int, /) -> User | None:
1127-
"""|coro|
1128-
1129-
Looks up a user in the user cache or fetches if not found.
1130-
1131-
Parameters
1132-
----------
1133-
id: :class:`int`
1134-
The ID to search for.
1135-
1136-
Returns
1137-
-------
1138-
Optional[:class:`~discord.User`]
1139-
The user or ``None`` if not found.
1140-
"""
1141-
1142-
return await utils.get_or_fetch(obj=self, attr="user", id=id, default=None)
1143-
11441134
# listeners/waiters
11451135

11461136
async def wait_until_ready(self) -> None:

discord/enums.py

Lines changed: 47 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
from __future__ import annotations
2727

2828
import types
29-
from collections import namedtuple
30-
from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, Union
29+
from enum import Enum as EnumBase
30+
from typing import Any, Self, TypeVar, Union
31+
32+
E = TypeVar("E", bound="Enum")
3133

3234
__all__ = (
3335
"Enum",
@@ -84,118 +86,57 @@
8486
)
8587

8688

87-
def _create_value_cls(name, comparable):
88-
cls = namedtuple(f"_EnumValue_{name}", "name value")
89-
cls.__repr__ = lambda self: f"<{name}.{self.name}: {self.value!r}>"
90-
cls.__str__ = lambda self: f"{name}.{self.name}"
91-
if comparable:
92-
cls.__le__ = lambda self, other: isinstance(other, self.__class__) and self.value <= other.value
93-
cls.__ge__ = lambda self, other: isinstance(other, self.__class__) and self.value >= other.value
94-
cls.__lt__ = lambda self, other: isinstance(other, self.__class__) and self.value < other.value
95-
cls.__gt__ = lambda self, other: isinstance(other, self.__class__) and self.value > other.value
96-
return cls
97-
98-
99-
def _is_descriptor(obj):
100-
return hasattr(obj, "__get__") or hasattr(obj, "__set__") or hasattr(obj, "__delete__")
101-
102-
103-
class EnumMeta(type):
104-
if TYPE_CHECKING:
105-
__name__: ClassVar[str]
106-
_enum_member_names_: ClassVar[list[str]]
107-
_enum_member_map_: ClassVar[dict[str, Any]]
108-
_enum_value_map_: ClassVar[dict[Any, Any]]
109-
110-
def __new__(cls, name, bases, attrs, *, comparable: bool = False):
111-
value_mapping = {}
112-
member_mapping = {}
113-
member_names = []
114-
115-
value_cls = _create_value_cls(name, comparable)
116-
for key, value in list(attrs.items()):
117-
is_descriptor = _is_descriptor(value)
118-
if key[0] == "_" and not is_descriptor:
119-
continue
120-
121-
# Special case classmethod to just pass through
122-
if isinstance(value, classmethod):
123-
continue
124-
125-
if is_descriptor:
126-
setattr(value_cls, key, value)
127-
del attrs[key]
128-
continue
129-
130-
try:
131-
new_value = value_mapping[value]
132-
except KeyError:
133-
new_value = value_cls(name=key, value=value)
134-
value_mapping[value] = new_value
135-
member_names.append(key)
136-
137-
member_mapping[key] = new_value
138-
attrs[key] = new_value
139-
140-
attrs["_enum_value_map_"] = value_mapping
141-
attrs["_enum_member_map_"] = member_mapping
142-
attrs["_enum_member_names_"] = member_names
143-
attrs["_enum_value_cls_"] = value_cls
144-
actual_cls = super().__new__(cls, name, bases, attrs)
145-
value_cls._actual_enum_cls_ = actual_cls # type: ignore
146-
return actual_cls
147-
148-
def __iter__(cls):
149-
return (cls._enum_member_map_[name] for name in cls._enum_member_names_)
150-
151-
def __reversed__(cls):
152-
return (cls._enum_member_map_[name] for name in reversed(cls._enum_member_names_))
153-
154-
def __len__(cls):
155-
return len(cls._enum_member_names_)
89+
class Enum(EnumBase):
90+
"""An :class:`enum.Enum` subclass that implements a missing value creation behavior if it is
91+
not present in any of the members of it.
92+
"""
15693

157-
def __repr__(cls):
158-
return f"<enum {cls.__name__}>"
94+
def __init_subclass__(cls, *, comparable: bool = False) -> None:
95+
super().__init_subclass__()
15996

160-
@property
161-
def __members__(cls):
162-
return types.MappingProxyType(cls._enum_member_map_)
97+
if comparable:
16398

164-
def __call__(cls, value):
165-
try:
166-
return cls._enum_value_map_[value]
167-
except (KeyError, TypeError) as e:
168-
raise ValueError(f"{value!r} is not a valid {cls.__name__}") from e
99+
def __lt__(self: Enum, other: object) -> bool:
100+
if not isinstance(other, cls):
101+
return NotImplemented
102+
return self.value < other.value
169103

170-
def __getitem__(cls, key):
171-
return cls._enum_member_map_[key]
104+
def __gt__(self: Enum, other: object) -> bool:
105+
if not isinstance(other, cls):
106+
return NotImplemented
107+
return self.value > other.value
172108

173-
def __setattr__(cls, name, value):
174-
raise TypeError("Enums are immutable.")
109+
def __le__(self: Enum, other: object) -> bool:
110+
if not isinstance(other, cls):
111+
return NotImplemented
112+
return self.value <= other.value
175113

176-
def __delattr__(cls, attr):
177-
raise TypeError("Enums are immutable")
114+
def __ge__(self: Enum, other: object) -> bool:
115+
if not isinstance(other, cls):
116+
return NotImplemented
117+
return self.value >= other.value
178118

179-
def __instancecheck__(self, instance):
180-
# isinstance(x, Y)
181-
# -> __instancecheck__(Y, x)
182-
try:
183-
return instance._actual_enum_cls_ is self
184-
except AttributeError:
185-
return False
119+
cls.__lt__ = __lt__
120+
cls.__gt__ = __gt__
121+
cls.__le__ = __le__
122+
cls.__ge__ = __ge__
186123

124+
@classmethod
125+
def _missing_(cls, value: Any) -> Self:
126+
name = f"unknown_{value}"
127+
if name in cls.__members__:
128+
return cls.__members__[name]
187129

188-
if TYPE_CHECKING:
189-
from enum import Enum
190-
else:
130+
# this creates the new unknown value member
131+
obj = object.__new__(cls)
132+
obj._name_ = name
133+
obj._value_ = value
191134

192-
class Enum(metaclass=EnumMeta):
193-
@classmethod
194-
def try_value(cls, value):
195-
try:
196-
return cls._enum_value_map_[value]
197-
except (KeyError, TypeError):
198-
return value
135+
# and adds it to the member mapping of this enum so we don't
136+
# create a different enum member value each time
137+
cls._member_map_[name] = obj
138+
cls._value2member_map_[value] = obj
139+
return obj
199140

200141

201142
class ChannelType(Enum):
@@ -1086,23 +1027,15 @@ class ApplicationCommandPermissionType(Enum):
10861027
user = 2
10871028
channel = 3
10881029

1089-
1090-
T = TypeVar("T")
1091-
1092-
10931030
def create_unknown_value(cls: type[T], val: Any) -> T:
10941031
value_cls = cls._enum_value_cls_ # type: ignore
10951032
name = f"unknown_{val}"
10961033
return value_cls(name=name, value=val)
10971034

10981035

1099-
def try_enum(cls: type[T], val: Any) -> T:
1036+
def try_enum(cls: type[E], val: Any) -> E:
11001037
"""A function that tries to turn the value into enum ``cls``.
11011038
11021039
If it fails it returns a proxy invalid value instead.
11031040
"""
1104-
1105-
try:
1106-
return cls._enum_value_map_[val] # type: ignore
1107-
except (KeyError, TypeError, AttributeError):
1108-
return create_unknown_value(cls, val)
1041+
return cls(val)

0 commit comments

Comments
 (0)