Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
529f9d5
feat: Add ComponentLimits and EmbedLimits enums for constraint defini…
Lumabots Apr 29, 2026
24d7f69
Merge branch 'master' into enum-limit
Lumabots Apr 29, 2026
4069993
fix: Update changelog to include `ComponentLimits` and `EmbedLimits` …
Lumabots Apr 29, 2026
ff6e8e8
feat: Add ComponentLimits and EmbedLimits classes with detailed const…
Lumabots Apr 29, 2026
e91d7d9
feat: Integrate ComponentLimits into UI components for enhanced valid…
Lumabots Apr 29, 2026
ec89add
refactor: Improve error messages for component limits in UI elements
Lumabots Apr 29, 2026
c03ba1a
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 29, 2026
acc6001
feat: Update ComponentLimits for text input and checkbox group valida…
Lumabots Apr 29, 2026
d328728
fix: Update component limits for thumbnail and gallery descriptions, …
Lumabots Apr 29, 2026
4c6ae48
Merge branch 'master' into enum-limit
Lumabots May 7, 2026
c3291f5
Merge branch 'master' into enum-limit
Lumabots May 9, 2026
6fe7a57
Update discord/enums.py
Lumabots May 14, 2026
6048caa
Update discord/ui/select.py
Lumabots May 14, 2026
4e315b1
Merge branch 'master' into enum-limit
Lumabots May 14, 2026
d9c8691
feat: Update file upload limits and add new constraints in enums
Lumabots May 14, 2026
6bdb292
Refactor component limits and embed limits
Lumabots May 22, 2026
fd91d93
Merge branch 'master' into enum-limit
Lumabots May 22, 2026
f6feacc
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 22, 2026
9118c0c
fix(changelog): remove merge conflict markers from changelog
Lumabots May 22, 2026
a2fcb95
Update enums.rst
Lumabots May 22, 2026
37b2af4
feat: add strict type checking configuration for basedpyright
Lumabots May 22, 2026
d1b03f6
Merge branch 'enum-limit' of https://github.com/Lumabots/pycord into …
Lumabots May 22, 2026
6b38959
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 22, 2026
236386a
Extract ComponentLimits to ui.constant
Lumabots May 23, 2026
8495bb2
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 23, 2026
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ These changes are available on the `master` branch, but have not yet been releas

- Support for **Python 3.14**.
([#2948](https://github.com/Pycord-Development/pycord/pull/2948))
- Added `ComponentLimits` and `EmbedLimits` enums for Discord API constraints.
([#3217](https://github.com/Pycord-Development/pycord/pull/3217))

### Changed

Expand Down
77 changes: 57 additions & 20 deletions discord/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from .enums import (
ButtonStyle,
ChannelType,
ComponentLimits,
ComponentType,
InputTextStyle,
SelectDefaultValueType,
Expand Down Expand Up @@ -724,14 +725,26 @@ def __init__(
emoji: str | GuildEmoji | AppEmoji | PartialEmoji | None = None,
default: bool = False,
) -> None:
if len(label) > 100:
raise ValueError("label must be 100 characters or fewer")
if len(label) > ComponentLimits.select_option_label_max.value:
raise ValueError(
f"label must be {ComponentLimits.select_option_label_max.value} characters or fewer"
)

if value is not MISSING and len(value) > 100:
raise ValueError("value must be 100 characters or fewer")
if (
value is not MISSING
and len(value) > ComponentLimits.select_option_value_max.value
):
raise ValueError(
f"value must be {ComponentLimits.select_option_value_max.value} characters or fewer"
)

if description is not None and len(description) > 100:
raise ValueError("description must be 100 characters or fewer")
if (
description is not None
and len(description) > ComponentLimits.select_option_description_max.value
):
raise ValueError(
f"description must be {ComponentLimits.select_option_description_max.value} characters or fewer"
)

self.label = label
self.value = label if value is MISSING else value
Expand Down Expand Up @@ -990,7 +1003,7 @@ class Thumbnail(Component):
media: :class:`UnfurledMediaItem`
The component's underlying media object.
description: Optional[:class:`str`]
The thumbnail's description, up to 1024 characters.
The thumbnail's description, up to 256 characters.
spoiler: Optional[:class:`bool`]
Whether the thumbnail has the spoiler overlay.
"""
Expand Down Expand Up @@ -1039,7 +1052,7 @@ class MediaGalleryItem:
url: :class:`str`
The URL of this gallery item. This can either be an arbitrary URL or an ``attachment://`` URL to work with local files.
description: Optional[:class:`str`]
The gallery item's description, up to 1024 characters.
The gallery item's description, up to 256 characters.
spoiler: Optional[:class:`bool`]
Whether the gallery item is a spoiler.
"""
Expand Down Expand Up @@ -1527,14 +1540,26 @@ def __init__(
description: str | None = None,
default: bool = False,
) -> None:
if len(label) > 100:
raise ValueError("label must be 100 characters or fewer")
if len(label) > ComponentLimits.select_option_label_max.value:
raise ValueError(
f"label must be {ComponentLimits.select_option_label_max.value} characters or fewer"
)

if value is not MISSING and len(value) > 100:
raise ValueError("value must be 100 characters or fewer")
if (
value is not MISSING
and len(value) > ComponentLimits.select_option_value_max.value
):
raise ValueError(
f"value must be {ComponentLimits.select_option_value_max.value} characters or fewer"
)

if description is not None and len(description) > 100:
raise ValueError("description must be 100 characters or fewer")
if (
description is not None
and len(description) > ComponentLimits.select_option_description_max.value
):
raise ValueError(
f"description must be {ComponentLimits.select_option_description_max.value} characters or fewer"
)

self.label = label
self.value = label if value is MISSING else value
Expand Down Expand Up @@ -1687,14 +1712,26 @@ def __init__(
description: str | None = None,
default: bool = False,
) -> None:
if len(label) > 100:
raise ValueError("label must be 100 characters or fewer")
if len(label) > ComponentLimits.select_option_label_max.value:
raise ValueError(
f"label must be {ComponentLimits.select_option_label_max.value} characters or fewer"
)

if value is not MISSING and len(value) > 100:
raise ValueError("value must be 100 characters or fewer")
if (
value is not MISSING
and len(value) > ComponentLimits.select_option_value_max.value
):
raise ValueError(
f"value must be {ComponentLimits.select_option_value_max.value} characters or fewer"
)

if description is not None and len(description) > 100:
raise ValueError("description must be 100 characters or fewer")
if (
description is not None
and len(description) > ComponentLimits.select_option_description_max.value
):
raise ValueError(
f"description must be {ComponentLimits.select_option_description_max.value} characters or fewer"
)

self.label = label
self.value = label if value is MISSING else value
Expand Down
122 changes: 110 additions & 12 deletions discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@
"SelectDefaultValueType",
"ApplicationEventWebhookStatus",
"InviteTargetUsersJobStatusCode",
"ComponentLimits",
"EmbedLimits",
)


Expand All @@ -96,21 +98,17 @@ def _create_value_cls(name, comparable):
cls.__repr__ = lambda self: f"<{name}.{self.name}: {self.value!r}>"
cls.__str__ = lambda self: f"{name}.{self.name}"
if comparable:
cls.__le__ = (
lambda self, other: isinstance(other, self.__class__)
and self.value <= other.value
cls.__le__ = lambda self, other: (
isinstance(other, self.__class__) and self.value <= other.value
)
cls.__ge__ = (
lambda self, other: isinstance(other, self.__class__)
and self.value >= other.value
cls.__ge__ = lambda self, other: (
isinstance(other, self.__class__) and self.value >= other.value
)
cls.__lt__ = (
lambda self, other: isinstance(other, self.__class__)
and self.value < other.value
cls.__lt__ = lambda self, other: (
isinstance(other, self.__class__) and self.value < other.value
)
cls.__gt__ = (
lambda self, other: isinstance(other, self.__class__)
and self.value > other.value
cls.__gt__ = lambda self, other: (
isinstance(other, self.__class__) and self.value > other.value
)
return cls

Expand Down Expand Up @@ -1212,6 +1210,106 @@ class InviteTargetUsersJobStatusCode(Enum):
failed = 3


class ComponentLimits(Enum):
Comment thread
Lumabots marked this conversation as resolved.
Outdated
Comment thread
Lumabots marked this conversation as resolved.
Outdated
# View constraints
view_children_max = 40

# ActionRow constraints
action_row_children_max = 5

# Button constraints
button_label_max = 80

# Container constraints
container_children_max = float("inf") # No limit
Comment thread
Lumabots marked this conversation as resolved.
Outdated

# MediaGallery constraints
media_gallery_items_min = 1
media_gallery_items_max = 10

# MediaGalleryItem constraints
media_gallery_item_description_max = 256
Comment thread
Lumabots marked this conversation as resolved.
Outdated

# Select constraints
select_placeholder_max = 150
select_min_value_min = 0
select_min_value_max = 25
select_max_value_min = 1
select_max_value_max = 25
Comment thread
Lumabots marked this conversation as resolved.
Outdated
select_options_max = 25
select_default_values_max = 25

# Select option constraints
select_option_label_max = 100
select_option_value_max = 100
select_option_description_max = 100

# Section constraints
section_accessory_max = 1
section_children_min = 1
section_children_max = 3

# TextInput constraints
text_input_max_count = 5
text_input_label_max = 45
text_input_placeholder_max = 100
text_input_min_length_min = 0
text_input_min_length_max = 4000
text_input_max_length_min = 1
text_input_max_length_max = 4000
text_input_value_max = 4000

# TextDisplay constraints
text_display_content_max = 4000

# Thumbnail constraints
thumbnail_description_max = 256

# Custom ID constraints
custom_id_min = 1
custom_id_max = 100

# RadioGroup constraints
radio_options_max = 10

# CheckboxGroup constraints
checkbox_options_max = 10
checkbox_min_values_min = 0
checkbox_min_values_max = 10
checkbox_max_values_min = 1
checkbox_max_values_max = 10

# FileUpload constraints
file_upload_min_files = 0
file_upload_max_files = 10

# Modal constraints
modal_title_max = 45
modal_rows_max = 5


class EmbedLimits(Enum):
# Embed field constraints
fields_max = 25

# Field title/name constraints
field_name_max = 256

# Field value constraints
field_value_max = 1024

# Embed description constraints
description_max = 4096

# Embed footer constraints
footer_text_max = 2048

# Embed author constraints
author_name_max = 256
title_max = 256
total_max = 6000


T = TypeVar("T")


Expand Down
29 changes: 20 additions & 9 deletions discord/ui/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from typing import TYPE_CHECKING, Callable, TypeVar

from ..components import Button as ButtonComponent
from ..enums import ButtonStyle, ComponentType
from ..enums import ButtonStyle, ComponentLimits, ComponentType
from ..partial_emoji import PartialEmoji, _EmojiTag
from .item import ItemCallbackType, ViewItem

Expand Down Expand Up @@ -113,10 +113,17 @@ def __init__(
self._row: int | None = None
self._rendered_row: int | None = None
super().__init__()
if label and len(str(label)) > 80:
raise ValueError("label must be 80 characters or fewer")
if custom_id is not None and len(str(custom_id)) > 100:
raise ValueError("custom_id must be 100 characters or fewer")
if label and len(str(label)) > ComponentLimits.button_label_max.value:
raise ValueError(
f"label must be {ComponentLimits.button_label_max.value} characters or fewer"
)
if (
custom_id is not None
and len(str(custom_id)) > ComponentLimits.custom_id_max.value
):
raise ValueError(
f"custom_id must be {ComponentLimits.custom_id_max.value} characters or fewer"
)
if custom_id is not None and url is not None:
raise TypeError("cannot mix both url and custom_id with Button")
if sku_id is not None and url is not None:
Expand Down Expand Up @@ -206,8 +213,10 @@ def custom_id(self) -> str | None:
def custom_id(self, value: str | None):
if value is not None and not isinstance(value, str):
raise TypeError("custom_id must be None or str")
if value and len(value) > 100:
raise ValueError("custom_id must be 100 characters or fewer")
if value and len(value) > ComponentLimits.custom_id_max.value:
raise ValueError(
f"custom_id must be {ComponentLimits.custom_id_max.value} characters or fewer"
)
self.underlying.custom_id = value
self._provided_custom_id = value is not None

Expand Down Expand Up @@ -238,8 +247,10 @@ def label(self) -> str | None:

@label.setter
def label(self, value: str | None):
if value and len(str(value)) > 80:
raise ValueError("label must be 80 characters or fewer")
if value and len(str(value)) > ComponentLimits.button_label_max.value:
raise ValueError(
f"label must be {ComponentLimits.button_label_max.value} characters or fewer"
)
self.underlying.label = str(value) if value is not None else value

@property
Expand Down
8 changes: 5 additions & 3 deletions discord/ui/checkbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from typing import TYPE_CHECKING

from ..components import Checkbox as CheckboxComponent
from ..enums import ComponentType
from ..enums import ComponentLimits, ComponentType
from .item import ModalItem

__all__ = ("Checkbox",)
Expand Down Expand Up @@ -105,8 +105,10 @@ def custom_id(self) -> str:
def custom_id(self, value: str):
if not isinstance(value, str):
raise TypeError(f"custom_id must be str not {value.__class__.__name__}")
if len(value) > 100:
raise ValueError("custom_id must be 100 characters or fewer")
if len(value) > ComponentLimits.custom_id_max.value:
raise ValueError(
f"custom_id must be {ComponentLimits.custom_id_max.value} characters or fewer"
)
self.underlying.custom_id = value

@property
Expand Down
Loading
Loading