Repository: scrapy/scrapy

Issue:
DeprecationWarning: There is no current event loop
```
/home/runner/work/scrapy/scrapy/scrapy/utils/reactor.py:70: DeprecationWarning: There is no current event loop
  event_loop = asyncio.get_event_loop()
tests/test_downloadermiddleware.py: 2 warnings
tests/test_utils_asyncgen.py: 2 warnings
tests/test_utils_defer.py: 4 warnings
tests/test_utils_python.py: 2 warnings
tests/test_utils_signal.py: 2 warnings
  /home/runner/work/scrapy/scrapy/scrapy/utils/defer.py:272: DeprecationWarning: There is no current event loop
    return Deferred.fromFuture(asyncio.ensure_future(o))
tests/test_utils_asyncio.py::AsyncioTest::test_install_asyncio_reactor
  /home/runner/work/scrapy/scrapy/scrapy/utils/reactor.py:70: DeprecationWarning: There is no current event loop
    event_loop = asyncio.get_event_loop()
```

Not sure yet what does this imply and when can this break (this is from tests on 3.10 and 3.11).
DeprecationWarning: There is no current event loop
```
/home/runner/work/scrapy/scrapy/scrapy/utils/reactor.py:70: DeprecationWarning: There is no current event loop
  event_loop = asyncio.get_event_loop()
tests/test_downloadermiddleware.py: 2 warnings
tests/test_utils_asyncgen.py: 2 warnings
tests/test_utils_defer.py: 4 warnings
tests/test_utils_python.py: 2 warnings
tests/test_utils_signal.py: 2 warnings
  /home/runner/work/scrapy/scrapy/scrapy/utils/defer.py:272: DeprecationWarning: There is no current event loop
    return Deferred.fromFuture(asyncio.ensure_future(o))
tests/test_utils_asyncio.py::AsyncioTest::test_install_asyncio_reactor
  /home/runner/work/scrapy/scrapy/scrapy/utils/reactor.py:70: DeprecationWarning: There is no current event loop
    event_loop = asyncio.get_event_loop()
```

Not sure yet what does this imply and when can this break (this is from tests on 3.10 and 3.11).


Discussion:
> asyncio.get_event_loop() now emits a deprecation warning if there is no running event loop. In the future it will be an alias of get_running_loop(). asyncio functions which implicitly create Future or Task objects now emit a deprecation warning if there is no running event loop and no explicit loop argument is passed
can I add an exception handling to this then? @wRAR 
@shashwata27 what kind of exception handling?
Maybe we can just switch to [`get_running_loop()`](https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.get_running_loop), it has been around since Python 3.7, which is our minimum Python version now.

~~I wonder, though, if there is any case where we can get a `RuntimeError` when using it.~~ Looking at the deprecation warnings, they explicitly say that there is no running event loop, which would trigger a `RuntimeError` with `get_running_loop()`.
For:

https://github.com/scrapy/scrapy/blob/82f25bc44acd2599115fa339967b436189eec9c1/scrapy/utils/reactor.py#L70

A try-except like this may make sense:

```python
try:
    event_loop = asyncio.get_running_loop()
except RuntimeError:
    event_loop = asyncio.new_event_loop()
```
This seems problematic, though:

https://github.com/scrapy/scrapy/blob/52d93490f57d4b2284e3d95efa5c622ab8ad16cb/scrapy/utils/defer.py#L323 
In the latter, maybe we could start by switching to `get_running_loop()`, and see what breaks. It should really only be called if there is a running loop, as far as I can tell. If not, maybe we are doing something wrong.
> asyncio.get_event_loop() now emits a deprecation warning if there is no running event loop. In the future it will be an alias of get_running_loop(). asyncio functions which implicitly create Future or Task objects now emit a deprecation warning if there is no running event loop and no explicit loop argument is passed
can I add an exception handling to this then? @wRAR 
@shashwata27 what kind of exception handling?
Maybe we can just switch to [`get_running_loop()`](https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.get_running_loop), it has been around since Python 3.7, which is our minimum Python version now.

~~I wonder, though, if there is any case where we can get a `RuntimeError` when using it.~~ Looking at the deprecation warnings, they explicitly say that there is no running event loop, which would trigger a `RuntimeError` with `get_running_loop()`.
For:

https://github.com/scrapy/scrapy/blob/82f25bc44acd2599115fa339967b436189eec9c1/scrapy/utils/reactor.py#L70

A try-except like this may make sense:

```python
try:
    event_loop = asyncio.get_running_loop()
except RuntimeError:
    event_loop = asyncio.new_event_loop()
```
This seems problematic, though:

https://github.com/scrapy/scrapy/blob/52d93490f57d4b2284e3d95efa5c622ab8ad16cb/scrapy/utils/defer.py#L323 
In the latter, maybe we could start by switching to `get_running_loop()`, and see what breaks. It should really only be called if there is a running loop, as far as I can tell. If not, maybe we are doing something wrong.

Resolving patch:
diff --git a/scrapy/utils/defer.py b/scrapy/utils/defer.py
--- a/scrapy/utils/defer.py
+++ b/scrapy/utils/defer.py
@@ -26,7 +26,7 @@
 from twisted.python.failure import Failure
 
 from scrapy.exceptions import IgnoreRequest
-from scrapy.utils.reactor import is_asyncio_reactor_installed
+from scrapy.utils.reactor import is_asyncio_reactor_installed, get_asyncio_event_loop_policy
 
 
 def defer_fail(_failure: Failure) -> Deferred:
@@ -269,7 +269,8 @@ def deferred_from_coro(o) -> Any:
             return ensureDeferred(o)
         else:
             # wrapping the coroutine into a Future and then into a Deferred, this requires AsyncioSelectorReactor
-            return Deferred.fromFuture(asyncio.ensure_future(o))
+            event_loop = get_asyncio_event_loop_policy().get_event_loop()
+            return Deferred.fromFuture(asyncio.ensure_future(o, loop=event_loop))
     return o
 
 
@@ -320,7 +321,8 @@ async def parse(self, response):
                 d = treq.get('https://example.com/additional')
                 additional_response = await deferred_to_future(d)
     """
-    return d.asFuture(asyncio.get_event_loop())
+    policy = get_asyncio_event_loop_policy()
+    return d.asFuture(policy.get_event_loop())
 
 
 def maybe_deferred_to_future(d: Deferred) -> Union[Deferred, Future]:
diff --git a/scrapy/utils/reactor.py b/scrapy/utils/reactor.py
--- a/scrapy/utils/reactor.py
+++ b/scrapy/utils/reactor.py
@@ -51,6 +51,19 @@ def __call__(self):
         return self._func(*self._a, **self._kw)
 
 
+def get_asyncio_event_loop_policy():
+    policy = asyncio.get_event_loop_policy()
+    if (
+        sys.version_info >= (3, 8)
+        and sys.platform == "win32"
+        and not isinstance(policy, asyncio.WindowsSelectorEventLoopPolicy)
+    ):
+        policy = asyncio.WindowsSelectorEventLoopPolicy()
+        asyncio.set_event_loop_policy(policy)
+
+    return policy
+
+
 def install_reactor(reactor_path, event_loop_path=None):
     """Installs the :mod:`~twisted.internet.reactor` with the specified
     import path. Also installs the asyncio event loop with the specified import
@@ -58,16 +71,14 @@ def install_reactor(reactor_path, event_loop_path=None):
     reactor_class = load_object(reactor_path)
     if reactor_class is asyncioreactor.AsyncioSelectorReactor:
         with suppress(error.ReactorAlreadyInstalledError):
-            if sys.version_info >= (3, 8) and sys.platform == "win32":
-                policy = asyncio.get_event_loop_policy()
-                if not isinstance(policy, asyncio.WindowsSelectorEventLoopPolicy):
-                    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
+            policy = get_asyncio_event_loop_policy()
             if event_loop_path is not None:
                 event_loop_class = load_object(event_loop_path)
                 event_loop = event_loop_class()
                 asyncio.set_event_loop(event_loop)
             else:
-                event_loop = asyncio.get_event_loop()
+                event_loop = policy.get_event_loop()
+
             asyncioreactor.install(eventloop=event_loop)
     else:
         *module, _ = reactor_path.split(".")


Test patch:
diff --git a/tests/test_utils_asyncio.py b/tests/test_utils_asyncio.py
--- a/tests/test_utils_asyncio.py
+++ b/tests/test_utils_asyncio.py
@@ -1,3 +1,4 @@
+import warnings
 from unittest import TestCase
 
 from pytest import mark
@@ -13,5 +14,6 @@ def test_is_asyncio_reactor_installed(self):
         self.assertEqual(is_asyncio_reactor_installed(), self.reactor_pytest == 'asyncio')
 
     def test_install_asyncio_reactor(self):
-        # this should do nothing
-        install_reactor("twisted.internet.asyncioreactor.AsyncioSelectorReactor")
+        with warnings.catch_warnings(record=True) as w:
+            install_reactor("twisted.internet.asyncioreactor.AsyncioSelectorReactor")
+            self.assertEqual(len(w), 0)