Repository: Pycord-Development/pycord

Issue:
Options with a default value of None still require the "required" kwarg
### Summary

When using `None` as the default value, `required` defaults to True even though there _is_ a default value.

### Reproduction Steps

- Make an application command (slash command) with at least one Option
- Give the option a default value of None, and see that the field is **required** in Discord (shows a red error message when trying to send the command)
- Change the default value to something else, and see that the field is now no longer required in Discord

### Minimal Reproducible Code

```python
import discord
from discord.commands import Option

bot = discord.Bot()

@bot.slash_command()
async def test(ctx, arg: Option(int, default=None)):
    pass

bot.run(...)
```


### Expected Results

If any value at all is passed, `required` should be `False`, no matter what that value may be. `None` is a pretty common use case and shouldn't always signify that there is **no** default value.

### Actual Results

If the value passed is `None`, it is treated as if no default value is present.

Error in Discord itself: **This option is required. Specify a value.**

To allow this default value to be used, the Option would have to be specified as

```py
Option(int, default=None, required=False)
```
which feels off, if `default` is present then `required` shouldn't be.

### Intents

default + members

### System Information

- Python v3.9.5-final
- py-cord v2.0.0-beta
    - py-cord pkg_resources: v2.0.0b1
- aiohttp v3.7.4.post0
- system info: Linux 4.15.0-58-generic #64-Ubuntu SMP Tue Aug 6 11:12:41 UTC 2019

### Checklist

- [X] I have searched the open issues for duplicates.
- [X] I have shown the entire traceback, if possible.
- [X] I have removed my token from display, if visible.

### Additional Context

This is caused by https://github.com/Pycord-Development/pycord/blob/68e9cf3db5c311153899a0c577c68d88c0feb4cf/discord/commands/options.py#L88-L91

where it's merely checked to be `None`. A better solution would be to check if it's **in** the kwargs dict, no matter the value (which would allow `None`), and *then* pop it. When popping it, the default value is `None`, making it impossible to differentiate from actually being *passed* or being *absent*.

```py
        # using "in" instead of popping the value with a default of None
        # which would allow None as a default
        self.required: bool = (
            kwargs.pop("required", True) if "default" not in kwargs else False
        )
        # Popping the default value AFTERWARDS so that it doesn't matter if it was already None or not
        self.default = kwargs.pop("default", None)
```

If you think this would fix the issue, I'd be more than happy to make the PR for it myself if necessary.
Options with a default value of None still require the "required" kwarg
### Summary

When using `None` as the default value, `required` defaults to True even though there _is_ a default value.

### Reproduction Steps

- Make an application command (slash command) with at least one Option
- Give the option a default value of None, and see that the field is **required** in Discord (shows a red error message when trying to send the command)
- Change the default value to something else, and see that the field is now no longer required in Discord

### Minimal Reproducible Code

```python
import discord
from discord.commands import Option

bot = discord.Bot()

@bot.slash_command()
async def test(ctx, arg: Option(int, default=None)):
    pass

bot.run(...)
```


### Expected Results

If any value at all is passed, `required` should be `False`, no matter what that value may be. `None` is a pretty common use case and shouldn't always signify that there is **no** default value.

### Actual Results

If the value passed is `None`, it is treated as if no default value is present.

Error in Discord itself: **This option is required. Specify a value.**

To allow this default value to be used, the Option would have to be specified as

```py
Option(int, default=None, required=False)
```
which feels off, if `default` is present then `required` shouldn't be.

### Intents

default + members

### System Information

- Python v3.9.5-final
- py-cord v2.0.0-beta
    - py-cord pkg_resources: v2.0.0b1
- aiohttp v3.7.4.post0
- system info: Linux 4.15.0-58-generic #64-Ubuntu SMP Tue Aug 6 11:12:41 UTC 2019

### Checklist

- [X] I have searched the open issues for duplicates.
- [X] I have shown the entire traceback, if possible.
- [X] I have removed my token from display, if visible.

### Additional Context

This is caused by https://github.com/Pycord-Development/pycord/blob/68e9cf3db5c311153899a0c577c68d88c0feb4cf/discord/commands/options.py#L88-L91

where it's merely checked to be `None`. A better solution would be to check if it's **in** the kwargs dict, no matter the value (which would allow `None`), and *then* pop it. When popping it, the default value is `None`, making it impossible to differentiate from actually being *passed* or being *absent*.

```py
        # using "in" instead of popping the value with a default of None
        # which would allow None as a default
        self.required: bool = (
            kwargs.pop("required", True) if "default" not in kwargs else False
        )
        # Popping the default value AFTERWARDS so that it doesn't matter if it was already None or not
        self.default = kwargs.pop("default", None)
```

If you think this would fix the issue, I'd be more than happy to make the PR for it myself if necessary.


Discussion:
The proposed fix for this looks like a good one. Please submit the PR for review. Thank you!
The proposed fix for this looks like a good one. Please submit the PR for review. Thank you!

Resolving patch:
diff --git a/discord/commands/options.py b/discord/commands/options.py
--- a/discord/commands/options.py
+++ b/discord/commands/options.py
@@ -85,10 +85,10 @@ def __init__(self, input_type: Any, /, description: str = None, **kwargs) -> Non
                         self.channel_types.append(channel_type)
                 input_type = _type
         self.input_type = input_type
-        self.default = kwargs.pop("default", None)
         self.required: bool = (
-            kwargs.pop("required", True) if self.default is None else False
+            kwargs.pop("required", True) if "default" not in kwargs else False
         )
+        self.default = kwargs.pop("default", None)
         self.choices: List[OptionChoice] = [
             o if isinstance(o, OptionChoice) else OptionChoice(o)
             for o in kwargs.pop("choices", list())


Test patch: