diff --git a/.flake8 b/.flake8
new file mode 100644
index 00000000..e61de53c
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+ignore = E203 E501 W503 W504
diff --git a/CHANGES.rst b/CHANGES.rst
index 1b0c18a6..910336ff 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -59,6 +59,11 @@ Unreleased
   ``RedditAPIException.items``, which returns a list.
 * :class:`.APIException` is an alias to :class:`.RedditAPIException`.
 
+**Changed**
+
+* ``prawcore.BadRequest`` should no longer be raised. Instead a more useful
+  :class:`.RedditAPIException` instance will be raised.
+
 **Deprecated**
 
 * :class:`.APIException` is deprecated and slotted for removal in PRAW 8.0.
diff --git a/praw/models/reddit/mixins/__init__.py b/praw/models/reddit/mixins/__init__.py
index d33f4604..99450720 100644
--- a/praw/models/reddit/mixins/__init__.py
+++ b/praw/models/reddit/mixins/__init__.py
@@ -188,7 +188,8 @@ class ThingModerationMixin:
         .. warning:: The object has to be removed before giving it a removal
             reason. Remove the object with :meth:`.remove`. Trying to add a
             removal reason without removing the object will result in
-            ``prawcore.exceptions.BadRequest`` being thrown.
+            :class:`.RedditAPIException` being thrown with an ``INVALID_ID``
+            error_type.
 
         Reddit adds human-readable information about the object to the message.
 
diff --git a/praw/models/reddit/redditor.py b/praw/models/reddit/redditor.py
index ce8ab354..3b100138 100644
--- a/praw/models/reddit/redditor.py
+++ b/praw/models/reddit/redditor.py
@@ -302,7 +302,7 @@ class Redditor(
         :returns: A ``list`` of :class:`~praw.models.Trophy` objects.
             Return ``[]`` if the redditor has no trophy.
 
-        Raise ``prawcore.exceptions.BadRequest`` if the redditor doesn't exist.
+        Raise :class:`.RedditAPIException` if the redditor doesn't exist.
 
         Usage:
 
diff --git a/praw/models/user.py b/praw/models/user.py
index b2887dcc..2675e46a 100644
--- a/praw/models/user.py
+++ b/praw/models/user.py
@@ -82,7 +82,7 @@ class User(PRAWBase):
             an instance of :class:`.Redditor` or a string can be given.
         :returns: A list of Redditors, or a Redditor if you are friends with
             the given Redditor. The Redditor also has friend attributes.
-        :raises: An instance of ``prawcore.exceptions.BadRequest`` if you are
+        :raises: An instance of :class:`.RedditAPIException` if you are
             not friends with the specified Redditor.
         """
         endpoint = (
diff --git a/praw/reddit.py b/praw/reddit.py
index 6d1a14a9..0722dfc2 100644
--- a/praw/reddit.py
+++ b/praw/reddit.py
@@ -16,11 +16,16 @@ from prawcore import (
     UntrustedAuthenticator,
     session,
 )
+from prawcore.exceptions import BadRequest
 
 from . import models
 from .config import Config
 from .const import API_PATH, USER_AGENT_FORMAT, __version__
-from .exceptions import ClientException, MissingRequiredAttributeException
+from .exceptions import (
+    ClientException,
+    MissingRequiredAttributeException,
+    RedditAPIException,
+)
 from .objector import Objector
 
 try:
@@ -648,9 +653,26 @@ class Reddit:
             (default: None).
 
         """
-        return self._core.request(
-            method, path, data=data, files=files, params=params
-        )
+        try:
+            return self._core.request(
+                method, path, data=data, files=files, params=params
+            )
+        except BadRequest as exception:
+            try:
+                data = exception.response.json()
+            except ValueError:
+                # TODO: Remove this exception after 2020-12-31 if no one has filed a bug against it.
+                raise Exception(
+                    "Unexpected BadRequest without json body. Please file a bug: https://github.com/praw-dev/praw/issues"
+                ) from exception
+            if "fields" in data:
+                assert len(data["fields"]) == 1
+                field = data["fields"][0]
+            else:
+                field = None
+            raise RedditAPIException(
+                [data["reason"], data["explanation"], field]
+            ) from exception
 
     def submission(  # pylint: disable=invalid-name,redefined-builtin
         self, id: Optional[str] = None, url: Optional[str] = None

Praw send_removal_message throws 400 when title > 50 characters
The praw method send_removal_message throws an unhelpful 400 error when attempting to send a removal message with a title > 50 characters. Reddit limits these titles to 50 characters but that limit is not documented anywhere and the error message is not descriptive. Adding documentation would be helpful.

Other alternatives are changing the error thrown when this happens to be more descriptive or implicitly truncating removal reason message titles to 50 characters.

[Link to documentation](https://praw.readthedocs.io/en/latest/code_overview/other/submissionmoderation.html#praw.models.reddit.submission.SubmissionModeration.send_removal_message)
diff --git a/tests/integration/cassettes/TestCommentModeration.test_send_removal_message__error.json b/tests/integration/cassettes/TestCommentModeration.test_send_removal_message__error.json
new file mode 100644
index 00000000..ee706299
--- /dev/null
+++ b/tests/integration/cassettes/TestCommentModeration.test_send_removal_message__error.json
@@ -0,0 +1,347 @@
+{
+  "http_interactions": [
+    {
+      "recorded_at": "2020-03-16T06:04:33",
+      "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": [
+            "59"
+          ],
+          "Content-Type": [
+            "application/x-www-form-urlencoded"
+          ],
+          "User-Agent": [
+            "<USER_AGENT> PRAW/7.0.0.dev0 prawcore/1.0.1"
+          ]
+        },
+        "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": [
+            "113"
+          ],
+          "Content-Type": [
+            "application/json; charset=UTF-8"
+          ],
+          "Date": [
+            "Mon, 16 Mar 2020 06:04:36 GMT"
+          ],
+          "Server": [
+            "snooserv"
+          ],
+          "Set-Cookie": [
+            "edgebucket=vMiBSRA7UnxPlljzqc; 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-lax8626-LAX"
+          ],
+          "X-Timer": [
+            "S1584338676.421791,VS0,VE361"
+          ],
+          "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-03-16T06:04:33",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": "api_type=json&id=t1_fkmrn4a&spam=False"
+        },
+        "headers": {
+          "Accept": [
+            "*/*"
+          ],
+          "Accept-Encoding": [
+            "identity"
+          ],
+          "Authorization": [
+            "bearer <ACCESS_TOKEN>"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "38"
+          ],
+          "Content-Type": [
+            "application/x-www-form-urlencoded"
+          ],
+          "Cookie": [
+            "edgebucket=vMiBSRA7UnxPlljzqc"
+          ],
+          "User-Agent": [
+            "<USER_AGENT> PRAW/7.0.0.dev0 prawcore/1.0.1"
+          ]
+        },
+        "method": "POST",
+        "uri": "https://oauth.reddit.com/api/remove/?raw_json=1"
+      },
+      "response": {
+        "body": {
+          "encoding": "UTF-8",
+          "string": "{}"
+        },
+        "headers": {
+          "Accept-Ranges": [
+            "bytes"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "2"
+          ],
+          "Content-Type": [
+            "application/json; charset=UTF-8"
+          ],
+          "Date": [
+            "Mon, 16 Mar 2020 06:04:37 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-lax8629-LAX"
+          ],
+          "X-Timer": [
+            "S1584338677.942068,VS0,VE345"
+          ],
+          "cache-control": [
+            "private, s-maxage=0, max-age=0, must-revalidate, no-store, max-age=0, must-revalidate"
+          ],
+          "expires": [
+            "-1"
+          ],
+          "set-cookie": [
+            "loid=00000000000004cixf.2.1284611485984.Z0FBQUFBQmVieGIxUHM1aDFrNUlSb0cxVHRuU2FGckhlNTIta3JRSkU5UTM3ZWMyY0k4SVlDVkdacmpHMWtHdFhqZ3lNREVxaTRGbEFjd2lQNEZJUmZoeGhTQ0tTcnBDY1l3QVVrOGdWUmJBUG5MNklWdzV0UU9ZNkduWGZFZFNYZVFJb0tiWjRlT2w; Domain=reddit.com; Max-Age=63071999; Path=/; expires=Wed, 16-Mar-2022 06:04:37 GMT; secure",
+            "redesign_optout=true; Domain=reddit.com; Max-Age=94607999; Path=/; expires=Thu, 16-Mar-2023 06:04:37 GMT; secure",
+            "session_tracker=DclkBLJCvWnYI0e8FA.0.1584338677003.Z0FBQUFBQmVieGIxZ2FfRFZJeXdHTU5jRWp0aGlNWXNTeHlYTXZwSGVuRHVtYWt5eVU3WTdYWHIyeFdmeTE4S2dZYXFzSk5idHFEVml3T0lXckQ3TmF4NDNjeG8yNVo5QzhrQ2dvc2tlZ0t1Qkl4NFJOckYyU3JvLUpVZ1hRS3BTb0VRMFdxNUJmVE0; Domain=reddit.com; Max-Age=7199; Path=/; expires=Mon, 16-Mar-2020 08:04:37 GMT; secure"
+          ],
+          "x-content-type-options": [
+            "nosniff"
+          ],
+          "x-frame-options": [
+            "SAMEORIGIN"
+          ],
+          "x-ratelimit-remaining": [
+            "598.0"
+          ],
+          "x-ratelimit-reset": [
+            "323"
+          ],
+          "x-ratelimit-used": [
+            "2"
+          ],
+          "x-ua-compatible": [
+            "IE=edge"
+          ],
+          "x-xss-protection": [
+            "1; mode=block"
+          ]
+        },
+        "status": {
+          "code": 200,
+          "message": "OK"
+        },
+        "url": "https://oauth.reddit.com/api/remove/?raw_json=1"
+      }
+    },
+    {
+      "recorded_at": "2020-03-16T06:04:33",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": "api_type=json&json=%7B%22item_id%22%3A+%5B%22t1_fkmrn4a%22%5D%2C+%22message%22%3A+%22message%22%2C+%22title%22%3A+%22aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%22%2C+%22type%22%3A+%22public%22%7D"
+        },
+        "headers": {
+          "Accept": [
+            "*/*"
+          ],
+          "Accept-Encoding": [
+            "identity"
+          ],
+          "Authorization": [
+            "bearer <ACCESS_TOKEN>"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "204"
+          ],
+          "Content-Type": [
+            "application/x-www-form-urlencoded"
+          ],
+          "Cookie": [
+            "edgebucket=vMiBSRA7UnxPlljzqc; loid=00000000000004cixf.2.1284611485984.Z0FBQUFBQmVieGIxUHM1aDFrNUlSb0cxVHRuU2FGckhlNTIta3JRSkU5UTM3ZWMyY0k4SVlDVkdacmpHMWtHdFhqZ3lNREVxaTRGbEFjd2lQNEZJUmZoeGhTQ0tTcnBDY1l3QVVrOGdWUmJBUG5MNklWdzV0UU9ZNkduWGZFZFNYZVFJb0tiWjRlT2w; redesign_optout=true; session_tracker=DclkBLJCvWnYI0e8FA.0.1584338677003.Z0FBQUFBQmVieGIxZ2FfRFZJeXdHTU5jRWp0aGlNWXNTeHlYTXZwSGVuRHVtYWt5eVU3WTdYWHIyeFdmeTE4S2dZYXFzSk5idHFEVml3T0lXckQ3TmF4NDNjeG8yNVo5QzhrQ2dvc2tlZ0t1Qkl4NFJOckYyU3JvLUpVZ1hRS3BTb0VRMFdxNUJmVE0"
+          ],
+          "User-Agent": [
+            "<USER_AGENT> PRAW/7.0.0.dev0 prawcore/1.0.1"
+          ]
+        },
+        "method": "POST",
+        "uri": "https://oauth.reddit.com/api/v1/modactions/removal_comment_message?raw_json=1"
+      },
+      "response": {
+        "body": {
+          "encoding": "UTF-8",
+          "string": "{\"fields\": [\"title\"], \"explanation\": \"this is too long (max: 50)\", \"message\": \"Bad Request\", \"reason\": \"TOO_LONG\"}"
+        },
+        "headers": {
+          "Accept-Ranges": [
+            "bytes"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "114"
+          ],
+          "Content-Type": [
+            "application/json; charset=UTF-8"
+          ],
+          "Date": [
+            "Mon, 16 Mar 2020 06:04:37 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-lax8629-LAX"
+          ],
+          "X-Timer": [
+            "S1584338677.309116,VS0,VE132"
+          ],
+          "cache-control": [
+            "private, s-maxage=0, max-age=0, must-revalidate, no-store, max-age=0, must-revalidate"
+          ],
+          "expires": [
+            "-1"
+          ],
+          "set-cookie": [
+            "session_tracker=DclkBLJCvWnYI0e8FA.0.1584338677365.Z0FBQUFBQmVieGIxTmF1dlJQWGZCUldaX1pZajBGdjFkV2IxTXNsM0dDUWxidm9CT2tjT3F6NWt4WDBlR0djNF9qczFGS2VVUEJVSTk4YkZoSUx3TmVVTGxlM1J3anR6X19jbGJOdU9fcW85Y0hVX2ZKcnZvMmtGRmk2bUNiS2lFV2VaZVNXcWVHQ0w; Domain=reddit.com; Max-Age=7199; Path=/; expires=Mon, 16-Mar-2020 08:04:37 GMT; secure",
+            "session_tracker=DclkBLJCvWnYI0e8FA.0.1584338677407.Z0FBQUFBQmVieGIxQlh3eE41dlNYTzhVU183REZiSkdXdkYwUWpybF9qQmdlcE5Hb3R0S2NmSDBIYzF0V2N4ei1SZTR4R3ItMFA2UmczSXhSOFhMMDdEQ0JBVkV3OTdsUEhrZEFnSjFkQ2NNZTNUa3h2bEpCVTloc3NMOHJhYzNZeUdvWUJsUUFhQmE; Domain=reddit.com; Max-Age=7199; Path=/; expires=Mon, 16-Mar-2020 08:04:37 GMT; secure"
+          ],
+          "x-content-type-options": [
+            "nosniff"
+          ],
+          "x-frame-options": [
+            "SAMEORIGIN"
+          ],
+          "x-ratelimit-remaining": [
+            "597.0"
+          ],
+          "x-ratelimit-reset": [
+            "323"
+          ],
+          "x-ratelimit-used": [
+            "3"
+          ],
+          "x-ua-compatible": [
+            "IE=edge"
+          ],
+          "x-xss-protection": [
+            "1; mode=block"
+          ]
+        },
+        "status": {
+          "code": 400,
+          "message": "Bad Request"
+        },
+        "url": "https://oauth.reddit.com/api/v1/modactions/removal_comment_message?raw_json=1"
+      }
+    }
+  ],
+  "recorded_with": "betamax/0.8.1"
+}
\ No newline at end of file
diff --git a/tests/integration/models/reddit/test_comment.py b/tests/integration/models/reddit/test_comment.py
index ff64d64a..913dd6f9 100644
--- a/tests/integration/models/reddit/test_comment.py
+++ b/tests/integration/models/reddit/test_comment.py
@@ -1,8 +1,7 @@
 import mock
 import pytest
-from prawcore import BadRequest
 
-from praw.exceptions import ClientException, PRAWException
+from praw.exceptions import ClientException, PRAWException, RedditAPIException
 from praw.models import Comment, Submission
 
 from ... import IntegrationTest
@@ -76,10 +75,10 @@ class TestComment(IntegrationTest):
     def test_gild__no_creddits(self):
         self.reddit.read_only = False
         with self.recorder.use_cassette("TestComment.test_gild__no_creddits"):
-            with pytest.raises(BadRequest) as excinfo:
+            with pytest.raises(RedditAPIException) as excinfo:
                 Comment(self.reddit, "d1616q2").gild()
-            reason = excinfo.value.response.json()["reason"]
-            assert "INSUFFICIENT_CREDDITS" == reason
+            exception = excinfo.value
+            assert "INSUFFICIENT_CREDDITS" == exception.error_type
 
     def test_invalid(self):
         with self.recorder.use_cassette("TestComment.test_invalid"):
@@ -361,6 +360,20 @@ class TestCommentModeration(IntegrationTest):
             assert res[1] is None
             assert res[2] is None
 
+    @mock.patch("time.sleep", return_value=None)
+    def test_send_removal_message__error(self, _):
+        self.reddit.read_only = False
+        with self.recorder.use_cassette(
+            "TestCommentModeration.test_send_removal_message__error"
+        ):
+            comment = self.reddit.comment("fkmrn4a")
+            comment.mod.remove()
+            with pytest.raises(RedditAPIException) as excinfo:
+                comment.mod.send_removal_message("message", "a" * 51)
+            exception = excinfo.value
+            assert "title" == exception.field
+            assert "TOO_LONG" == exception.error_type
+
     def test_undistinguish(self):
         self.reddit.read_only = False
         with self.recorder.use_cassette(
diff --git a/tests/integration/models/reddit/test_redditor.py b/tests/integration/models/reddit/test_redditor.py
index e823314c..46c9a994 100644
--- a/tests/integration/models/reddit/test_redditor.py
+++ b/tests/integration/models/reddit/test_redditor.py
@@ -1,8 +1,9 @@
 """Test praw.models.redditor."""
 import mock
 import pytest
-from prawcore import BadRequest, Forbidden
+from prawcore import Forbidden
 
+from praw.exceptions import RedditAPIException
 from praw.models import Comment, Submission
 
 from ... import IntegrationTest
@@ -29,9 +30,9 @@ class TestRedditor(IntegrationTest):
         with self.recorder.use_cassette(
             "TestRedditor.test_friend__with_note__no_gold"
         ):
-            with pytest.raises(BadRequest) as excinfo:
+            with pytest.raises(RedditAPIException) as excinfo:
                 self.reddit.redditor(self.FRIEND.lower()).friend(note="praw")
-            assert "GOLD_REQUIRED" == excinfo.value.response.json()["reason"]
+            assert "GOLD_REQUIRED" == excinfo.value.error_type
 
     @mock.patch("time.sleep", return_value=None)
     def test_friend_info(self, _):
@@ -53,10 +54,9 @@ class TestRedditor(IntegrationTest):
     def test_gild__no_creddits(self):
         self.reddit.read_only = False
         with self.recorder.use_cassette("TestRedditor.test_gild__no_creddits"):
-            with pytest.raises(BadRequest) as excinfo:
+            with pytest.raises(RedditAPIException) as excinfo:
                 self.reddit.redditor("subreddit_stats").gild()
-            reason = excinfo.value.response.json()["reason"]
-            assert "INSUFFICIENT_CREDDITS" == reason
+            assert "INSUFFICIENT_CREDDITS" == excinfo.value.error_type
 
     @mock.patch("time.sleep", return_value=None)
     def test_message(self, _):
@@ -126,10 +126,9 @@ class TestRedditor(IntegrationTest):
         with self.recorder.use_cassette(
             "TestRedditor.test_trophies__user_not_exist"
         ):
-            with pytest.raises(BadRequest) as excinfo:
+            with pytest.raises(RedditAPIException) as excinfo:
                 redditor.trophies()
-            response = excinfo.value.response
-            assert "USER_DOESNT_EXIST" == response.json()["reason"]
+            assert "USER_DOESNT_EXIST" == excinfo.value.error_type
 
     @mock.patch("time.sleep", return_value=None)
     def test_unblock(self, _):
diff --git a/tests/integration/models/reddit/test_submission.py b/tests/integration/models/reddit/test_submission.py
index 11065668..d1a63b65 100644
--- a/tests/integration/models/reddit/test_submission.py
+++ b/tests/integration/models/reddit/test_submission.py
@@ -1,8 +1,7 @@
 import mock
 import pytest
-from prawcore import BadRequest
 
-from praw.exceptions import APIException
+from praw.exceptions import RedditAPIException
 from praw.models import Comment, Submission
 
 from ... import IntegrationTest
@@ -62,9 +61,7 @@ class TestSubmission(IntegrationTest):
         self.reddit.validate_on_submit = True
         with self.recorder.use_cassette("TestSubmission.test_edit_invalid"):
             submission = Submission(self.reddit, "eippcc")
-            with pytest.raises(
-                (APIException, BadRequest)
-            ):  # waiting for prawcore fix
+            with pytest.raises(RedditAPIException):
                 submission.edit("rewtwert")
 
     def test_enable_inbox_replies(self):
@@ -80,10 +77,10 @@ class TestSubmission(IntegrationTest):
         with self.recorder.use_cassette(
             "TestSubmission.test_gild__no_creddits"
         ):
-            with pytest.raises(BadRequest) as excinfo:
+            with pytest.raises(RedditAPIException) as excinfo:
                 Submission(self.reddit, "4b1tfm").gild()
-            reason = excinfo.value.response.json()["reason"]
-            assert "INSUFFICIENT_CREDDITS" == reason
+            exception = excinfo.value
+            assert "INSUFFICIENT_CREDDITS" == exception.error_type
 
     def test_gilded(self):
         with self.recorder.use_cassette("TestSubmission.test_gilded"):
diff --git a/tests/integration/models/test_user.py b/tests/integration/models/test_user.py
index 7887a5aa..427e3541 100644
--- a/tests/integration/models/test_user.py
+++ b/tests/integration/models/test_user.py
@@ -1,8 +1,8 @@
 """Test praw.models.user."""
 import mock
 import pytest
-from prawcore.exceptions import BadRequest
 
+from praw.exceptions import RedditAPIException
 from praw.models import Multireddit, Redditor, Subreddit
 
 from .. import IntegrationTest
@@ -45,7 +45,7 @@ class TestUser(IntegrationTest):
     def test_friend_not_exist(self, _):
         self.reddit.read_only = False
         with self.recorder.use_cassette("TestUser.test_friend_not_exist"):
-            with pytest.raises(BadRequest):
+            with pytest.raises(RedditAPIException):
                 self.reddit.user.friends(user="fake__user_user_user")
 
     def test_karma(self):
diff --git a/tests/unit/test_reddit.py b/tests/unit/test_reddit.py
index 177214ea..11fb1c9d 100644
--- a/tests/unit/test_reddit.py
+++ b/tests/unit/test_reddit.py
@@ -4,6 +4,7 @@ import types
 import mock
 import pytest
 from prawcore import Requestor
+from prawcore.exceptions import BadRequest
 
 from praw import Reddit, __version__
 from praw.config import Config
@@ -162,6 +163,23 @@ class TestReddit(UnitTest):
             Reddit("bad_site_name")
         assert "praw.readthedocs.io" in excinfo.value.message
 
+    @mock.patch("prawcore.sessions.Session")
+    def test_request__badrequest_with_no_json_body(self, mock_session):
+        response = mock.Mock(status_code=400)
+        response.json.side_effect = ValueError
+        mock_session.return_value.request = mock.Mock(
+            side_effect=BadRequest(response=response)
+        )
+
+        reddit = Reddit(
+            client_id="dummy", client_secret="dummy", user_agent="dummy"
+        )
+        with pytest.raises(Exception) as excinfo:
+            reddit.request("POST", "/")
+        assert str(excinfo.value).startswith(
+            "Unexpected BadRequest without json body."
+        )
+
     def test_submission(self):
         assert self.reddit.submission("2gmzqe").id == "2gmzqe"
 

pip install -e .[dev]
parse_log_pytest
requirements.txt
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
babel==2.17.0
betamax==0.8.1
betamax-matchers==0.4.0
betamax-serializers==0.2.1
black==25.1.0
certifi==2025.1.31
cfgv==3.4.0
charset-normalizer==3.4.1
click==8.1.8
distlib==0.3.9
docutils==0.21.2
exceptiongroup==1.2.2
filelock==3.18.0
flake8==7.2.0
identify==2.6.9
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig==2.1.0
Jinja2==3.1.6
MarkupSafe==3.0.2
mccabe==0.7.0
mock==5.2.0
mypy-extensions==1.0.0
nodeenv==1.9.1
packaging==24.2
pathspec==0.12.1
platformdirs==4.3.7
pluggy==1.5.0
-e git+https://github.com/praw-dev/praw.git@6df52d289557704dc4478afc0c4cfcb25eeaf43b#egg=praw
prawcore==1.5.0
pre_commit==4.2.0
pycodestyle==2.13.0
pydocstyle==6.3.0
pyflakes==3.3.1
Pygments==2.19.1
pytest==8.3.5
PyYAML==6.0.2
requests==2.32.3
requests-toolbelt==1.0.0
snowballstemmer==2.2.0
Sphinx==7.4.7
sphinx-rtd-theme==3.0.2
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
tomli==2.2.1
typing_extensions==4.13.0
update-checker==0.18.0
urllib3==2.3.0
virtualenv==20.29.3
websocket-client==1.8.0
zipp==3.21.0

name: praw
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - babel==2.17.0
      - betamax==0.8.1
      - betamax-matchers==0.4.0
      - betamax-serializers==0.2.1
      - black==25.1.0
      - certifi==2025.1.31
      - cfgv==3.4.0
      - charset-normalizer==3.4.1
      - click==8.1.8
      - distlib==0.3.9
      - docutils==0.21.2
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - flake8==7.2.0
      - identify==2.6.9
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - jinja2==3.1.6
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - mock==5.2.0
      - mypy-extensions==1.0.0
      - nodeenv==1.9.1
      - packaging==24.2
      - pathspec==0.12.1
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - praw==7.0.0.dev0
      - prawcore==1.5.0
      - pre-commit==4.2.0
      - pycodestyle==2.13.0
      - pydocstyle==6.3.0
      - pyflakes==3.3.1
      - pygments==2.19.1
      - pytest==8.3.5
      - pyyaml==6.0.2
      - requests==2.32.3
      - requests-toolbelt==1.0.0
      - snowballstemmer==2.2.0
      - sphinx==7.4.7
      - sphinx-rtd-theme==3.0.2
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jquery==4.1
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - tomli==2.2.1
      - typing-extensions==4.13.0
      - update-checker==0.18.0
      - urllib3==2.3.0
      - virtualenv==20.29.3
      - websocket-client==1.8.0
      - zipp==3.21.0
prefix: /opt/conda/envs/praw

tests/unit/test_reddit.py::TestReddit::test_request__badrequest_with_no_json_body
tests/integration/models/reddit/test_comment.py::TestComment::test_attributes tests/integration/models/reddit/test_comment.py::TestComment::test_block tests/integration/models/reddit/test_comment.py::TestComment::test_clear_vote tests/integration/models/reddit/test_comment.py::TestComment::test_delete tests/integration/models/reddit/test_comment.py::TestComment::test_disable_inbox_replies tests/integration/models/reddit/test_comment.py::TestComment::test_downvote tests/integration/models/reddit/test_comment.py::TestComment::test_edit tests/integration/models/reddit/test_comment.py::TestComment::test_enable_inbox_replies tests/integration/models/reddit/test_comment.py::TestComment::test_gild__no_creddits tests/integration/models/reddit/test_comment.py::TestComment::test_invalid tests/integration/models/reddit/test_comment.py::TestComment::test_mark_read tests/integration/models/reddit/test_comment.py::TestComment::test_mark_unread tests/integration/models/reddit/test_comment.py::TestComment::test_parent__comment tests/integration/models/reddit/test_comment.py::TestComment::test_parent__chain tests/integration/models/reddit/test_comment.py::TestComment::test_parent__comment_from_forest tests/integration/models/reddit/test_comment.py::TestComment::test_parent__from_replies tests/integration/models/reddit/test_comment.py::TestComment::test_parent__submission tests/integration/models/reddit/test_comment.py::TestComment::test_refresh tests/integration/models/reddit/test_comment.py::TestComment::test_refresh__raises_exception tests/integration/models/reddit/test_comment.py::TestComment::test_refresh__twice tests/integration/models/reddit/test_comment.py::TestComment::test_refresh__deleted_comment tests/integration/models/reddit/test_comment.py::TestComment::test_refresh__removed_comment tests/integration/models/reddit/test_comment.py::TestComment::test_refresh__with_reply_sort_and_limit tests/integration/models/reddit/test_comment.py::TestComment::test_reply tests/integration/models/reddit/test_comment.py::TestComment::test_reply__none tests/integration/models/reddit/test_comment.py::TestComment::test_report tests/integration/models/reddit/test_comment.py::TestComment::test_save tests/integration/models/reddit/test_comment.py::TestComment::test_unsave tests/integration/models/reddit/test_comment.py::TestComment::test_upvote tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_approve tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_distinguish tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_distinguish__sticky tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_ignore_reports tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_lock tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_remove tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_remove_with_reason_id tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_show tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_unlock tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_add_removal_reason tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_add_removal_reason_without_id tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_add_removal_reason_without_id_or_note tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_send_removal_message tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_send_removal_message__error tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_undistinguish tests/integration/models/reddit/test_comment.py::TestCommentModeration::test_unignore_reports tests/integration/models/reddit/test_redditor.py::TestRedditor::test_block tests/integration/models/reddit/test_redditor.py::TestRedditor::test_friend tests/integration/models/reddit/test_redditor.py::TestRedditor::test_friend__with_note__no_gold tests/integration/models/reddit/test_redditor.py::TestRedditor::test_friend_info tests/integration/models/reddit/test_redditor.py::TestRedditor::test_fullname_init tests/integration/models/reddit/test_redditor.py::TestRedditor::test_gild__no_creddits tests/integration/models/reddit/test_redditor.py::TestRedditor::test_message tests/integration/models/reddit/test_redditor.py::TestRedditor::test_message_from_subreddit tests/integration/models/reddit/test_redditor.py::TestRedditor::test_moderated tests/integration/models/reddit/test_redditor.py::TestRedditor::test_multireddits tests/integration/models/reddit/test_redditor.py::TestRedditor::test_stream__comments tests/integration/models/reddit/test_redditor.py::TestRedditor::test_stream__submissions tests/integration/models/reddit/test_redditor.py::TestRedditor::test_trophies tests/integration/models/reddit/test_redditor.py::TestRedditor::test_trophies__user_not_exist tests/integration/models/reddit/test_redditor.py::TestRedditor::test_unblock tests/integration/models/reddit/test_redditor.py::TestRedditor::test_unfriend tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_comments__controversial tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_comments__hot tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_comments__new tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_comments__top tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_controversial tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_downvoted tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_downvoted__in_read_only_mode tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_downvoted__other_user tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_gilded tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_gildings tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_gildings__in_read_only_mode tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_gildings__other_user tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_hidden tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_hidden__in_read_only_mode tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_hidden__other_user tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_hot tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_new tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_saved tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_saved__in_read_only_mode tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_saved__other_user tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_submissions__controversial tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_submissions__hot tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_submissions__new tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_submissions__top tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_top tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_upvoted tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_upvoted__in_read_only_mode tests/integration/models/reddit/test_redditor.py::TestRedditorListings::test_upvoted__other_user tests/integration/models/reddit/test_submission.py::TestSubmission::test_comments tests/integration/models/reddit/test_submission.py::TestSubmission::test_clear_vote tests/integration/models/reddit/test_submission.py::TestSubmission::test_delete tests/integration/models/reddit/test_submission.py::TestSubmission::test_disable_inbox_replies tests/integration/models/reddit/test_submission.py::TestSubmission::test_downvote tests/integration/models/reddit/test_submission.py::TestSubmission::test_duplicates tests/integration/models/reddit/test_submission.py::TestSubmission::test_edit tests/integration/models/reddit/test_submission.py::TestSubmission::test_edit_invalid tests/integration/models/reddit/test_submission.py::TestSubmission::test_enable_inbox_replies tests/integration/models/reddit/test_submission.py::TestSubmission::test_gild__no_creddits tests/integration/models/reddit/test_submission.py::TestSubmission::test_gilded tests/integration/models/reddit/test_submission.py::TestSubmission::test_hide tests/integration/models/reddit/test_submission.py::TestSubmission::test_hide_multiple tests/integration/models/reddit/test_submission.py::TestSubmission::test_hide_multiple_in_batches tests/integration/models/reddit/test_submission.py::TestSubmission::test_invalid_attribute tests/integration/models/reddit/test_submission.py::TestSubmission::test_reply tests/integration/models/reddit/test_submission.py::TestSubmission::test_reply__none tests/integration/models/reddit/test_submission.py::TestSubmission::test_report tests/integration/models/reddit/test_submission.py::TestSubmission::test_save tests/integration/models/reddit/test_submission.py::TestSubmission::test_mark_visited tests/integration/models/reddit/test_submission.py::TestSubmission::test_unhide tests/integration/models/reddit/test_submission.py::TestSubmission::test_unhide_multiple tests/integration/models/reddit/test_submission.py::TestSubmission::test_unhide_multiple_in_batches tests/integration/models/reddit/test_submission.py::TestSubmission::test_unsave tests/integration/models/reddit/test_submission.py::TestSubmission::test_upvote tests/integration/models/reddit/test_submission.py::TestSubmission::test_crosspost tests/integration/models/reddit/test_submission.py::TestSubmission::test_crosspost__subreddit_object tests/integration/models/reddit/test_submission.py::TestSubmission::test_crosspost__custom_title tests/integration/models/reddit/test_submission.py::TestSubmission::test_crosspost__flair tests/integration/models/reddit/test_submission.py::TestSubmission::test_crosspost__nsfw tests/integration/models/reddit/test_submission.py::TestSubmission::test_crosspost__spoiler tests/integration/models/reddit/test_submission.py::TestSubmissionFlair::test_choices tests/integration/models/reddit/test_submission.py::TestSubmissionFlair::test_select tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_approve tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_contest_mode tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_contest_mode__disable tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_flair tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_flair_template_id tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_flair_text_only tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_flair_text_and_css_class tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_flair_all tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_flair_just_template_id tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_flair_just_css_class tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_distinguish tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_ignore_reports tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_nsfw tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_lock tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_remove tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_remove_with_reason_id tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_add_removal_reason tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_add_removal_reason_without_id tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_add_removal_reason_without_id_or_note tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_send_removal_message tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_set_original_content tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_sfw tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_spoiler tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_sticky tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_sticky__remove tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_sticky__top tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_sticky__ignore_conflicts tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_suggested_sort tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_suggested_sort__clear tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_undistinguish tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_unignore_reports tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_unlock tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_unset_original_content tests/integration/models/reddit/test_submission.py::TestSubmissionModeration::test_unspoiler tests/integration/models/test_user.py::TestUser::test_blocked tests/integration/models/test_user.py::TestUser::test_contributor_subreddits tests/integration/models/test_user.py::TestUser::test_friends tests/integration/models/test_user.py::TestUser::test_friend_exist tests/integration/models/test_user.py::TestUser::test_friend_not_exist tests/integration/models/test_user.py::TestUser::test_karma tests/integration/models/test_user.py::TestUser::test_me tests/integration/models/test_user.py::TestUser::test_me__bypass_cache tests/integration/models/test_user.py::TestUser::test_multireddits tests/integration/models/test_user.py::TestUser::test_subreddits tests/unit/test_reddit.py::TestReddit::test_comment tests/unit/test_reddit.py::TestReddit::test_info__invalid_param tests/unit/test_reddit.py::TestReddit::test_info__not_list tests/unit/test_reddit.py::TestReddit::test_live_info__valid_param tests/unit/test_reddit.py::TestReddit::test_live_info__invalid_param tests/unit/test_reddit.py::TestReddit::test_multireddit tests/unit/test_reddit.py::TestReddit::test_submission tests/unit/test_reddit.py::TestReddit::test_subreddit tests/unit/test_reddit.py::TestRedditCustomRequestor::test_requestor_class
tests/unit/test_reddit.py::TestReddit::test_check_for_updates tests/unit/test_reddit.py::TestReddit::test_check_for_updates_update_checker_missing tests/unit/test_reddit.py::TestReddit::test_context_manager tests/unit/test_reddit.py::TestReddit::test_read_only__with_authenticated_core tests/unit/test_reddit.py::TestReddit::test_read_only__with_authenticated_core__non_confidential tests/unit/test_reddit.py::TestReddit::test_read_only__with_script_authenticated_core tests/unit/test_reddit.py::TestReddit::test_read_only__without_trusted_authenticated_core tests/unit/test_reddit.py::TestReddit::test_read_only__without_untrusted_authenticated_core tests/unit/test_reddit.py::TestReddit::test_reddit__missing_required_settings tests/unit/test_reddit.py::TestReddit::test_reddit__required_settings_set_to_none tests/unit/test_reddit.py::TestReddit::test_reddit__site_name_no_section tests/unit/test_reddit.py::TestRedditCustomRequestor::test_requestor_kwargs
praw/models/reddit/mixins/__init__.py praw/reddit.py .flake8 praw/models/reddit/redditor.py CHANGES.rst praw/models/user.py
praw/models/reddit/mixins/__init__.py praw/reddit.py .flake8 praw/models/reddit/redditor.py CHANGES.rst praw/models/user.py