Repository: hikari-py/hikari
Pull request #643 (2021-06-03 18:21:30)

Separate member and author on GuildMessage* events
This will provide more consistency as well as fix .author raising and error when a webhook sends/edits a message

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [x] I have run `nox` and all the pipelines have passed.
- [x] I have made unittests according to the code I have added/modified/deleted.



Patch:
diff --git a/hikari/events/message_events.py b/hikari/events/message_events.py
index 930c2a09..303e39c4 100644
--- a/hikari/events/message_events.py
+++ b/hikari/events/message_events.py
@@ -220,17 +220,27 @@ class GuildMessageCreateEvent(MessageCreateEvent):
     # <<inherited docstring from ShardEvent>>
 
     @property
-    def author(self) -> guilds.Member:
+    def author(self) -> users.User:
+        """User object of the user that sent the message.
+
+        Returns
+        -------
+        hikari.users.User
+            The user object of the user that sent the message.
+        """
+        return self.message.author
+
+    @property
+    def member(self) -> typing.Optional[guilds.Member]:
         """Member object of the user that sent the message.
 
         Returns
         -------
-        hikari.guilds.Member
-            The member object of the user that sent the message.
+        typing.Optional[hikari.guilds.Member]
+            The member object of the user that sent the message or
+            `builtins.None` if sent by a webhook.
         """
-        member = self.message.member
-        assert member is not None, "no member given for guild message create event!"
-        return member
+        return self.message.member
 
     @property
     def channel(self) -> typing.Union[None, channels.GuildTextChannel, channels.GuildNewsChannel]:
@@ -476,18 +486,34 @@ class GuildMessageUpdateEvent(MessageUpdateEvent):
 
     @property
     def author(self) -> typing.Optional[users.User]:
-        """Member or user that sent the message.
+        """User that sent the message.
 
         Returns
         -------
         typing.Union[builtins.None, hikari.users.User, hikari.guilds.Member]
-            The user that sent the message. If the member is cached
+            The user that sent the message.
+
+            This will be `builtins.None` in some cases, such as when Discord
+            updates a message with an embed for a URL preview or if the message
+            was sent by a webhook.
+        """
+        return self.message.author
+
+    @property
+    def member(self) -> typing.Optional[guilds.Member]:
+        """Member that sent the message.
+
+        Returns
+        -------
+        typing.Optional[hikari.guilds.Member]
+            The member that sent the message. If the member is cached
             (the intents are enabled), then this will be the corresponding
             member object instead (which is a specialization of the
             user object you should otherwise expect).
 
             This will be `builtins.None` in some cases, such as when Discord
-            updates a message with an embed for a URL preview.
+            updates a message with an embed for a URL preview or if the message
+            was sent by a webhook.
         """
         member = self.message.member
         if member is not None:
@@ -501,7 +527,7 @@ def author(self) -> typing.Optional[users.User]:
             if member is not None:
                 return member
 
-        return author
+        return None
 
     @property
     def channel(self) -> typing.Union[None, channels.GuildTextChannel, channels.GuildNewsChannel]:
diff --git a/hikari/messages.py b/hikari/messages.py
index 9b8b680f..cdb3376f 100644
--- a/hikari/messages.py
+++ b/hikari/messages.py
@@ -542,8 +542,8 @@ class PartialMessage(snowflakes.Unique):
     If the message is not in a guild, this will be `builtins.None`.
 
     This will also be `builtins.None` in some cases such as when Discord updates
-    a message with an embed URL preview or in messages fetched from the
-    REST API.
+    a message with an embed URL preview, in messages fetched from the
+    REST API or messages sent by discord.
     """
 
     content: undefined.UndefinedNoneOr[str] = attr.field(hash=False, eq=False, repr=False)


Test patch:
diff --git a/tests/hikari/events/test_message_events.py b/tests/hikari/events/test_message_events.py
index 0a0a8f6c..7b6af2ba 100644
--- a/tests/hikari/events/test_message_events.py
+++ b/tests/hikari/events/test_message_events.py
@@ -24,7 +24,6 @@
 import pytest
 
 from hikari import channels
-from hikari import guilds
 from hikari import messages
 from hikari import snowflakes
 from hikari import undefined
@@ -207,7 +206,10 @@ def test_guild_property(self, event):
         event.app.cache.get_guild.assert_called_once_with(342123123)
 
     def test_author_property(self, event):
-        assert event.author is event.message.member
+        assert event.author is event.message.author
+
+    def test_member_property(self, event):
+        assert event.member is event.message.member
 
 
 class TestGuildMessageUpdateEvent:
@@ -224,37 +226,46 @@ def event(self):
             shard=mock.Mock(),
         )
 
-    def test_author_property_when_member_defined(self, event):
-        event.message.member = mock.Mock(spec_set=guilds.Member)
+    def test_author_property(self, event):
+        assert event.author is event.message.author
+
+    def test_member_property(self, event):
+        assert event.member is event.message.member
+
+    def test_member_property_when_member_defined(self, event):
+        event.message.member = mock.Mock()
         event.message.author = undefined.UNDEFINED
 
-        assert event.author is event.message.member
+        assert event.member is event.message.member
 
-    def test_author_property_when_member_none_but_cached(self, event):
+    def test_member_property_when_member_none_but_cached(self, event):
         event.message.member = None
-        event.message.author = mock.Mock(spec_set=users.User, id=1234321)
+        event.message.author = mock.Mock(id=1234321)
         event.message.guild_id = snowflakes.Snowflake(696969)
-        real_member = mock.Mock(spec_set=guilds.Member)
+        real_member = mock.Mock()
         event.app.cache.get_member = mock.Mock(return_value=real_member)
 
-        assert event.author is real_member
+        assert event.member is real_member
 
         event.app.cache.get_member.assert_called_once_with(696969, 1234321)
 
-    def test_author_property_when_member_none_but_author_also_none(self, event):
+    def test_member_property_when_member_none_and_author_none(self, event):
         event.message.author = None
         event.message.member = None
 
-        assert event.author is None
+        assert event.member is None
 
         event.app.cache.get_member.assert_not_called()
 
-    def test_author_property_when_member_none_and_uncached_but_author_defined(self, event):
+    def test_member_property_when_member_none_and_uncached(self, event):
         event.message.member = None
+        event.message.author = mock.Mock(id=1234321)
+        event.message.guild_id = snowflakes.Snowflake(696969)
         event.app.cache.get_member = mock.Mock(return_value=None)
-        event.message.author = mock.Mock(spec_set=users.User)
 
-        assert event.author is event.message.author
+        assert event.member is None
+
+        event.app.cache.get_member.assert_called_once_with(696969, 1234321)
 
     def test_guild_id_property(self, event):
         assert event.guild_id == snowflakes.Snowflake(54123123123)