Repository: hikari-py/hikari
Pull request #917 (2021-11-26 05:19:25)

Add EventStream.filter specialisation to the abc
### Summary
Add EventStream.filter specialisation to the abc

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [ ] I have run `nox` and all the pipelines have passed.
- [ ] I have made unittests according to the code I have added/modified/deleted.

### Related issues
<!--
To mention an issue use `#issue-id` and to mention a merge request use `!merge-request-id`
To close/fix an issue use `Close #issue-id` or `Fix #issue-id` (depending on the merge request)
-->



Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4a8440af..af4cdeb4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,7 +20,7 @@ jobs:
         uses: actions/checkout@v2.4.0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.0
+        uses: actions/setup-python@v2.3.1
         with:
           python-version: ${{ matrix.python-version }}
 
@@ -60,7 +60,7 @@ jobs:
         uses: actions/checkout@v2.4.0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.0
+        uses: actions/setup-python@v2.3.1
         with:
           python-version: 3.8
 
@@ -90,7 +90,7 @@ jobs:
         uses: actions/checkout@v2.4.0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.0
+        uses: actions/setup-python@v2.3.1
         with:
           python-version: 3.8
 
@@ -117,7 +117,7 @@ jobs:
         uses: actions/checkout@v2.4.0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.0
+        uses: actions/setup-python@v2.3.1
         with:
           python-version: 3.8
 
@@ -134,12 +134,12 @@ jobs:
         uses: actions/checkout@v2.4.0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.0
+        uses: actions/setup-python@v2.3.1
         with:
           python-version: 3.8
 
       - name: Setup node
-        uses: actions/setup-node@v2.4.1
+        uses: actions/setup-node@v2.5.0
 
       - name: Build pages
         run: |
diff --git a/.github/workflows/fragments-check.yml b/.github/workflows/fragments-check.yml
index 8a738c9a..d2de736b 100644
--- a/.github/workflows/fragments-check.yml
+++ b/.github/workflows/fragments-check.yml
@@ -20,7 +20,7 @@ jobs:
           fetch-depth: 0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.0
+        uses: actions/setup-python@v2.3.1
         with:
           python-version: 3.8
 
diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml
index 8850e317..b9c708f4 100644
--- a/.github/workflows/prepare-release.yml
+++ b/.github/workflows/prepare-release.yml
@@ -16,7 +16,7 @@ jobs:
         uses: actions/checkout@v2.4.0
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.0
+        uses: actions/setup-python@v2.3.1
         with:
           python-version: 3.8
 
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index bce5bf18..4ec622b3 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -15,7 +15,7 @@ jobs:
           token: ${{ secrets.PAT_TOKEN }}
 
       - name: Setup python
-        uses: actions/setup-python@v2.3.0
+        uses: actions/setup-python@v2.3.1
         with:
           python-version: 3.8
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 755487f8..83e1d675 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,7 +22,7 @@ Deprecation
 -----------
 
 - `edit_my_nick` rest method. ([#827](https://github.com/hikari-py/hikari/issues/827))
-- EventStream is now a sync context menu not async. ([#864](https://github.com/hikari-py/hikari/issues/864))
+- EventStream is now a sync context manager, not async. ([#864](https://github.com/hikari-py/hikari/issues/864))
 
 
 Features
diff --git a/changes/917.bugfix.md b/changes/917.bugfix.md
new file mode 100644
index 00000000..7479e2be
--- /dev/null
+++ b/changes/917.bugfix.md
@@ -0,0 +1,1 @@
+Add EventStream.filter specialisation to the abc.
diff --git a/changes/918.bugfix.md b/changes/918.bugfix.md
new file mode 100644
index 00000000..3348c15a
--- /dev/null
+++ b/changes/918.bugfix.md
@@ -0,0 +1,1 @@
+Update the app command name regex to account for more recently documented support for non-english characters on Discord's end.
diff --git a/changes/920.feature.md b/changes/920.feature.md
new file mode 100644
index 00000000..f2ec79a7
--- /dev/null
+++ b/changes/920.feature.md
@@ -0,0 +1,1 @@
+Add min_value and max_value to `CommandOption`
diff --git a/changes/928.bugfix.md b/changes/928.bugfix.md
new file mode 100644
index 00000000..e8a1817a
--- /dev/null
+++ b/changes/928.bugfix.md
@@ -0,0 +1,1 @@
+Fix reposition_roles using the wrong route.
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 182f0724..c8e78883 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -9,10 +9,10 @@ mock==4.0.3
 pytest==6.2.5
 pytest-asyncio==0.16.0
 pytest-cov==3.0.0
-pytest-randomly==3.10.2
+pytest-randomly==3.10.3
 
 # Coverage testing.
-coverage==6.1.2
+coverage==6.2
 
 # Other stuff
 async-timeout==4.0.1  # Used for timeouts in some test cases.
@@ -40,7 +40,7 @@ safety~=1.10.3
 # FORMATTING #
 #############
 
-black==21.11b1
+black==21.12b0
 isort==5.10.1
 
 ##################
diff --git a/hikari/api/event_manager.py b/hikari/api/event_manager.py
index 8cd02c76..57e060a9 100644
--- a/hikari/api/event_manager.py
+++ b/hikari/api/event_manager.py
@@ -38,6 +38,7 @@
     from hikari.events import base_events
     from hikari.internal import data_binding
 
+    _T = typing.TypeVar("_T")
     EventT_co = typing.TypeVar("EventT_co", bound=base_events.Event, covariant=True)
     EventT_inv = typing.TypeVar("EventT_inv", bound=base_events.Event)
     PredicateT = typing.Callable[[EventT_co], bool]
@@ -110,6 +111,41 @@ def open(self) -> None:
             closing a stream.
         """
 
+    @abc.abstractmethod
+    def filter(
+        self: _T,
+        *predicates: typing.Union[typing.Tuple[str, typing.Any], typing.Callable[[EventT], bool]],
+        **attrs: typing.Any,
+    ) -> _T:
+        """Filter the items by one or more conditions.
+
+        Each condition is treated as a predicate, being called with each item
+        that this iterator would return when it is requested.
+
+        All conditions must evaluate to `builtins.True` for the item to be
+        returned. If this is not met, then the item is discarded and ignored,
+        the next matching item will be returned instead, if there is one.
+
+        Parameters
+        ----------
+        *predicates : typing.Union[typing.Callable[[ValueT], builtins.bool], typing.Tuple[builtins.str, typing.Any]]
+            Predicates to invoke. These are functions that take a value and
+            return `builtins.True` if it is of interest, or `builtins.False`
+            otherwise. These may instead include 2-`builtins.tuple` objects
+            consisting of a `builtins.str` attribute name (nested attributes
+            are referred to using the `.` operator), and values to compare for
+            equality. This allows you to specify conditions such as
+            `members.filter(("user.bot", True))`.
+        **attrs : typing.Any
+            Alternative to passing 2-tuples. Cannot specify nested attributes
+            using this method.
+
+        Returns
+        -------
+        EventStream[ValueT]
+            The current stream with the new filter applied.
+        """
+
     @abc.abstractmethod
     def __enter__(self) -> EventStream[EventT]:
         raise NotImplementedError
diff --git a/hikari/api/rest.py b/hikari/api/rest.py
index 7b4a2fcf..ea6d3d4a 100644
--- a/hikari/api/rest.py
+++ b/hikari/api/rest.py
@@ -6580,7 +6580,8 @@ def command_builder(self, name: str, description: str, /) -> special_endpoints.C
         Parameters
         ----------
         name : builtins.str
-            The command's name. This should match the regex `^[a-z0-9_-]{1,32}$`.
+            The command's name. This should match the regex `^[\w-]{1,32}$` in
+            Unicode mode and be lowercase.
         description : builtins.str
             The description to set for the command.
             This should be inclusively between 1-100 characters in length.
@@ -6711,7 +6712,8 @@ async def create_application_command(
         application: hikari.snowflakes.SnowflakeishOr[hikari.guilds.PartialApplication]
             Object or ID of the application to create a command for.
         name : builtins.str
-            The command's name. This should match the regex `^[a-z0-9_-]{1,32}$`.
+            The command's name. This should match the regex `^[\w-]{1,32}$` in
+            Unicode mode and be lowercase.
         description : builtins.str
             The description to set for the command.
             This should be inclusively between 1-100 characters in length.
diff --git a/hikari/api/special_endpoints.py b/hikari/api/special_endpoints.py
index a1b1ac45..eb793b69 100644
--- a/hikari/api/special_endpoints.py
+++ b/hikari/api/special_endpoints.py
@@ -901,7 +901,8 @@ def name(self) -> str:
         r"""Name to set for this command.
 
         !!! warning
-            This should match the regex `^[a-z0-9_-]{1,32}$`.
+            This should match the regex `^[\w-]{1,32}$` in Unicode mode
+            and must be lowercase.
 
         Returns
         -------
diff --git a/hikari/commands.py b/hikari/commands.py
index b0f2f326..922d81c1 100644
--- a/hikari/commands.py
+++ b/hikari/commands.py
@@ -113,7 +113,8 @@ class CommandOption:
     r"""The command option's name.
 
     !!! note
-        This will match the regex `^[a-z0-9_-]{1,32}$`.
+        This will match the regex `^[\w-]{1,32}$` in Unicode mode and will be
+        lowercase.
     """
 
     description: str = attr.field(repr=False)
@@ -145,6 +146,20 @@ class CommandOption:
     If `builtins.None`, then all channel types will be accepted.
     """
 
+    min_value: typing.Union[int, float, None] = attr.field(default=None, repr=False)
+    """The minimum value permitted (inclusive).
+
+    This will be `builtins.int` if the type of the option is `hikari.commands.OptionType.INTEGER`
+    and `builtins.float` if the type is `hikari.commands.OptionType.NUMBER`.
+    """
+
+    max_value: typing.Union[int, float, None] = attr.field(default=None, repr=False)
+    """The maximum value permitted (inclusive).
+
+    This will be `builtins.int` if the type of the option is `hikari.commands.OptionType.INTEGER`
+    and `builtins.float` if the type is `hikari.commands.OptionType.NUMBER`.
+    """
+
 
 @attr_extensions.with_copy
 @attr.define(hash=True, kw_only=True, weakref_slot=False)
@@ -164,7 +179,8 @@ class Command(snowflakes.Unique):
     r"""The command's name.
 
     !!! note
-        This will match the regex `^[a-z0-9_-]{1,32}$`.
+        This will match the regex `^[\w-]{1,32}$` in Unicode mode and will be
+        lowercase.
     """
 
     description: str = attr.field(eq=False, hash=False, repr=False)
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index fce6589a..134e0c85 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -1690,6 +1690,8 @@ def _deserialize_command_option(self, payload: data_binding.JSONObject) -> comma
             choices=choices,
             options=suboptions,
             channel_types=channel_types,
+            min_value=payload.get("min_value"),
+            max_value=payload.get("max_value"),
         )
 
     def deserialize_command(
@@ -1913,6 +1915,11 @@ def serialize_command_option(self, option: commands.CommandOption) -> data_bindi
         if option.options is not None:
             payload["options"] = [self.serialize_command_option(suboption) for suboption in option.options]
 
+        if option.min_value is not None:
+            payload["min_value"] = option.min_value
+        if option.max_value is not None:
+            payload["max_value"] = option.max_value
+
         return payload
 
     def deserialize_component_interaction(
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index 20806553..9d43eb45 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -2883,7 +2883,7 @@ async def reposition_roles(
         guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
         positions: typing.Mapping[int, snowflakes.SnowflakeishOr[guilds.PartialRole]],
     ) -> None:
-        route = routes.POST_GUILD_ROLES.compile(guild=guild)
+        route = routes.PATCH_GUILD_ROLES.compile(guild=guild)
         body = [{"id": str(int(role)), "position": pos} for pos, role in positions.items()]
         await self._request(route, json=body)
 
diff --git a/hikari/internal/data_binding.py b/hikari/internal/data_binding.py
index 28577ea3..a6876bd6 100644
--- a/hikari/internal/data_binding.py
+++ b/hikari/internal/data_binding.py
@@ -91,7 +91,6 @@ def dump_json(_: typing.Union[JSONArray, JSONObject], /, *, indent: int = ...) -
     def load_json(_: typing.AnyStr, /) -> typing.Union[JSONArray, JSONObject]:
         """Convert a JSON string to a Python type."""
 
-
 else:
     import json
 


Test patch:
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index 92ce8008..3fa82441 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -2719,6 +2719,8 @@ def command_payload(self):
                     "description": "42",
                     "channel_types": [0, 1, 2],
                     "required": True,
+                    "min_value": 0,
+                    "max_value": 10,
                     "options": [
                         {
                             "type": 6,
@@ -2758,6 +2760,8 @@ def test_deserialize_command(self, entity_factory_impl, mock_app, command_payloa
             channel_models.ChannelType.DM,
             channel_models.ChannelType.GUILD_VOICE,
         ]
+        assert option.min_value == 0
+        assert option.max_value == 10
 
         assert len(option.options) == 1
         suboption = option.options[0]
@@ -3133,6 +3137,27 @@ def test_serialize_command_option_with_channel_type(self, entity_factory_impl):
             "channel_types": [13, 0, 100],
         }
 
+    def test_serialize_command_option_with_min_and_max_value(self, entity_factory_impl):
+        option = commands.CommandOption(
+            type=commands.OptionType.FLOAT,
+            name="a name",
+            description="go away",
+            is_required=True,
+            min_value=1.2,
+            max_value=9.999,
+        )
+
+        result = entity_factory_impl.serialize_command_option(option)
+
+        assert result == {
+            "type": 10,
+            "name": "a name",
+            "description": "go away",
+            "required": True,
+            "min_value": 1.2,
+            "max_value": 9.999,
+        }
+
     def test_serialize_command_option_with_choices(self, entity_factory_impl):
         option = commands.CommandOption(
             type=commands.OptionType.INTEGER,
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index 2a5fab21..009e06aa 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -23,11 +23,11 @@
 import contextlib
 import datetime
 import http
+import re
 import warnings
 
 import mock
 import pytest
-import regex
 
 from hikari import applications
 from hikari import audit_logs
@@ -1903,7 +1903,7 @@ async def test__create_message_when_non_collection_passed_to_plural(self, rest_c
             f"'{singular_arg}' (singular) instead?"
         )
 
-        with pytest.raises(TypeError, match=regex.escape(expected_error_message)):
+        with pytest.raises(TypeError, match=re.escape(expected_error_message)):
             await rest_client._create_message(None, {}, **kwargs)
 
     @pytest.mark.skip("TODO")
@@ -2025,7 +2025,7 @@ async def test__edit_message_when_non_collection_passed_to_plural(self, rest_cli
             f"'{singular_arg}' (singular) instead?"
         )
 
-        with pytest.raises(TypeError, match=regex.escape(expected_error_message)):
+        with pytest.raises(TypeError, match=re.escape(expected_error_message)):
             await rest_client._edit_message(None, {}, **kwargs)
 
     @pytest.mark.skip("TODO")
@@ -3678,7 +3678,7 @@ async def test_create_role_when_icon_unicode_emoji_specified(self, rest_client):
             await rest_client.create_role(StubModel(123), icon="icon.png", unicode_emoji="\N{OK HAND SIGN}")
 
     async def test_reposition_roles(self, rest_client):
-        expected_route = routes.POST_GUILD_ROLES.compile(guild=123)
+        expected_route = routes.PATCH_GUILD_ROLES.compile(guild=123)
         expected_json = [{"id": "456", "position": 1}, {"id": "789", "position": 2}]
         rest_client._request = mock.AsyncMock()