Skip to content
This repository has been archived by the owner on Mar 13, 2023. It is now read-only.

[FEAT] Modal builder class decorator #704

Open
i0bs opened this issue Oct 29, 2022 · 1 comment
Open

[FEAT] Modal builder class decorator #704

i0bs opened this issue Oct 29, 2022 · 1 comment
Labels
New Feature::Library A new feature for the library

Comments

@i0bs
Copy link
Contributor

i0bs commented Oct 29, 2022

Is your feature request related to a problem? Please describe.

I'm currently using NAFF for a company contract that involves needing to survey user data at a large scale and give us administrative control over what text inputs are used. We do this by checking a database for given text inputs and select menus, and sending it on an interaction create event.

Doing this, however, brings pros and cons:

The good

  • We can easily control which text inputs are sent and how. 👍🏼
  • Using a database lets us build commands to write to it in order to change text inputs instead of hardcoding the logic. 👍🏼

The bad

  • The modals are limited to a database which can be inefficient for storage of a modal. 👎🏼
  • It's arguably easier if we have a way to flexibly control how a modal is built in the code instead of relying on formatting of database fields. 👎🏼

Describe the solution you'd like

The modal class in NAFF strictly follows the API object schema for its resource. Instead of directly modifying the contents of the Python object, I'd like if there was a way we could pre-emptively build a modal without being too specific. This is where I pitch my idea: a modal builder.

Take the following example of what you currently do in NAFF:

modal = Modal(
  title="Modal title",
  components=[
    ShortText(label="Short text input", custom_id="short_text_input"),
    ParagraphText(label="Paragraph text input", custom_id="long_text", required=False),
    Select(options=[SelectOption(label="foo", value="bar", default=True)], custom_id="selection")
  ]
)

This works good if you're not needing to change anything, and/or you're keeping the modal's design hardcoded inside the source code. However, doing modal.components[0] and etc. is an inefficient; and non-ergonomic manner to changing the modal's contents. We now have to remember which indices of the modal's components we want to change, or iterate through it. Yucky and not nice!

Instead, if we used something like dataclasses, we could establish a key-value pair of the attribute to the object. Take the following proposal where we use typing.TypedDict to our advantage for required and non-required values.

from naff import * # forgive me polls
import typing

# We can either adopt the name of the modal through the class or as a positional argument of the decorator.
@naff.modal_builder(name="Modal title")
class MyNewModal(typing.TypedDict):
  # In the class, modal text inputs and their respective object are type annotated instead.
  # We still adopt the kwargs approach to writing contents.
  short_text_input: typing.Required[ShortText(label="Short input text")]

  # Instead of declaring a text input component's optionality in the object, we can refer from
  # the optional value in the class.
  long_text: typing.NotRequired[ParagraphText(label="Paragraph text input")] = None # equals expression is unnecessary

  # The same may apply for select menus within where we want to automatically represent an
  # default choice.
  # We technically make it "required" since it's not an undefined value, but it'll still default to that select option.
  selection: typing.Required[Select] = SelectOption(label="foo", value="bar")

With a key-value pair set with the attribute name and the type, we could also establish an object return from the decorator's application onto the class. In this case we'll just call it ModalBuild. This object essentially acts as a manager of the newly built modal and will now allow us to modify it in whatever situation. This theoretically lets us do this:

global new_modal
new_modal = typing.Mapping[MyNewModal, ModalBuild]

# We can now directly affect the contents of it easily without needing to iterate through
# components and checking the custom ID, and offers more versatility.
new_modal.short_text_input.required = False

# Subsequently we could do this. Not nice, but if we were working with a database, it'd be simpler.
new_modal.long_text = ParagraphText(label="Paragraph text input")

Describe alternatives you've considered

We've originally considered just writing our own modal builder class that used special magic methods for changing attribute data based off of the database and apply them during runtime, but we found this didn't work nicely for what we wanted.

Additional Information

With this solution, it should still be possible to send a ModalBuild the same as a regular Modal object from NAFF in an interaction response.

@slash_command(name="modal_sender", description="Sends a modal.")
async def my_new_modal_sender(ctx: ModalContext) -> None:
  await ctx.send_modal(new_modal)
@i0bs i0bs added the New Feature::Library A new feature for the library label Oct 29, 2022
@silasary
Copy link
Collaborator

Sounds useful

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
New Feature::Library A new feature for the library
Projects
None yet
Development

No branches or pull requests

2 participants