Repository: hikari-py/hikari
Pull request #1426 (2022-12-27 13:36:39)

Remove long deprecated `async with` support for `EventStream`


Patch:
diff --git a/changes/1425.bugfix.md b/changes/1425.bugfix.md
new file mode 100644
index 00000000..69db2ca1
--- /dev/null
+++ b/changes/1425.bugfix.md
@@ -0,0 +1,1 @@
+Suppress incorrect deprecation warning regarding event loops.
diff --git a/changes/1426.breaking.md b/changes/1426.breaking.md
new file mode 100644
index 00000000..20e61051
--- /dev/null
+++ b/changes/1426.breaking.md
@@ -0,0 +1,1 @@
+Remove long deprecated `async with` support for `EventStream`.
diff --git a/changes/1434.bugfix.md b/changes/1434.bugfix.md
new file mode 100644
index 00000000..5dc7bcb6
--- /dev/null
+++ b/changes/1434.bugfix.md
@@ -0,0 +1,2 @@
+Properly close unclosed file descriptor when reading banner.
+- This only affects versions of Python >= 3.9.
diff --git a/changes/1435.bugfix.md b/changes/1435.bugfix.md
new file mode 100644
index 00000000..82c45ca6
--- /dev/null
+++ b/changes/1435.bugfix.md
@@ -0,0 +1,1 @@
+Start GC of bucket manager when creating a rest client.
diff --git a/dev-requirements/slotscheck.txt b/dev-requirements/slotscheck.txt
index 89c1338f..9fb1f6e7 100644
--- a/dev-requirements/slotscheck.txt
+++ b/dev-requirements/slotscheck.txt
@@ -1,1 +1,1 @@
-slotscheck==0.16.1
+slotscheck==0.16.2
diff --git a/hikari/impl/event_manager_base.py b/hikari/impl/event_manager_base.py
index a420959a..12cf8d42 100644
--- a/hikari/impl/event_manager_base.py
+++ b/hikari/impl/event_manager_base.py
@@ -162,29 +162,6 @@ def __init__(
         # The registered wrapping function for the weak ref to this class's _listener method.
         self._timeout = timeout
 
-    # These are only included at runtime in-order to avoid the model being typed as an asynchronous context manager.
-    if not typing.TYPE_CHECKING:
-
-        async def __aenter__(self: event_manager_.EventStreamT) -> event_manager_.EventStreamT:
-            # This is sync only.
-            warnings.warn(
-                "Using EventStream as an async context manager has been deprecated since 2.0.0.dev104. "
-                "Please use it as a synchronous context manager (e.g. with bot.stream(...)) instead.",
-                category=DeprecationWarning,
-                stacklevel=2,
-            )
-
-            self.open()
-            return self
-
-        async def __aexit__(
-            self,
-            exc_type: typing.Optional[typing.Type[BaseException]],
-            exc: typing.Optional[BaseException],
-            exc_tb: typing.Optional[types.TracebackType],
-        ) -> None:
-            self.close()
-
     def __enter__(self: _EventStreamT) -> _EventStreamT:
         self.open()
         return self
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index d296e892..e6ca8364 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -449,8 +449,10 @@ def build(
             trust_env=proxy_settings.trust_env,
         )
         _LOGGER.log(ux.TRACE, "acquired new aiohttp client session")
+        buckets = buckets_impl.RESTBucketManager(max_rate_limit)
+        buckets.start()
         return _LiveAttributes(
-            buckets=buckets_impl.RESTBucketManager(max_rate_limit),
+            buckets=buckets,
             client_session=client_session,
             closed_event=asyncio.Event(),
             global_rate_limit=rate_limits.ManualRateLimiter(),
diff --git a/hikari/internal/aio.py b/hikari/internal/aio.py
index 37d34843..32a5860e 100644
--- a/hikari/internal/aio.py
+++ b/hikari/internal/aio.py
@@ -38,6 +38,7 @@
 import inspect
 import sys
 import typing
+import warnings
 
 if typing.TYPE_CHECKING:
     import logging
@@ -208,7 +209,9 @@ def get_or_make_loop() -> asyncio.AbstractEventLoop:
     # get_event_loop will error under oddly specific cases such as if set_event_loop has been called before even
     # if it was just called with None or if it's called on a thread which isn't the main Thread.
     try:
-        loop = asyncio.get_event_loop_policy().get_event_loop()
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            loop = asyncio.get_event_loop_policy().get_event_loop()
 
         # Closed loops cannot be re-used.
         if not loop.is_closed():
diff --git a/hikari/internal/ux.py b/hikari/internal/ux.py
index cf1914c2..0e73dd72 100644
--- a/hikari/internal/ux.py
+++ b/hikari/internal/ux.py
@@ -149,7 +149,8 @@ def init_logging(
 
 def _read_banner(package: str) -> str:
     if sys.version_info >= (3, 9):
-        return importlib.resources.files(package).joinpath("banner.txt").open("r").read()
+        with importlib.resources.files(package).joinpath("banner.txt").open("r") as fp:
+            return fp.read()
     else:
         return importlib.resources.read_text(package, "banner.txt")
 


Test patch:
diff --git a/tests/hikari/impl/test_event_manager_base.py b/tests/hikari/impl/test_event_manager_base.py
index 39de4f63..ad124c02 100644
--- a/tests/hikari/impl/test_event_manager_base.py
+++ b/tests/hikari/impl/test_event_manager_base.py
@@ -94,34 +94,6 @@ def test___enter___and___exit__(
         stub_stream.open.assert_called_once_with()
         stub_stream.close.assert_called_once_with()
 
-    @pytest.mark.asyncio()
-    async def test___aenter__(self):
-        # flake8 gets annoyed if we use "with" here so here's a hacky alternative
-        stub_stream = hikari_test_helpers.mock_class_namespace(
-            event_manager_base.EventStream, open=mock.Mock(), close=mock.Mock()
-        )(mock_app, base_events.Event, timeout=None)
-
-        with warnings.catch_warnings():
-            warnings.filterwarnings("ignore", category=DeprecationWarning)
-
-            async with stub_stream:
-                stub_stream.open.assert_called_once_with()
-                stub_stream.close.assert_not_called()
-
-        stub_stream.open.assert_called_once_with()
-        stub_stream.close.assert_called_once_with()
-
-    @pytest.mark.asyncio()
-    async def test___exit__(self):
-        stub_stream = hikari_test_helpers.mock_class_namespace(event_manager_base.EventStream)(
-            mock_app, base_events.Event, timeout=None
-        )
-
-        try:
-            await stub_stream.__aexit__(None, None, None)
-        except AttributeError as exc:
-            pytest.fail(exc)
-
     @pytest.mark.asyncio()
     async def test__listener_when_filter_returns_false(self, mock_app):
         stream = event_manager_base.EventStream(mock_app, base_events.Event, timeout=None)
diff --git a/tests/hikari/impl/test_rest.py b/tests/hikari/impl/test_rest.py
index 2af8b2f9..9d914ac0 100644
--- a/tests/hikari/impl/test_rest.py
+++ b/tests/hikari/impl/test_rest.py
@@ -320,6 +320,7 @@ def test_build(self):
         assert attributes.tcp_connector is create_tcp_connector.return_value
 
         bucket_manager.assert_called_once_with(123.321)
+        bucket_manager.return_value.start.assert_called_once_with()
         create_tcp_connector.assert_called_once_with(mock_settings)
         create_client_session.assert_called_once_with(
             connector=create_tcp_connector.return_value,
diff --git a/tests/hikari/internal/test_ux.py b/tests/hikari/internal/test_ux.py
index 5596a76e..c5226702 100644
--- a/tests/hikari/internal/test_ux.py
+++ b/tests/hikari/internal/test_ux.py
@@ -146,9 +146,25 @@ def test_when_doesnt_support_color(self):
 
 class TestRedBanner:
     def test_when_above_3_9(self):
+        class MockFile:
+            context_entered = 0
+            context_exited = 0
+
+            def __enter__(self):
+                self.context_entered += 1
+                return self
+
+            def __exit__(self, exc_type, exc_val, exc_tb):
+                self.context_exited += 1
+                return None
+
+            def read(self):
+                return read
+
         class MockTraversable:
             joint_path = None
             open_mode = None
+            mock_file = None
 
             def joinpath(self, path):
                 self.joint_path = path
@@ -156,12 +172,10 @@ def joinpath(self, path):
 
             def open(self, mode):
                 self.open_mode = mode
-                return self
-
-            def read(self):
-                return read
+                return self.mock_file
 
         traversable = MockTraversable()
+        traversable.mock_file = MockFile()
         read = object()
 
         with mock.patch.object(sys, "version_info", new=(3, 9)):
@@ -171,6 +185,8 @@ def read(self):
         read_text.assert_called_once_with("hikaru")
         assert traversable.joint_path == "banner.txt"
         assert traversable.open_mode == "r"
+        assert traversable.mock_file.context_entered == 1
+        assert traversable.mock_file.context_exited == 1
 
     def test_when_bellow_3_9(self):
         with mock.patch.object(sys, "version_info", new=(2, 7)):