Skip to content

refactor: utils #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 69 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
17766df
Move utils.py to utils/
Paillat-dev May 29, 2025
195e2a0
:fire: Remove `filter_params`
Paillat-dev May 29, 2025
fafa149
:recycle: Merge `time_snowflake` and `generate_snowflake`, move `basi…
Paillat-dev May 29, 2025
e2d8eb1
:recycle: Merge `time_snowflake` and `generate_snowflake`, move `basi…
Paillat-dev May 29, 2025
86bd168
:fire: Remove `utils.sleep_until`
Paillat-dev May 29, 2025
23c2c49
chore: Start migration to uv & ruff & hatch (#4)
Paillat-dev May 29, 2025
97ccda5
Setup CHANGELOG.md (#6)
Paillat-dev May 29, 2025
f2a4eb6
chore: update docs workflows to use 'uv' for dependency management (#33)
Paillat-dev May 30, 2025
40e20c0
:fire: Move stuff to private
Paillat-dev May 31, 2025
6e5ccb5
Merge branch 'master' into refactor-utils
Paillat-dev May 31, 2025
1da5de0
:refactor: move parse_time function to private utils and update refer…
Paillat-dev Jun 24, 2025
88180b2
:memo: update CHANGELOG to reflect utility function changes
Paillat-dev Jun 24, 2025
b5c8a58
:art: Format
Paillat-dev Jun 24, 2025
0cdcb31
:recycle: move deprecation utilities to private utils and update refe…
Paillat-dev Jun 24, 2025
09a11a9
:recycle: move snowflake_time function to public.py
Paillat-dev Jun 24, 2025
4a2753c
:recycle: move oauth_url and Undefined class to public.py; update imp…
Paillat-dev Jun 24, 2025
ca89539
Merge branch 'master' into refactor-utils
Paillat-dev Jun 24, 2025
1cf53ab
:memo: remove deprecated utility functions from documentation
Paillat-dev Jun 24, 2025
2ac8152
:memo: remove (re)moved utility functions from documentation
Paillat-dev Jun 24, 2025
24a367c
:memo: add utils.resolve_template to changelog and remove from docume…
Paillat-dev Jun 24, 2025
0c6270a
:bug: fix import path for warn_deprecated utility function
Paillat-dev Jun 24, 2025
84ff9df
:refactor: reorganize utility function imports and move evaluate_anno…
Paillat-dev Jun 24, 2025
8474f05
:recycle: update import paths for utility functions to use relative i…
Paillat-dev Jun 24, 2025
c8ddcb5
:recycle: move delay_task function to private
Paillat-dev Jul 7, 2025
8a02d54
:recycle: removed `utils.get` in favor of `utils.find`
Paillat-dev Jul 7, 2025
39aef0f
:recycle: removed `utils._unique`
Paillat-dev Jul 7, 2025
6b0379c
:recycle: move `async_all` to private
Paillat-dev Jul 7, 2025
cc8f27f
:recycle: move `maybe_coroutine` to private
Paillat-dev Jul 7, 2025
1d70dec
:recycle: rename `maybe_coroutine` to `maybe_awaitable`
Paillat-dev Jul 7, 2025
b2b8625
:recycle: move `sane_wait_for` to private
Paillat-dev Jul 7, 2025
d0a094d
:recycle: move `format_dt` to public
Paillat-dev Jul 7, 2025
fb554fd
:recycle: remove `as_chunks` function
Paillat-dev Jul 7, 2025
1a551cb
:memo: update `utils.sleep_until` and `utils.parse_time` changelog to…
Paillat-dev Jul 7, 2025
295ed46
:recycle: move `compute_timedelta` function
Paillat-dev Jul 7, 2025
4afe54a
:recycle: move `valid_icon_size` to `asset.py`
Paillat-dev Jul 7, 2025
a021450
:recycle: refactor `utils.get` to `utils.find` across multiple files
Paillat-dev Jul 7, 2025
c6f7914
:recycle: refactor markdown and mention handling functions in `__init…
Paillat-dev Jul 7, 2025
c29cdc2
:recycle: move SnowflakeList to `private.py`
Paillat-dev Jul 7, 2025
6c57d5e
:recycle: move `find` function from `__init__.py` to `public.py`
Paillat-dev Jul 7, 2025
d079c7c
:recycle: move `copy_doc` to private
Paillat-dev Jul 7, 2025
611c7c7
:recycle: refactor `get` to `find` in onboarding and sticker modules
Paillat-dev Jul 7, 2025
cd4e436
:bug: fix `copy_doc` decorator usage in context.py
Paillat-dev Jul 7, 2025
ad777e4
Merge branch 'master' into refactor-utils
Paillat-dev Jul 7, 2025
aec45a2
:recycle: move SequenceProxy to private module
Paillat-dev Jul 7, 2025
9755fe8
:recycle: move cached_slot_property to private
Paillat-dev Jul 7, 2025
006a7f8
:rotating_light: add noqa comments to prevent linting errors
Paillat-dev Jul 7, 2025
d5263c7
:recycle: move get_slots function to private module
Paillat-dev Jul 7, 2025
44f753e
:recycle: refactor JSON serialization functions to private module
Paillat-dev Jul 7, 2025
c961e36
:recycle: replace custom cached_property implementation with functool…
Paillat-dev Jul 7, 2025
a4bda5f
:pencil2: fix typo in CHANGELOG-V3.md
Paillat-dev Jul 7, 2025
b81d2d5
:heavy_minus_sign: remove unused dependencies from pyproject.toml and…
Paillat-dev Jul 7, 2025
a84e06d
:coffin: remove test.py
Paillat-dev Jul 7, 2025
330161c
:recycle: remove duplicate import of raw_mentions in __init__.py
Paillat-dev Jul 7, 2025
b3f919e
:bug: fix raw_role_mentions import in utils/__init__.py
Paillat-dev Jul 8, 2025
0c9272d
:fire: Duplicate `Iterable` import
Paillat-dev Aug 4, 2025
2b981dd
Apply suggestion from @Lumabots
Paillat-dev Aug 4, 2025
488c4de
Merge branch 'master' into refactor-utils
Paillat-dev Aug 4, 2025
df155d6
:fire: Simplify imports
Paillat-dev Aug 6, 2025
7d92da0
:fire: Simplify imports number 2
Paillat-dev Aug 6, 2025
856853f
:fire: Simplify imports number 3 omg this is amazing
Paillat-dev Aug 6, 2025
e32ba97
:fire: Simplify imports number 4 omg this is amazing yee
Paillat-dev Aug 6, 2025
d1be746
:fire: Simplify imports number 5
Paillat-dev Aug 6, 2025
bd59784
Update discord/ext/commands/converter.py
Paillat-dev Aug 6, 2025
34d5fde
Apply suggestions from code review
Paillat-dev Aug 6, 2025
5ef78ca
Update discord/state.py
Paillat-dev Aug 6, 2025
0acef26
Update discord/state.py
Paillat-dev Aug 6, 2025
c7db59c
:recycle: Remove _ prefix in from and to json
Paillat-dev Aug 6, 2025
0ca346d
:recycle: Make import less weird
Paillat-dev Aug 6, 2025
2a0427c
Update utils.po
Paillat-dev Aug 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ repos:
- id: end-of-file-fixer
exclude: \.(po|pot|yml|yaml)$
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.9
rev: v0.12.0
hooks:
- id: ruff
args: [ --fix ]
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG-V3.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,17 @@ release.
### Deprecated

### Removed

- `utils.filter_params`
- `utils.sleep_until` use `asyncio.sleep` combined with `datetime.datetime` instead
- `utils.compute_timedelta` use the `datetime` module instead
- `utils.resolve_invite`
- `utils.resolve_template`
- `utils.parse_time` use `datetime.datetime.fromisoformat` instead
- `utils.time_snowflake` use `utils.generate_snowflake` instead
- `utils.warn_deprecated`
- `utils.deprecated`
- `utils.get` use `utils.find` with `lambda i: i.attr == val` instead
- `AsyncIterator.get` use `AsyncIterator.find` with `lambda i: i.attr == val` instead
- `utils.as_chunks` use `itertools.batched` on Python 3.12+ or your own implementation
instead
2 changes: 1 addition & 1 deletion discord/__version.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

from typing import Literal, NamedTuple

from .utils import deprecated
from .utils.private import deprecated
from ._version import __version__, __version_tuple__


Expand Down
5 changes: 3 additions & 2 deletions discord/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
runtime_checkable,
)

from .utils.private import warn_deprecated
from . import utils
from .context_managers import Typing
from .enums import ChannelType
Expand Down Expand Up @@ -724,7 +725,7 @@ def permissions_for(self, obj: Member | Role, /) -> Permissions:
if obj.is_default():
return base

overwrite = utils.get(self._overwrites, type=_Overwrites.ROLE, id=obj.id)
overwrite = utils.find(lambda o: o.type == _Overwrites.ROLE and o.id == obj.id, self._overwrites)
if overwrite is not None:
base.handle_overwrite(overwrite.allow, overwrite.deny)

Expand Down Expand Up @@ -1529,7 +1530,7 @@ async def send(
from .message import MessageReference # noqa: PLC0415

if not isinstance(reference, MessageReference):
utils.warn_deprecated(
warn_deprecated(
f"Passing {type(reference).__name__} to reference",
"MessageReference",
"2.7",
Expand Down
4 changes: 2 additions & 2 deletions discord/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from .colour import Colour
from .enums import ActivityType, try_enum
from .partial_emoji import PartialEmoji
from .utils import _get_as_snowflake
from .utils.private import get_as_snowflake

__all__ = (
"BaseActivity",
Expand Down Expand Up @@ -226,7 +226,7 @@ def __init__(self, **kwargs):
self.timestamps: ActivityTimestamps = kwargs.pop("timestamps", {})
self.assets: ActivityAssets = kwargs.pop("assets", {})
self.party: ActivityParty = kwargs.pop("party", {})
self.application_id: int | None = _get_as_snowflake(kwargs, "application_id")
self.application_id: int | None = get_as_snowflake(kwargs, "application_id")
self.url: str | None = kwargs.pop("url", None)
self.flags: int = kwargs.pop("flags", 0)
self.sync_id: str | None = kwargs.pop("sync_id", None)
Expand Down
8 changes: 5 additions & 3 deletions discord/appinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

from typing import TYPE_CHECKING

from .utils.private import warn_deprecated
from .utils.private import get_as_snowflake
from . import utils
from .asset import Asset
from .permissions import Permissions
Expand Down Expand Up @@ -200,9 +202,9 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload):
self._summary: str = data["summary"]
self.verify_key: str = data["verify_key"]

self.guild_id: int | None = utils._get_as_snowflake(data, "guild_id")
self.guild_id: int | None = get_as_snowflake(data, "guild_id")

self.primary_sku_id: int | None = utils._get_as_snowflake(data, "primary_sku_id")
self.primary_sku_id: int | None = get_as_snowflake(data, "primary_sku_id")
self.slug: str | None = data.get("slug")
self._cover_image: str | None = data.get("cover_image")
self.terms_of_service_url: str | None = data.get("terms_of_service_url")
Expand Down Expand Up @@ -261,7 +263,7 @@ def summary(self) -> str | None:
.. versionadded:: 1.3
.. deprecated:: 2.7
"""
utils.warn_deprecated(
warn_deprecated(
"summary",
"description",
reference="https://discord.com/developers/docs/resources/application#application-object-application-structure",
Expand Down
9 changes: 7 additions & 2 deletions discord/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
MISSING = utils.MISSING


def _valid_icon_size(size: int) -> bool:
"""Icons must be power of 2 within [16, 4096]."""
return not size & (size - 1) and 4096 >= size >= 16


class AssetMixin:
url: str
_state: Any | None
Expand Down Expand Up @@ -371,7 +376,7 @@ def replace(
url = url.with_path(f"{path}.{static_format}")

if size is not MISSING:
if not utils.valid_icon_size(size):
if not _valid_icon_size(size):
raise InvalidArgument("size must be a power of 2 between 16 and 4096")
url = url.with_query(size=size)
else:
Expand All @@ -398,7 +403,7 @@ def with_size(self, size: int, /) -> Asset:
InvalidArgument
The asset had an invalid size.
"""
if not utils.valid_icon_size(size):
if not _valid_icon_size(size):
raise InvalidArgument("size must be a power of 2 between 16 and 4096")

url = str(yarl.URL(self._url).with_query(size=size))
Expand Down
18 changes: 10 additions & 8 deletions discord/audit_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generator, TypeVar
from functools import cached_property

from .utils.private import get_as_snowflake
from . import enums, utils
from .asset import Asset
from .automod import AutoModAction, AutoModTriggerMetadata
Expand Down Expand Up @@ -559,21 +561,21 @@ def _from_data(self, data: AuditLogEntryPayload) -> None:
# into meaningful data when requested
self._changes = data.get("changes", [])

self.user = self._get_member(utils._get_as_snowflake(data, "user_id")) # type: ignore
self._target_id = utils._get_as_snowflake(data, "target_id")
self.user = self._get_member(get_as_snowflake(data, "user_id")) # type: ignore
self._target_id = get_as_snowflake(data, "target_id")

def _get_member(self, user_id: int) -> Member | User | None:
return self.guild.get_member(user_id) or self._users.get(user_id)

def __repr__(self) -> str:
return f"<AuditLogEntry id={self.id} action={self.action} user={self.user!r}>"

@utils.cached_property
@cached_property
def created_at(self) -> datetime.datetime:
"""Returns the entry's creation time in UTC."""
return utils.snowflake_time(self.id)

@utils.cached_property
@cached_property
def target(
self,
) -> (
Expand All @@ -597,24 +599,24 @@ def target(
else:
return converter(self._target_id)

@utils.cached_property
@property
def category(self) -> enums.AuditLogActionCategory:
"""The category of the action, if applicable."""
return self.action.category

@utils.cached_property
@cached_property
def changes(self) -> AuditLogChanges:
"""The list of changes this entry has."""
obj = AuditLogChanges(self, self._changes, state=self._state)
del self._changes
return obj

@utils.cached_property
@property
def before(self) -> AuditLogDiff:
"""The target's prior state."""
return self.changes.before

@utils.cached_property
@property
def after(self) -> AuditLogDiff:
"""The target's subsequent state."""
return self.changes.after
Expand Down
25 changes: 12 additions & 13 deletions discord/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
from .shard import AutoShardedClient
from .types import interactions
from .user import User
from .utils import MISSING, async_all, find, get
from .utils import MISSING, find
from .utils.private import async_all

if TYPE_CHECKING:
from .member import Member
Expand Down Expand Up @@ -216,13 +217,13 @@ def get_application_command(
return command
elif (names := name.split())[0] == command.name and isinstance(command, SlashCommandGroup):
while len(names) > 1:
command = get(commands, name=names.pop(0))
command = find(lambda c: c.name == names.pop(0), commands)
if not isinstance(command, SlashCommandGroup) or (
guild_ids is not None and command.guild_ids != guild_ids
):
return
commands = command.subcommands
command = get(commands, name=names.pop())
command = find(lambda c: c.name == names.pop(), commands)
if not isinstance(command, type) or (guild_ids is not None and command.guild_ids != guild_ids):
return
return command
Expand Down Expand Up @@ -357,7 +358,7 @@ def _check_command(cmd: ApplicationCommand, match: Mapping[str, Any]) -> bool:

# Now let's see if there are any commands on discord that we need to delete
for cmd, value_ in registered_commands_dict.items():
match = get(pending, name=value_["name"])
match = find(lambda c: c.name == value_["name"], pending)
if match is None:
# We have this command registered but not in our list
return_value.append(
Expand Down Expand Up @@ -515,8 +516,9 @@ def register(
}
)
continue
# We can assume the command item is a command, since it's only a string if action is delete
match = get(pending, name=cmd["command"].name, type=cmd["command"].type)
# We can assume the command item is a com
# mand, since it's only a string if action is delete
match = find(lambda c: c.name == cmd["command"].name and c.type == cmd["command"].type, pending)
if match is None:
continue
if cmd["action"] == "edit":
Expand Down Expand Up @@ -605,10 +607,9 @@ def register(
registered = await register("bulk", data, guild_id=guild_id)

for i in registered:
cmd = get(
cmd = find(
lambda c: c.name == i["name"] and c.type == i.get("type"),
self.pending_application_commands,
name=i["name"],
type=i.get("type"),
)
if not cmd:
raise ValueError(f"Registered command {i['name']}, type {i.get('type')} not found in pending commands")
Expand Down Expand Up @@ -712,11 +713,9 @@ async def on_connect():
registered_guild_commands[guild_id] = app_cmds

for i in registered_commands:
cmd = get(
cmd = find(
lambda c: c.name == i["name"] and c.guild_ids is None and c.type == i.get("type"),
self.pending_application_commands,
name=i["name"],
guild_ids=None,
type=i.get("type"),
)
if cmd:
cmd.id = i["id"]
Expand Down
Loading