Repository: praw-dev/praw
Pull request #1628 (2021-01-29 20:41:04)

Add check to see if PRAW is being ran in an asynchronous environment
## Feature Summary and Justification

This adds a check to see if PRAW is being ran in an asynchronous environment (more specifically, if there is a running asyncio event loop) and advise users to use Async PRAW if there is one running.

This adds a check that is ran on the initialization of `praw.Reddit` and every time `Reddit.request()` is called. It can be suppressed by passing `check_for_async=False` anywhere config options are accepted.

LilSpazJoekp: In the first revision, I had on just the initialization of `praw.Reddit` since that is when the requestor is initialized. But then I got thinking about it and thought it would be better have it on `Reddit.request` as well since that's where the blocking requests are made and because `praw.Reddit` can be initialized outside of the asynchronous environment.
bboe: Perhaps we should only do it only request then since having it in both seems a little redundant.
LilSpazJoekp: Sounds good! I'll get that changed later tonight.

Patch:
diff --git a/docs/getting_started/multiple_instances.rst b/docs/getting_started/multiple_instances.rst
index c25ace6e..6b0d8401 100644
--- a/docs/getting_started/multiple_instances.rst
+++ b/docs/getting_started/multiple_instances.rst
@@ -22,6 +22,18 @@ is strongly recommended to use `Async PRAW <https://asyncpraw.readthedocs.io/>`_
 the official asynchronous version of PRAW and its usage is similar and has the same
 features as PRAW.
 
+.. note::
+
+    By default, PRAW will check to see if it is in an asynchronous environment at the
+    initialization of :class:`.Reddit` and every time a network request is made. To
+    disable this check you can do:
+
+    .. code-block:: python
+
+        import praw
+
+        reddit = praw.Reddit(..., check_for_async=False)
+
 Multiple Programs
 -----------------
 
diff --git a/praw/config.py b/praw/config.py
index 8660e1f2..383a762c 100644
--- a/praw/config.py
+++ b/praw/config.py
@@ -107,6 +107,9 @@ class Config:
 
     def _initialize_attributes(self):
         self._short_url = self._fetch_default("short_url") or self.CONFIG_NOT_SET
+        self.check_for_async = self._config_boolean(
+            self._fetch_default("check_for_async", True)
+        )
         self.check_for_updates = self._config_boolean(
             self._fetch_or_not_set("check_for_updates")
         )
diff --git a/praw/reddit.py b/praw/reddit.py
index e2fb3d83..bbddfa5c 100644
--- a/praw/reddit.py
+++ b/praw/reddit.py
@@ -1,4 +1,5 @@
 """Provide the Reddit class."""
+import asyncio
 import configparser
 import os
 import re
@@ -136,7 +137,7 @@ class Reddit:
         config_interpolation: Optional[str] = None,
         requestor_class: Optional[Type[Requestor]] = None,
         requestor_kwargs: Dict[str, Any] = None,
-        **config_settings: str,
+        **config_settings: Union[str, bool],
     ):  # noqa: D207, D301
         """Initialize a Reddit instance.
 
@@ -348,6 +349,21 @@ class Reddit:
 
         """
 
+    def _check_for_async(self):
+        if self.config.check_for_async:
+            try:
+                asyncio.get_running_loop()
+            except RuntimeError:
+                pass
+            else:
+                logger.warning(
+                    "It appears that you are using PRAW in an asynchronous"
+                    " environment.\nIt is strongly recommended to use Async PRAW:"
+                    " https://asyncpraw.readthedocs.io/en/latest/.\nSee"
+                    " https://praw.readthedocs.io/en/latest/getting_started/multiple_instances.html#discord-bots-and-asynchronous-environments"
+                    " for more info.\n",
+                )
+
     def _check_for_update(self):
         if UPDATE_CHECKER_MISSING:
             return
@@ -757,6 +773,8 @@ class Reddit:
             provided, ``data`` should not be.
 
         """
+        if self.config.check_for_async:
+            self._check_for_async()
         if data and json:
             raise ClientException("At most one of `data` and `json` is supported.")
         try:


Test patch:
diff --git a/tests/unit/test_reddit.py b/tests/unit/test_reddit.py
index b7833fe4..1731b221 100644
--- a/tests/unit/test_reddit.py
+++ b/tests/unit/test_reddit.py
@@ -1,8 +1,10 @@
+import asyncio
 import configparser
 import types
 from unittest import mock
 
 import pytest
+import requests
 from prawcore import Requestor
 from prawcore.exceptions import BadRequest
 
@@ -18,6 +20,39 @@ class TestReddit(UnitTest):
         x: "dummy" for x in ["client_id", "client_secret", "user_agent"]
     }
 
+    @staticmethod
+    async def check_async(reddit):
+        reddit.request("GET", "path")
+
+    @staticmethod
+    def patch_request(*args, **kwargs):
+        """Patch requests to return mock data on specific url."""
+        response = requests.Response()
+        response._content = '{"name":"username"}'.encode("utf-8")
+        response.status_code = 200
+        return response
+
+    def test_check_for_async(self, caplog):
+        reddit = Reddit(**self.REQUIRED_DUMMY_SETTINGS)
+        reddit._core.request = self.patch_request
+        loop = asyncio.get_event_loop()
+        loop.run_until_complete(self.check_async(reddit))
+        log_record = caplog.records[0]
+        assert log_record.levelname == "WARNING"
+        assert log_record.message == (
+            "It appears that you are using PRAW in an asynchronous environment.\nIt is"
+            " strongly recommended to use Async PRAW: https://asyncpraw.readthedocs.io/en/latest/."
+            "\nSee https://praw.readthedocs.io/en/latest/getting_started/multiple_instances.html#discord-bots-and-asynchronous-environments"
+            " for more info.\n"
+        )
+
+    def test_check_for_async__disabled(self, caplog):
+        reddit = Reddit(**self.REQUIRED_DUMMY_SETTINGS, check_for_async=False)
+        reddit._core.request = self.patch_request
+        loop = asyncio.get_event_loop()
+        loop.run_until_complete(self.check_async(reddit))
+        assert caplog.records == []
+
     @mock.patch("praw.reddit.update_check", create=True)
     @mock.patch("praw.reddit.UPDATE_CHECKER_MISSING", False)
     @mock.patch("praw.reddit.Reddit.update_checked", False)