Repository: praw-dev/praw

Issue:
Removal reasons code needs improvement
* Removal reasons could be objectified easily
* The documentation is wholly incorrect

Discussion:
jarhill0: Sorry, I think that one of my PRs must have caused this merge conflict. Can you rebase on top of master when you get the chance?

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index da64bb83..f17f4422 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -15,6 +15,12 @@ Unreleased
 * Add method :meth:`~.Reddit.delete` to :class:`.Reddit` class to support HTTP
   DELETE requests.
 
+**Changed**
+
+* :meth:`.RemovalReason.update`\ 's parameters are now optional.
+* :meth:`.SubredditRemovalReasons.__getitem__` now takes integers and slices to
+  get removal reasons from the list of reasons as returned by Reddit.
+
 **Fixed**
 
 * An issue where certain subreddit settings could not be set through
diff --git a/praw/models/reddit/redditor.py b/praw/models/reddit/redditor.py
index e5801429..325a25c4 100644
--- a/praw/models/reddit/redditor.py
+++ b/praw/models/reddit/redditor.py
@@ -299,7 +299,7 @@ class Redditor(
         """Return a list of the redditor's trophies.
 
         :returns: A ``list`` of :class:`~praw.models.Trophy` objects.
-            Return ``[]`` if the redditor has no trophy.
+            Return an empty list (``[]``) if the redditor has no trophies.
 
         :raises: :class:`.RedditAPIException` if the redditor doesn't exist.
 
diff --git a/praw/models/reddit/removal_reasons.py b/praw/models/reddit/removal_reasons.py
index cb58246a..e4615d39 100644
--- a/praw/models/reddit/removal_reasons.py
+++ b/praw/models/reddit/removal_reasons.py
@@ -1,8 +1,10 @@
-"""Provide the Reason class."""
-from typing import TYPE_CHECKING, Any, Dict, Generator, Optional, Union
+"""Provide the Removal Reason class."""
+from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Union
+from warnings import warn
 
 from ...const import API_PATH
 from ...exceptions import ClientException
+from ...util.cache import cachedproperty
 from .base import RedditBase
 
 if TYPE_CHECKING:  # pragma: no cover
@@ -32,6 +34,28 @@ class RemovalReason(RedditBase):
 
     STR_FIELD = "id"
 
+    @staticmethod
+    def _warn_reason_id(
+        reason_id_value: Optional[str], id_value: Optional[str]
+    ):
+        """Reason id param is deprecated. Warns if it's used.
+
+        :param reason_id_value: The value passed as parameter ``reason_id``.
+        :param id_value: Returns the actual value of parameter ``id`` is
+            parameter ``reason_id`` is not used.
+        """
+        if reason_id_value is not None:
+            warn(
+                "Parameter ``reason_id`` is deprecated. Either use positional"
+                ' arguments (reason_id="x" -> "x") or change the parameter '
+                'name to ``id`` (resaon_id="x" -> id="x"). The parameter will'
+                " be removed in PRAW 8.",
+                category=DeprecationWarning,
+                stacklevel=3,
+            )
+            return reason_id_value
+        return id_value
+
     def __eq__(self, other: Union[str, "RemovalReason"]) -> bool:
         """Return whether the other instance equals the current."""
         if isinstance(other, str):
@@ -46,11 +70,24 @@ class RemovalReason(RedditBase):
         self,
         reddit: "Reddit",
         subreddit: "Subreddit",
-        reason_id: str,
+        id: Optional[str] = None,  # pylint: disable=redefined-builtin
+        reason_id: Optional[str] = None,
         _data: Optional[Dict[str, Any]] = None,
     ):
-        """Construct an instance of the Removal Reason object."""
-        self.id = reason_id
+        """Construct an instance of the Removal Reason object.
+
+        :param reddit: An instance of :class:`.Reddit`.
+        :param subreddit: An instance of :class:`.Subreddit`.
+        :param id: The id of the removal reason.
+        :param reason_id: (Deprecated) The original name of the ``id``
+            parameter. Used for backwards compatibility. This parameter should
+            not be used.
+        """
+        id = self._warn_reason_id(reason_id, id)
+        if (id, _data).count(None) != 1:
+            raise ValueError("Either id or _data needs to be given.")
+
+        self.id = id
         self.subreddit = subreddit
         super().__init__(reddit, _data=_data)
 
@@ -61,7 +98,7 @@ class RemovalReason(RedditBase):
                 self._fetched = True
                 return
         raise ClientException(
-            "r/{} does not have the removal reason {}".format(
+            "Subreddit {} does not have the removal reason {}".format(
                 self.subreddit, self.id
             )
         )
@@ -73,7 +110,7 @@ class RemovalReason(RedditBase):
 
         .. code-block:: python
 
-           reddit.subreddit("NAME").removal_reasons["141vv5c16py7d"].mod.delete()
+           reddit.subreddit("NAME").mod.removal_reasons["141vv5c16py7d"].delete()
 
         """
         url = API_PATH["removal_reason"].format(
@@ -81,17 +118,21 @@ class RemovalReason(RedditBase):
         )
         self._reddit.delete(url)
 
-    def update(self, message: str, title: str):
+    def update(
+        self, message: Optional[str] = None, title: Optional[str] = None
+    ):
         """Update the removal reason from this subreddit.
 
-        :param message: The removal reason's new message (required).
-        :param title: The removal reason's new title (required).
+        .. note:: Existing values will be used for any unspecified arguments.
+
+        :param message: The removal reason's new message.
+        :param title: The removal reason's new title.
 
         To update ``"141vv5c16py7d"`` from the subreddit ``"NAME"`` try:
 
         .. code-block:: python
 
-           reddit.subreddit("NAME").removal_reasons["141vv5c16py7d"].mod.update(
+           reddit.subreddit("NAME").mod.removal_reasons["141vv5c16py7d"].update(
                message="New message",
                title="New title")
 
@@ -99,17 +140,24 @@ class RemovalReason(RedditBase):
         url = API_PATH["removal_reason"].format(
             subreddit=self.subreddit, id=self.id
         )
-        data = {"message": message, "title": title}
-        self.subreddit._reddit.put(url, data=data)
+        data = {
+            name: getattr(self, name) if value is None else value
+            for name, value in {"message": message, "title": title}.items()
+        }
+        self._reddit.put(url, data=data)
 
 
 class SubredditRemovalReasons:
     """Provide a set of functions to a Subreddit's removal reasons."""
 
-    def __getitem__(self, reason_id: str) -> RemovalReason:
-        """Lazily return the Removal Reason for the subreddit with id ``reason_id``.
+    def __getitem__(self, reason_id: Union[str, int, slice]) -> RemovalReason:
+        """Return the Removal Reason with the ID/number/slice ``reason_id``.
+
+        :param reason_id: The ID or index of the removal reason
 
-        :param reason_id: The id of the removal reason
+        .. note:: Removal reasons fetched using a specific rule name are lazy
+            loaded, so you might have to access an attribute to get all of the
+            expected attributes.
 
         This method is to be used to fetch a specific removal reason, like so:
 
@@ -119,8 +167,34 @@ class SubredditRemovalReasons:
            reason = reddit.subreddit("NAME").mod.removal_reasons[reason_id]
            print(reason)
 
+        You can also use indices to get a numbered rule. Since Python
+        uses 0-indexing, the first rule is index 0, and so on.
+
+        .. note:: Both negative indices and slices can be used to interact with
+            the removal reasons.
+
+        :raises: :py:class:`IndexError` if a removal reason of a specific
+            number does not exist.
+
+        For example, to get the second removal reason of the subreddit
+        ``"NAME"``:
+
+        .. code-block:: python
+
+            reason = reddit.subreddit('NAME').mod.removal_reasons[1]
+
+        To get the last three removal reasons in a subreddit:
+
+        .. code-block:: python
+
+            reasons = reddit.subreddit('NAME').mod.removal_reasons[-3:]
+            for reason in reasons:
+                print(reason)
+
         """
-        return RemovalReason(self.subreddit._reddit, self.subreddit, reason_id)
+        if not isinstance(reason_id, str):
+            return self._removal_reason_list[reason_id]
+        return RemovalReason(self._reddit, self.subreddit, reason_id)
 
     def __init__(self, subreddit: "Subreddit"):
         """Create a SubredditRemovalReasons instance.
@@ -131,7 +205,7 @@ class SubredditRemovalReasons:
         self.subreddit = subreddit
         self._reddit = subreddit._reddit
 
-    def __iter__(self) -> Generator[RemovalReason, None, None]:
+    def __iter__(self) -> Iterator[RemovalReason]:
         """Return a list of Removal Reasons for the subreddit.
 
         This method is used to discover all removal reasons for a
@@ -143,13 +217,21 @@ class SubredditRemovalReasons:
                print(removal_reason)
 
         """
-        response = self.subreddit._reddit.get(
+        return iter(self._removal_reason_list)
+
+    @cachedproperty
+    def _removal_reason_list(self) -> List[RemovalReason]:
+        """Get a list of Removal Reason objects.
+
+        :returns: A list of instances of :class:`.RemovalReason`.
+        """
+        response = self._reddit.get(
             API_PATH["removal_reasons_list"].format(subreddit=self.subreddit)
         )
-        for reason_id, reason_data in response["data"].items():
-            yield RemovalReason(
-                self._reddit, self.subreddit, reason_id, _data=reason_data
-            )
+        return [
+            RemovalReason(self._reddit, self.subreddit, _data=reason_data)
+            for id, reason_data in response["data"].items()
+        ]
 
     def add(self, message: str, title: str) -> RemovalReason:
         """Add a removal reason to this subreddit.
@@ -164,12 +246,12 @@ class SubredditRemovalReasons:
 
         .. code-block:: python
 
-           reddit.subreddit("NAME").removal_reasons.mod.add(
+           reddit.subreddit("NAME").mod.removal_reasons.add(
                message="Foobar",
                title="Test")
 
         """
         data = {"message": message, "title": title}
         url = API_PATH["removal_reasons_list"].format(subreddit=self.subreddit)
-        reason_id = self.subreddit._reddit.post(url, data=data)
-        return RemovalReason(self._reddit, self.subreddit, reason_id)
+        id = self._reddit.post(url, data=data)
+        return RemovalReason(self._reddit, self.subreddit, id)


Test patch:
diff --git a/tests/integration/cassettes/TestSubredditRemovalReasons.test_update_empty.json b/tests/integration/cassettes/TestSubredditRemovalReasons.test_update_empty.json
new file mode 100644
index 00000000..64681ec3
--- /dev/null
+++ b/tests/integration/cassettes/TestSubredditRemovalReasons.test_update_empty.json
@@ -0,0 +1,333 @@
+{
+  "http_interactions": [
+    {
+      "recorded_at": "2020-04-30T19:00:44",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": "grant_type=password&password=<PASSWORD>&username=<USERNAME>"
+        },
+        "headers": {
+          "Accept": [
+            "*/*"
+          ],
+          "Accept-Encoding": [
+            "identity"
+          ],
+          "Authorization": [
+            "Basic <BASIC_AUTH>"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "61"
+          ],
+          "Content-Type": [
+            "application/x-www-form-urlencoded"
+          ],
+          "User-Agent": [
+            "<USER_AGENT> PRAW/7.0.1.dev0 prawcore/1.3.0"
+          ]
+        },
+        "method": "POST",
+        "uri": "https://www.reddit.com/api/v1/access_token"
+      },
+      "response": {
+        "body": {
+          "encoding": "UTF-8",
+          "string": "{\"access_token\": \"<ACCESS_TOKEN>\", \"token_type\": \"bearer\", \"expires_in\": 3600, \"scope\": \"*\"}"
+        },
+        "headers": {
+          "Accept-Ranges": [
+            "bytes"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "118"
+          ],
+          "Content-Type": [
+            "application/json; charset=UTF-8"
+          ],
+          "Date": [
+            "Thu, 30 Apr 2020 19:00:44 GMT"
+          ],
+          "Server": [
+            "snooserv"
+          ],
+          "Set-Cookie": [
+            "edgebucket=QKv3g7KzJhhhCZ5zex; Domain=reddit.com; Max-Age=63071999; Path=/;  secure"
+          ],
+          "Strict-Transport-Security": [
+            "max-age=15552000; includeSubDomains; preload"
+          ],
+          "Via": [
+            "1.1 varnish"
+          ],
+          "X-Cache": [
+            "MISS"
+          ],
+          "X-Cache-Hits": [
+            "0"
+          ],
+          "X-Moose": [
+            "majestic"
+          ],
+          "X-Served-By": [
+            "cache-lga21957-LGA"
+          ],
+          "X-Timer": [
+            "S1588273244.946751,VS0,VE312"
+          ],
+          "cache-control": [
+            "max-age=0, must-revalidate"
+          ],
+          "x-content-type-options": [
+            "nosniff"
+          ],
+          "x-frame-options": [
+            "SAMEORIGIN"
+          ],
+          "x-xss-protection": [
+            "1; mode=block"
+          ]
+        },
+        "status": {
+          "code": 200,
+          "message": "OK"
+        },
+        "url": "https://www.reddit.com/api/v1/access_token"
+      }
+    },
+    {
+      "recorded_at": "2020-04-30T19:00:52",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": ""
+        },
+        "headers": {
+          "Accept": [
+            "*/*"
+          ],
+          "Accept-Encoding": [
+            "identity"
+          ],
+          "Authorization": [
+            "bearer <ACCESS_TOKEN>"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Cookie": [
+            "edgebucket=QKv3g7KzJhhhCZ5zex"
+          ],
+          "User-Agent": [
+            "<USER_AGENT> PRAW/7.0.1.dev0 prawcore/1.3.0"
+          ]
+        },
+        "method": "GET",
+        "uri": "https://oauth.reddit.com/api/v1/<TEST_SUBREDDIT>/removal_reasons?raw_json=1"
+      },
+      "response": {
+        "body": {
+          "encoding": "UTF-8",
+          "string": "{\"data\": {\"14w04ir36i07n\": {\"message\": \"dfgfdsggergthh54hrt6eghtdehteg\", \"id\": \"14w04ir36i07n\", \"title\": \"bruh\"}, \"14w04i85r8i3x\": {\"message\": \"dgsdfgsdgdfgfdgsdg\", \"id\": \"14w04i85r8i3x\", \"title\": \"test\"}}, \"order\": [\"14w04i85r8i3x\", \"14w04ir36i07n\"]}"
+        },
+        "headers": {
+          "Accept-Ranges": [
+            "bytes"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "251"
+          ],
+          "Content-Type": [
+            "application/json; charset=UTF-8"
+          ],
+          "Date": [
+            "Thu, 30 Apr 2020 19:00:52 GMT"
+          ],
+          "Server": [
+            "snooserv"
+          ],
+          "Set-Cookie": [
+            "session_tracker=x4nVyN2qelVgcJknxV.0.1588273251948.Z0FBQUFBQmVxeUJqZUxPb3JzanZCdHFXWkVzcnRQR1U2WGI4X2FJSDUwTGJJM0dQXzFpcWxLRk1MTFFlYktIdHptVkhfV0lUMUx1c3g0ZVdWamJHYlhnMkFieEtFUXlBbG00dVpVeGlkcUJXVFN3emMtRnVQdENEVHRYUGdsTG5JTGlMVWNYVUUwbHY; Domain=reddit.com; Max-Age=7199; Path=/; expires=Thu, 30-Apr-2020 21:00:51 GMT; secure; SameSite=None; Secure",
+            "csv=1; Max-Age=63072000; Domain=.reddit.com; Path=/; Secure; SameSite=None"
+          ],
+          "Strict-Transport-Security": [
+            "max-age=15552000; includeSubDomains; preload"
+          ],
+          "Via": [
+            "1.1 varnish"
+          ],
+          "X-Cache": [
+            "MISS"
+          ],
+          "X-Cache-Hits": [
+            "0"
+          ],
+          "X-Moose": [
+            "majestic"
+          ],
+          "X-Served-By": [
+            "cache-lga21944-LGA"
+          ],
+          "X-Timer": [
+            "S1588273252.873834,VS0,VE135"
+          ],
+          "cache-control": [
+            "private, s-maxage=0, max-age=0, must-revalidate, no-store, max-age=0, must-revalidate"
+          ],
+          "expires": [
+            "-1"
+          ],
+          "x-content-type-options": [
+            "nosniff"
+          ],
+          "x-frame-options": [
+            "SAMEORIGIN"
+          ],
+          "x-ratelimit-remaining": [
+            "599.0"
+          ],
+          "x-ratelimit-reset": [
+            "549"
+          ],
+          "x-ratelimit-used": [
+            "1"
+          ],
+          "x-xss-protection": [
+            "1; mode=block"
+          ]
+        },
+        "status": {
+          "code": 200,
+          "message": "OK"
+        },
+        "url": "https://oauth.reddit.com/api/v1/<TEST_SUBREDDIT>/removal_reasons?raw_json=1"
+      }
+    },
+    {
+      "recorded_at": "2020-04-30T19:00:52",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": "api_type=json&message=dfgfdsggergthh54hrt6eghtdehteg&title=bruh"
+        },
+        "headers": {
+          "Accept": [
+            "*/*"
+          ],
+          "Accept-Encoding": [
+            "identity"
+          ],
+          "Authorization": [
+            "bearer <ACCESS_TOKEN>"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "63"
+          ],
+          "Content-Type": [
+            "application/x-www-form-urlencoded"
+          ],
+          "Cookie": [
+            "csv=1; edgebucket=QKv3g7KzJhhhCZ5zex; session_tracker=x4nVyN2qelVgcJknxV.0.1588273251948.Z0FBQUFBQmVxeUJqZUxPb3JzanZCdHFXWkVzcnRQR1U2WGI4X2FJSDUwTGJJM0dQXzFpcWxLRk1MTFFlYktIdHptVkhfV0lUMUx1c3g0ZVdWamJHYlhnMkFieEtFUXlBbG00dVpVeGlkcUJXVFN3emMtRnVQdENEVHRYUGdsTG5JTGlMVWNYVUUwbHY"
+          ],
+          "User-Agent": [
+            "<USER_AGENT> PRAW/7.0.1.dev0 prawcore/1.3.0"
+          ]
+        },
+        "method": "PUT",
+        "uri": "https://oauth.reddit.com/api/v1/<TEST_SUBREDDIT>/removal_reasons/14w04ir36i07n?raw_json=1"
+      },
+      "response": {
+        "body": {
+          "encoding": "UTF-8",
+          "string": ""
+        },
+        "headers": {
+          "Accept-Ranges": [
+            "bytes"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "0"
+          ],
+          "Content-Type": [
+            "application/json; charset=UTF-8"
+          ],
+          "Date": [
+            "Thu, 30 Apr 2020 19:00:52 GMT"
+          ],
+          "Server": [
+            "snooserv"
+          ],
+          "Strict-Transport-Security": [
+            "max-age=15552000; includeSubDomains; preload"
+          ],
+          "Via": [
+            "1.1 varnish"
+          ],
+          "X-Cache": [
+            "MISS"
+          ],
+          "X-Cache-Hits": [
+            "0"
+          ],
+          "X-Moose": [
+            "majestic"
+          ],
+          "X-Served-By": [
+            "cache-lga21944-LGA"
+          ],
+          "X-Timer": [
+            "S1588273252.045403,VS0,VE97"
+          ],
+          "cache-control": [
+            "private, s-maxage=0, max-age=0, must-revalidate, no-store, max-age=0, must-revalidate"
+          ],
+          "expires": [
+            "-1"
+          ],
+          "set-cookie": [
+            "session_tracker=x4nVyN2qelVgcJknxV.0.1588273252111.Z0FBQUFBQmVxeUJrSXR0Qm5zbWJEY1luanZBUkk0d1BUTFdEbVRPRzJUUmFXZDZKeExWTndqWVNLV0pKSUUzR1FmV0RFTDJ3aFZTRkx3RjBLT2V4WU8zdExFWVo3YVk1NG1uSmNOWGtpa1RDdV9MS3NlYWgxcTFib1VRZkFkbXctRFBXcU9mdEJoM1o; Domain=reddit.com; Max-Age=7199; Path=/; expires=Thu, 30-Apr-2020 21:00:52 GMT; secure"
+          ],
+          "x-content-type-options": [
+            "nosniff"
+          ],
+          "x-frame-options": [
+            "SAMEORIGIN"
+          ],
+          "x-ratelimit-remaining": [
+            "598.0"
+          ],
+          "x-ratelimit-reset": [
+            "548"
+          ],
+          "x-ratelimit-used": [
+            "2"
+          ],
+          "x-xss-protection": [
+            "1; mode=block"
+          ]
+        },
+        "status": {
+          "code": 200,
+          "message": "OK"
+        },
+        "url": "https://oauth.reddit.com/api/v1/<TEST_SUBREDDIT>/removal_reasons/14w04ir36i07n?raw_json=1"
+      }
+    }
+  ],
+  "recorded_with": "betamax/0.8.1"
+}
diff --git a/tests/integration/models/reddit/test_removal_reasons.py b/tests/integration/models/reddit/test_removal_reasons.py
index c6582a3e..63f2c61f 100644
--- a/tests/integration/models/reddit/test_removal_reasons.py
+++ b/tests/integration/models/reddit/test_removal_reasons.py
@@ -20,6 +20,22 @@ class TestRemovalReason(IntegrationTest):
         with self.recorder.use_cassette("TestRemovalReason.test__fetch"):
             assert reason.title.startswith("Be Kind")
 
+    @mock.patch("time.sleep", return_value=None)
+    def test__fetch_int(self, _):
+        self.reddit.read_only = False
+        with self.recorder.use_cassette("TestRemovalReason.test__fetch"):
+            reason = self.subreddit.mod.removal_reasons[0]
+            assert isinstance(reason, RemovalReason)
+
+    @mock.patch("time.sleep", return_value=None)
+    def test__fetch_slice(self, _):
+        self.reddit.read_only = False
+        with self.recorder.use_cassette("TestRemovalReason.test__fetch"):
+            reasons = self.subreddit.mod.removal_reasons[-3:]
+            assert len(reasons) == 3
+            for reason in reasons:
+                assert isinstance(reason, RemovalReason)
+
     @mock.patch("time.sleep", return_value=None)
     def test__fetch__invalid_reason(self, _):
         self.reddit.read_only = False
@@ -30,7 +46,7 @@ class TestRemovalReason(IntegrationTest):
             with pytest.raises(ClientException) as excinfo:
                 reason.title
             assert str(excinfo.value) == (
-                "r/{} does not have the removal reason {}".format(
+                "Subreddit {} does not have the removal reason {}".format(
                     self.subreddit, "invalid"
                 )
             )
@@ -44,6 +60,15 @@ class TestRemovalReason(IntegrationTest):
             reason = self.subreddit.mod.removal_reasons["110nhk2cgmaxy"]
             reason.update(message="New Message", title="New Title")
 
+    @mock.patch("time.sleep", return_value=None)
+    def test_update_empty(self, _):
+        self.reddit.read_only = False
+        with self.recorder.use_cassette(
+            "TestSubredditRemovalReasons.test_update_empty"
+        ):
+            reason = self.subreddit.mod.removal_reasons[0]
+            reason.update()
+
     @mock.patch("time.sleep", return_value=None)
     def test_delete(self, _):
         self.reddit.read_only = False
diff --git a/tests/unit/models/reddit/test_removal_reasons.py b/tests/unit/models/reddit/test_removal_reasons.py
index 2547fc31..dcefdca0 100644
--- a/tests/unit/models/reddit/test_removal_reasons.py
+++ b/tests/unit/models/reddit/test_removal_reasons.py
@@ -1,6 +1,8 @@
 import pickle
 
-from praw.models import RemovalReason, Subreddit
+import pytest
+
+from praw.models import RemovalReason
 from praw.models.reddit.removal_reasons import SubredditRemovalReasons
 
 from ... import UnitTest
@@ -9,19 +11,19 @@ from ... import UnitTest
 class TestRemovalReason(UnitTest):
     def test_equality(self):
         reason1 = RemovalReason(
-            self.reddit, subreddit=Subreddit(self.reddit, "a"), reason_id="x"
+            self.reddit, subreddit=self.reddit.subreddit("a"), id="x"
         )
         reason2 = RemovalReason(
-            self.reddit, subreddit=Subreddit(self.reddit, "a"), reason_id="2"
+            self.reddit, subreddit=self.reddit.subreddit("a"), id="2"
         )
         reason3 = RemovalReason(
-            self.reddit, subreddit=Subreddit(self.reddit, "b"), reason_id="1"
+            self.reddit, subreddit=self.reddit.subreddit("b"), id="1"
         )
         reason4 = RemovalReason(
-            self.reddit, subreddit=Subreddit(self.reddit, "A"), reason_id="x"
+            self.reddit, subreddit=self.reddit.subreddit("A"), id="x"
         )
         reason5 = RemovalReason(
-            self.reddit, subreddit=Subreddit(self.reddit, "a"), reason_id="X"
+            self.reddit, subreddit=self.reddit.subreddit("a"), reason_id="X"
         )
         assert reason1 == reason1
         assert reason1 == "x"
@@ -32,6 +34,17 @@ class TestRemovalReason(UnitTest):
         assert reason1 == reason4
         assert reason1 != reason5
 
+    def test_exception(self):
+        with pytest.raises(ValueError):
+            RemovalReason(self.reddit, subreddit=self.reddit.subreddit("a"))
+        with pytest.raises(ValueError):
+            RemovalReason(
+                self.reddit,
+                subreddit=self.reddit.subreddit("a"),
+                id="test",
+                _data={},
+            )
+
     def test__get(self):
         subreddit = self.reddit.subreddit("a")
         removal_reason = subreddit.mod.removal_reasons["a"]
@@ -39,19 +52,19 @@ class TestRemovalReason(UnitTest):
 
     def test_hash(self):
         reason1 = RemovalReason(
-            self.reddit, subreddit=Subreddit(self.reddit, "a"), reason_id="x"
+            self.reddit, subreddit=self.reddit.subreddit("a"), id="x"
         )
         reason2 = RemovalReason(
-            self.reddit, subreddit=Subreddit(self.reddit, "a"), reason_id="2"
+            self.reddit, subreddit=self.reddit.subreddit("a"), id="2"
         )
         reason3 = RemovalReason(
-            self.reddit, subreddit=Subreddit(self.reddit, "b"), reason_id="1"
+            self.reddit, subreddit=self.reddit.subreddit("b"), id="1"
         )
         reason4 = RemovalReason(
-            self.reddit, subreddit=Subreddit(self.reddit, "A"), reason_id="x"
+            self.reddit, subreddit=self.reddit.subreddit("A"), id="x"
         )
         reason5 = RemovalReason(
-            self.reddit, subreddit=Subreddit(self.reddit, "a"), reason_id="X"
+            self.reddit, subreddit=self.reddit.subreddit("a"), reason_id="X"
         )
         assert hash(reason1) == hash(reason1)
         assert hash(reason2) == hash(reason2)
@@ -63,7 +76,7 @@ class TestRemovalReason(UnitTest):
 
     def test_pickle(self):
         reason = RemovalReason(
-            self.reddit, subreddit=Subreddit(self.reddit, "a"), reason_id="x"
+            self.reddit, subreddit=self.reddit.subreddit("a"), id="x"
         )
         for level in range(pickle.HIGHEST_PROTOCOL + 1):
             other = pickle.loads(pickle.dumps(reason, protocol=level))
@@ -71,18 +84,18 @@ class TestRemovalReason(UnitTest):
 
     def test_repr(self):
         reason = RemovalReason(
-            self.reddit, subreddit=Subreddit(self.reddit, "a"), reason_id="x"
+            self.reddit, subreddit=self.reddit.subreddit("a"), id="x"
         )
         assert repr(reason) == ("RemovalReason(id='x')")
 
     def test_str(self):
         reason = RemovalReason(
-            self.reddit, subreddit=Subreddit(self.reddit, "a"), reason_id="x"
+            self.reddit, subreddit=self.reddit.subreddit("a"), id="x"
         )
         assert str(reason) == "x"
 
 
 class TestSubredditRemovalReasons(UnitTest):
     def test_repr(self):
-        sr = SubredditRemovalReasons(subreddit=Subreddit(self.reddit, "a"))
+        sr = SubredditRemovalReasons(subreddit=self.reddit.subreddit("a"))
         assert repr(sr)  # assert it has some repr