👷 Work in progress

This commit is contained in:
2024-08-02 15:13:21 +02:00
parent 95dc716ede
commit f463824261
14 changed files with 443 additions and 35 deletions

View File

@@ -1,7 +1,53 @@
<component name="InspectionProjectProfileManager"> <component name="InspectionProjectProfileManager">
<profile version="1.0"> <profile version="1.0">
<option name="myName" value="Project Default" /> <option name="myName" value="Project Default" />
<inspection_tool class="CommandLineInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="CythonUsageBeforeDeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PoetryPackageVersionsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyAbstractClassInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyArgumentListInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyAssignmentToLoopOrWithParameterInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyAsyncCallInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyAttributeOutsideInitInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyBDDParametersInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyBroadExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyByteLiteralInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyCallingNonCallableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyChainedComparisonsInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyClassHasNoInitInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyClassVarInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyComparisonWithNoneInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyDataclassInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyDecoratorInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyDefaultArgumentInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyDeprecationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyDictCreationInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyDictDuplicateKeysInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyDocstringTypesInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyDunderSlotsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyExceptClausesOrderInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyExceptionInheritInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyFinalInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyFromFutureImportInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyGlobalUndefinedInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyInconsistentIndentationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyIncorrectDocstringInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyInitNewSignatureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyListCreationInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyMethodFirstArgAssignmentInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyMethodMayBeStaticInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyMethodOverridingInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyMethodParametersInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyMissingConstructorInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyNamedTupleInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyNestedDecoratorsInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyNewStyleGenericSyntaxInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyNonAsciiCharInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyNoneFunctionAssignmentInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyOldStyleClassesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyOverloadsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyOverridesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="false" level="WARNING" enabled_by_default="false"> <inspection_tool class="PyPackageRequirementsInspection" enabled="false" level="WARNING" enabled_by_default="false">
<option name="ignoredPackages"> <option name="ignoredPackages">
<value> <value>
@@ -11,5 +57,41 @@
</value> </value>
</option> </option>
</inspection_tool> </inspection_tool>
<inspection_tool class="PyPandasSeriesToListInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyPep8Inspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyPep8NamingInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyPropertyAccessInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyPropertyDefinitionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyProtectedMemberInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyProtocolInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyRedeclarationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyRedundantParenthesesInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyRelativeImportInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyReturnFromInitInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PySetFunctionToLiteralInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyShadowingBuiltinsInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyShadowingNamesInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PySimplifyBooleanCheckInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PySingleQuotedDocstringInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyStatementEffectInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyStringFormatInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyStubPackagesAdvertiser" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyStubPackagesCompatibilityInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PySuperArgumentsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyTestParametrizedInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyTestUnpassedFixtureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyTrailingSemicolonInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyTupleAssignmentBalanceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyTupleItemAssignmentInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyTypeCheckerInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyTypeHintsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyTypedDictInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyUnboundLocalVariableInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyUnnecessaryBackslashInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyUnreachableCodeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyUnusedLocalInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PyVulnerableApiCodeInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyrightInspection" enabled="false" level="WARNING" enabled_by_default="false" />
</profile> </profile>
</component> </component>

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

2
.idea/misc.xml generated
View File

@@ -3,5 +3,5 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="Python 3.11 (www)" /> <option name="sdkName" value="Python 3.11 (www)" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11.2 WSL (Debian): (/home/jerem/.local/share/pdm/venvs/pycord-reactive-views-myCslMhv-3.11/bin/python3)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (pycord-reactive-views)" project-jdk-type="Python SDK" />
</project> </project>

View File

@@ -3,8 +3,9 @@
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.11.2 WSL (Debian): (/home/jerem/.local/share/pdm/venvs/pycord-reactive-views-myCslMhv-3.11/bin/python3)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.12 (pycord-reactive-views)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="PyDocumentationSettings"> <component name="PyDocumentationSettings">

53
examples/counter.py Normal file
View File

@@ -0,0 +1,53 @@
# ruff: noqa: INP001
import os
import discord
from dotenv import load_dotenv
from pycord_reactive_views import ReactiveButton, ReactiveValue, ReactiveView
load_dotenv()
bot = discord.Bot()
class Counter(ReactiveView):
"""A simple counter view that increments a counter when a button is clicked."""
def __init__(self):
super().__init__()
self.counter = 0
self.counter_button = ReactiveButton(
label=ReactiveValue(lambda: str(self.counter), "0"),
style=ReactiveValue(
lambda: discord.ButtonStyle.primary if self.counter % 2 == 0 else discord.ButtonStyle.secondary,
discord.ButtonStyle.primary,
),
)
self.reset_button = ReactiveButton(
label="Reset",
style=discord.ButtonStyle.danger,
disabled=ReactiveValue(lambda: self.counter == 0, default=True),
)
self.counter_button.callback = self._button_callback
self.reset_button.callback = self._reset_callback
self.add_item(self.counter_button)
self.add_item(self.reset_button)
async def _button_callback(self, interaction: discord.Interaction) -> None:
await interaction.response.defer()
self.counter += 1
await self.update()
async def _reset_callback(self, interaction: discord.Interaction) -> None:
await interaction.response.defer()
self.counter = 0
await self.update()
@bot.slash_command()
async def counter(ctx: discord.ApplicationContext) -> None:
"""Send the counter view."""
await Counter().send(ctx)
bot.run(os.getenv("TOKEN"))

View File

@@ -1,19 +0,0 @@
import discord
from pycord_reactive_views import ReactiveButton, ReactiveView
bot = discord.Bot()
class Counter(ReactiveView):
"""A simple counter view that increments a counter when a button is clicked."""
def __init__(self):
super().__init__()
self.counter = 0
self.counter_button = ReactiveButton(value=lambda: self.counter)
self.counter_button.callback = self._button_callback
self.add_item(self.counter_button)
async def _button_callback(self, interaction: discord.Interaction):
await interaction.response.defer()
self.counter += 1

13
pdm.lock generated
View File

@@ -5,7 +5,7 @@
groups = ["default", "dev"] groups = ["default", "dev"]
strategy = ["inherit_metadata"] strategy = ["inherit_metadata"]
lock_version = "4.5.0" lock_version = "4.5.0"
content_hash = "sha256:fe19e8fbc71b42f70e7f42def3e0dad93de1c2fcf9e5a252b4fa606c3ea5b2f7" content_hash = "sha256:ce0276a2b471885bb42d49ce0aa48f39564637bfc4dbc67a04a8fc57e0920415"
[[metadata.targets]] [[metadata.targets]]
requires_python = ">=3.11" requires_python = ">=3.11"
@@ -224,6 +224,17 @@ files = [
{file = "py_cord-2.6.0.tar.gz", hash = "sha256:bbc0349542965d05e4b18cc4424136206430a8cc911fda12a0a57df6fdf9cd9c"}, {file = "py_cord-2.6.0.tar.gz", hash = "sha256:bbc0349542965d05e4b18cc4424136206430a8cc911fda12a0a57df6fdf9cd9c"},
] ]
[[package]]
name = "python-dotenv"
version = "1.0.1"
requires_python = ">=3.8"
summary = "Read key-value pairs from a .env file and set them as environment variables"
groups = ["dev"]
files = [
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.5.5" version = "0.5.5"

View File

@@ -33,20 +33,144 @@ name = "pycord_reactive_views"
[tool.ruff] [tool.ruff]
line-length = 120 line-length = 120
target-version = "py311" target-version = "py311"
select = ["E", "F", "I", "N", "W", "B", "C", "D"] fix = true
ignore = ["D100", "D104", "D107"]
[tool.ruff.pydocstyle]
convention = "google"
[tool.ruff.per-file-ignores] [tool.ruff.per-file-ignores]
"__init__.py" = ["F401"] "__init__.py" = ["F401"]
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"C90", # mccabe
"CPY", # flake8-copyright
"EM", # flake8-errmsg
"SLF", # flake8-self
"ARG", # flake8-unused-arguments
"TD", # flake8-todos
"FIX", # flake8-fixme
"PD", # pandas-vet
"D100", # Missing docstring in public module
"D104", # Missing docstring in public package
"D105", # Missing docstring in magic method
"D106", # Missing docstring in public nested class
"D107", # Missing docstring in __init__
"D203", # Blank line required before class docstring
"D213", # Multi-line summary should start at the second line (incompatible with D212)
"D301", # Use r""" if any backslashes in a docstring
"D401", # First line of docstring should be in imperative mood
"D404", # First word of the docstring should not be "This"
"D405", # Section name should be properly capitalized
"D406", # Section name should end with a newline
"D407", # Missing dashed underline after section
"D408", # Section underline should be in the line following the section's name
"D409", # Section underline should match the length of its name
"D410", # Missing blank line after section
"D411", # Missing blank line before section
"D412", # No blank lines allowed between a section header and its content
"D413", # Missing blank line after last section
"D414", # Section has no content
"D416", # Section name should end with a colon
"D417", # Missing argument description in the docstring
"ANN101", # Missing type annotation for self in method
"ANN102", # Missing type annotation for cls in classmethod
"ANN204", # Missing return type annotation for special method
"ANN401", # Dynamically typed expressions (typing.Any) disallowed
"SIM102", # use a single if statement instead of nested if statements
"SIM108", # Use ternary operator {contents} instead of if-else-block
"G001", # Logging statement uses str.format
"G004", # Logging statement uses f-string
"G003", # Logging statement uses +
"B904", # Raise without `from` within an `except` clause
"UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)`
"PLR2004", # Using unnamed numerical constants
"PGH003", # Using specific rule codes in type ignores
"E731", # Don't asign a lambda expression, use a def
"S311", # Use `secrets` for random number generation, not `random`
"TRY003", # Avoid specifying long messages outside the exception class
# Redundant rules with ruff-format:
"E111", # Indentation of a non-multiple of 4 spaces
"E114", # Comment with indentation of a non-multiple of 4 spaces
"E117", # Cheks for over-indented code
"D206", # Checks for docstrings indented with tabs
"D300", # Checks for docstring that use ''' instead of """
"Q000", # Checks of inline strings that use wrong quotes (' instead of ")
"Q001", # Multiline string that use wrong quotes (''' instead of """)
"Q002", # Checks for docstrings that use wrong quotes (''' instead of """)
"Q003", # Checks for avoidable escaped quotes ("\"" -> '"')
"COM812", # Missing trailing comma (in multi-line lists/tuples/...)
"COM819", # Prohibited trailing comma (in single-line lists/tuples/...)
"ISC001", # Single line implicit string concatenation ("hi" "hey" -> "hihey")
"ISC002", # Multi line implicit string concatenation
"DOC501"
]
[tool.ruff.lint.isort]
order-by-type = false
case-sensitive = true
combine-as-imports = true
# Redundant rules with ruff-format
force-single-line = false # forces all imports to appear on their own line
force-wrap-aliases = false # Split imports with multiple members and at least one alias
lines-after-imports = -1 # The number of blank lines to place after imports
lines-between-types = 0 # Number of lines to place between "direct" and import from imports
split-on-trailing-comma = false # if last member of multiline import has a comma, don't fold it to single line
[tool.ruff.lint.pylint]
max-args = 15
max-branches = 15
max-locals = 15
max-nested-blocks = 5
max-returns = 8
max-statements = 75
[tool.ruff.lint.per-file-ignores]
"tests/**.py" = [
"ANN", # annotations
"D", # docstrings
"S101", # Use of assert
]
".github/scripts/**.py" = [
"INP001", # Implicit namespace package
]
"alembic-migrations/env.py" = [
"INP001", # Implicit namespace package
]
"alembic-migrations/versions/*" = [
"INP001", # Implicit namespace package
"D103", # Missing docstring in public function
"D400", # First line should end with a period
"D415", # First line should end with a period, question mark, or exclamation point
]
[tool.ruff.format]
line-ending = "lf"
[tool.basedpyright] [tool.basedpyright]
include = ["src"] include = ["src"]
exclude = ["**/__pycache__"] exclude = ["**/__pycache__"]
venv = "env311" venv = "env311"
pythonVersion = "3.11" pythonPlatform = "All"
pythonVersion = "3.12"
typeCheckingMode = "all"
reportAny = false
reportUnusedCallResult = false
reportUnknownArgumentType = false
reportUnknownVariableType = false
reportUnknownMemberType = false
reportUnknownParameterType = false
reportUnknownLambdaType = false
[tool.pdm] [tool.pdm]
distribution = true distribution = true
@@ -54,6 +178,7 @@ distribution = true
dev = [ dev = [
"basedpyright>=1.15.0", "basedpyright>=1.15.0",
"ruff>=0.5.5", "ruff>=0.5.5",
"python-dotenv>=1.0.1",
] ]

View File

@@ -1 +0,0 @@
This directoy stores each Python Package.

View File

@@ -0,0 +1,5 @@
from .components import ReactiveButton
from .utils import ReactiveValue
from .view import ReactiveView
__all__ = ["ReactiveButton", "ReactiveView", "ReactiveValue"]

View File

@@ -0,0 +1,53 @@
from typing import Any
import discord
from .utils import MaybeReactiveValue, ReactiveValue, is_reactive
class Reactive:
"""A class that can be used with reactive values."""
def __init__(self):
super().__init__()
self.reactives: dict[str, ReactiveValue[Any]] = {}
self.super_kwargs: dict[str, Any] = {}
def add_reactive(self, key: str, value: MaybeReactiveValue[Any]) -> None:
"""Add a reactive value to the view."""
if is_reactive(value):
self.reactives[key] = value
if value.default:
setattr(self, key, value.default)
else:
setattr(self, key, value)
async def refresh(self) -> None:
"""Refresh the reactive values."""
for key, value in self.reactives.items():
setattr(self, key, await value())
class ReactiveButton(discord.ui.Button, Reactive): # pyright: ignore[reportUnsafeMultipleInheritance,reportMissingTypeArgument]
"""A button that can be used with reactive values."""
def __init__(
self,
*,
style: MaybeReactiveValue[discord.ButtonStyle] = discord.ButtonStyle.secondary,
label: MaybeReactiveValue[str | None] = None,
disabled: MaybeReactiveValue[bool] = False,
custom_id: str | None = None,
url: MaybeReactiveValue[str | None] = None,
emoji: MaybeReactiveValue[str | discord.Emoji | discord.PartialEmoji | None] = None,
sku_id: int | None = None,
row: MaybeReactiveValue[int | None] = None,
):
discord.ui.Button.__init__(self)
Reactive.__init__(self)
self.add_reactive("style", style)
self.add_reactive("label", label)
self.add_reactive("disabled", disabled)
self.add_reactive("url", url)
self.add_reactive("emoji", emoji)
self.add_reactive("row", row)

View File

@@ -0,0 +1,3 @@
from .reactivity import MaybeReactiveValue, ReactiveValue, is_reactive
__all__ = ["ReactiveValue", "MaybeReactiveValue", "is_reactive"]

View File

@@ -0,0 +1,44 @@
from collections.abc import Awaitable, Callable
from inspect import isawaitable
from typing import TypeGuard, TypeVar
T = TypeVar("T")
class Unset:
"""A class to represent an unset value."""
def __bool__(self):
return False
UNSET = Unset()
class ReactiveValue[T]:
"""A value that can be a constant, a callable, or an async callable."""
def __init__(self, func: Callable[[], T] | Callable[[], Awaitable[T]], default: T | Unset = UNSET):
"""Create a new reactive value."""
self._func: Callable[[], T] | Callable[[], Awaitable[T]] = func
self.default = default
async def __call__(self) -> T:
"""Call the function and return the value.
:raises TypeError: If the value is not callable
"""
if callable(self._func):
ret = self._func()
if isawaitable(ret):
return await ret
return ret
raise TypeError("ReactiveValue must be a callable")
MaybeReactiveValue = T | ReactiveValue[T]
def is_reactive(value: MaybeReactiveValue[T]) -> TypeGuard[ReactiveValue[T]]:
"""Check if a value is a reactive value."""
return isinstance(value, ReactiveValue)

View File

@@ -0,0 +1,57 @@
from typing import Self, override
import discord
from .components import ReactiveButton
class ReactiveView(discord.ui.View):
"""A view that can be used with reactive components."""
def __init__(
self,
*,
timeout: float | None = 180.0,
disable_on_timeout: bool = False,
):
super().__init__(timeout=timeout, disable_on_timeout=disable_on_timeout)
self._reactives: list[ReactiveButton] = []
@override
def add_item(self, item: discord.ui.Item[Self]) -> None:
if isinstance(item, ReactiveButton):
self._reactives.append(item)
super().add_item(item)
async def _get_embed(self) -> discord.Embed | None:
"""Get the discord embed to be displayed in the message."""
return None
async def _get_embeds(self) -> list[discord.Embed]:
"""Get the discord embeds to be displayed in the message."""
return []
async def _get_content(self) -> str | None:
"""Get the content to be displayed in the message."""
return None
async def update(self) -> None:
"""Update the view with new components.
:raises ValueError: If the view has no message (not yet sent?), can't update
"""
for reactive in self._reactives:
await reactive.refresh()
if not self.message:
raise ValueError("View has no message (not yet sent?), can't refresh")
if embeds := await self._get_embeds():
await self.message.edit(content=await self._get_content(), embeds=embeds, view=self)
else:
await self.message.edit(content=await self._get_content(), view=self)
async def send(self, ctx: discord.ApplicationContext | discord.Interaction) -> None:
"""Send the view to a context."""
if embeds := await self._get_embeds():
await ctx.respond(content=await self._get_content(), embeds=embeds, view=self)
else:
await ctx.respond(content=await self._get_content(), view=self)