)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"36bf8b2c308cf2123ab536a152655ca3a74369b0","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"e2c69ad7_bc1ed063","updated":"2026-09-04 11:07:19.000000000","message":"Good idea. Some small requests.","commit_id":"a9a452afb04396da7c3720317932e0c24a67f31d"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"b0f40e9fce0c4c9d5c0d080c0c9b794eb81e3d74","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"7cf6edae_25bb368d","updated":"2026-09-04 12:50:17.000000000","message":"Thanks 🙏","commit_id":"4741636c9075a7eefe5f13c2fa0796b5fd8508a5"}],"keystoneauth1/session.py":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"36bf8b2c308cf2123ab536a152655ca3a74369b0","unresolved":true,"context_lines":[{"line_number":465,"context_line":"        left untouched: ownership of that object belongs to the caller."},{"line_number":466,"context_line":"        \"\"\""},{"line_number":467,"context_line":"        if self._session is not None:"},{"line_number":468,"context_line":"            self._session.close()"},{"line_number":469,"context_line":"            self._session \u003d None"},{"line_number":470,"context_line":""},{"line_number":471,"context_line":"    def __del__(self) -\u003e None:"}],"source_content_type":"text/x-python","patch_set":1,"id":"281e56de_c626ef7c","line":468,"updated":"2026-09-04 11:07:19.000000000","message":"Can we have the error handling here, rather than in `__del__`? By not having it here we make this everyone\u0027s problem. If we want to do that, we should document it in the docstring, ideally with `:raises:`. I think we are liable to raise:\n\n- socket errors (`OSError` since [`socket.error` is deprecated](https://docs.python.org/3/library/socket.html#socket.error))\n- ssl errors (`ssl.SSLError`, which is [a subtype of `OSError`](https://docs.python.org/3/library/ssl.html#ssl.SSLError))\n- misc requests errors? (`requests.exceptions.RequestException`)\n\nIf we make it more specific we probably want to keep the generic try-except in `__del__` since that must succeed.","commit_id":"a9a452afb04396da7c3720317932e0c24a67f31d"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"3d9f08face1be958e38ce7ab7d9db6050dc63d08","unresolved":false,"context_lines":[{"line_number":465,"context_line":"        left untouched: ownership of that object belongs to the caller."},{"line_number":466,"context_line":"        \"\"\""},{"line_number":467,"context_line":"        if self._session is not None:"},{"line_number":468,"context_line":"            self._session.close()"},{"line_number":469,"context_line":"            self._session \u003d None"},{"line_number":470,"context_line":""},{"line_number":471,"context_line":"    def __del__(self) -\u003e None:"}],"source_content_type":"text/x-python","patch_set":1,"id":"05442570_54aa2baf","line":468,"in_reply_to":"281e56de_c626ef7c","updated":"2026-09-04 12:08:54.000000000","message":"Done","commit_id":"a9a452afb04396da7c3720317932e0c24a67f31d"}],"keystoneauth1/tests/unit/test_session.py":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"36bf8b2c308cf2123ab536a152655ca3a74369b0","unresolved":true,"context_lines":[{"line_number":2417,"context_line":"        session \u003d client_session.Session(tls_min_version\u003dNone)"},{"line_number":2418,"context_line":"        self.assertIsNone(session.tls_min_version)"},{"line_number":2419,"context_line":""},{"line_number":2420,"context_line":""},{"line_number":2421,"context_line":"class SessionCloseTests(utils.TestCase):"},{"line_number":2422,"context_line":"    def test_close_releases_owned_session(self):"},{"line_number":2423,"context_line":"        session \u003d client_session.Session()"},{"line_number":2424,"context_line":"        self.assertIsNotNone(session._session)"},{"line_number":2425,"context_line":"        with mock.patch.object("},{"line_number":2426,"context_line":"            session._session, \u0027close\u0027, wraps\u003dsession._session.close"},{"line_number":2427,"context_line":"        ) as close_mock:"},{"line_number":2428,"context_line":"            session.close()"},{"line_number":2429,"context_line":"        close_mock.assert_called_once()"},{"line_number":2430,"context_line":"        self.assertIsNone(session._session)"},{"line_number":2431,"context_line":""},{"line_number":2432,"context_line":"    def test_close_leaves_external_session(self):"},{"line_number":2433,"context_line":"        external \u003d requests.Session()"},{"line_number":2434,"context_line":"        session \u003d client_session.Session(session\u003dexternal)"},{"line_number":2435,"context_line":"        self.assertIsNone(session._session)"},{"line_number":2436,"context_line":"        with mock.patch.object(external, \u0027close\u0027) as close_mock:"},{"line_number":2437,"context_line":"            session.close()"},{"line_number":2438,"context_line":"        close_mock.assert_not_called()"},{"line_number":2439,"context_line":""},{"line_number":2440,"context_line":"    def test_close_is_idempotent(self):"},{"line_number":2441,"context_line":"        session \u003d client_session.Session()"},{"line_number":2442,"context_line":"        with mock.patch.object("},{"line_number":2443,"context_line":"            session._session, \u0027close\u0027, wraps\u003dsession._session.close"},{"line_number":2444,"context_line":"        ) as close_mock:"},{"line_number":2445,"context_line":"            session.close()"},{"line_number":2446,"context_line":"            session.close()"},{"line_number":2447,"context_line":"        close_mock.assert_called_once()"},{"line_number":2448,"context_line":"        self.assertIsNone(session._session)"},{"line_number":2449,"context_line":""},{"line_number":2450,"context_line":"    def test_close_keeps_session_usable(self):"},{"line_number":2451,"context_line":"        session \u003d client_session.Session()"},{"line_number":2452,"context_line":"        session.close()"},{"line_number":2453,"context_line":"        self.assertIsNotNone(session.session)"},{"line_number":2454,"context_line":""},{"line_number":2455,"context_line":"    def test_del_swallows_errors(self):"},{"line_number":2456,"context_line":"        session \u003d client_session.Session()"},{"line_number":2457,"context_line":"        session._session \u003d mock.Mock()"},{"line_number":2458,"context_line":"        session._session.close.side_effect \u003d RuntimeError(\u0027boom\u0027)"},{"line_number":2459,"context_line":"        self.assertIsNone(session.__del__())"}],"source_content_type":"text/x-python","patch_set":1,"id":"0a5dd2e7_bcbac64f","line":2459,"range":{"start_line":2420,"start_character":0,"end_line":2459,"end_character":44},"updated":"2026-09-04 11:07:19.000000000","message":"Can we fold these in `SessionTests` above? I\u0027ll do a follow-up to fold `SessionTLSTests` in also, since that doesn\u0027t need to be a separate change IMO","commit_id":"a9a452afb04396da7c3720317932e0c24a67f31d"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"3d9f08face1be958e38ce7ab7d9db6050dc63d08","unresolved":true,"context_lines":[{"line_number":2417,"context_line":"        session \u003d client_session.Session(tls_min_version\u003dNone)"},{"line_number":2418,"context_line":"        self.assertIsNone(session.tls_min_version)"},{"line_number":2419,"context_line":""},{"line_number":2420,"context_line":""},{"line_number":2421,"context_line":"class SessionCloseTests(utils.TestCase):"},{"line_number":2422,"context_line":"    def test_close_releases_owned_session(self):"},{"line_number":2423,"context_line":"        session \u003d client_session.Session()"},{"line_number":2424,"context_line":"        self.assertIsNotNone(session._session)"},{"line_number":2425,"context_line":"        with mock.patch.object("},{"line_number":2426,"context_line":"            session._session, \u0027close\u0027, wraps\u003dsession._session.close"},{"line_number":2427,"context_line":"        ) as close_mock:"},{"line_number":2428,"context_line":"            session.close()"},{"line_number":2429,"context_line":"        close_mock.assert_called_once()"},{"line_number":2430,"context_line":"        self.assertIsNone(session._session)"},{"line_number":2431,"context_line":""},{"line_number":2432,"context_line":"    def test_close_leaves_external_session(self):"},{"line_number":2433,"context_line":"        external \u003d requests.Session()"},{"line_number":2434,"context_line":"        session \u003d client_session.Session(session\u003dexternal)"},{"line_number":2435,"context_line":"        self.assertIsNone(session._session)"},{"line_number":2436,"context_line":"        with mock.patch.object(external, \u0027close\u0027) as close_mock:"},{"line_number":2437,"context_line":"            session.close()"},{"line_number":2438,"context_line":"        close_mock.assert_not_called()"},{"line_number":2439,"context_line":""},{"line_number":2440,"context_line":"    def test_close_is_idempotent(self):"},{"line_number":2441,"context_line":"        session \u003d client_session.Session()"},{"line_number":2442,"context_line":"        with mock.patch.object("},{"line_number":2443,"context_line":"            session._session, \u0027close\u0027, wraps\u003dsession._session.close"},{"line_number":2444,"context_line":"        ) as close_mock:"},{"line_number":2445,"context_line":"            session.close()"},{"line_number":2446,"context_line":"            session.close()"},{"line_number":2447,"context_line":"        close_mock.assert_called_once()"},{"line_number":2448,"context_line":"        self.assertIsNone(session._session)"},{"line_number":2449,"context_line":""},{"line_number":2450,"context_line":"    def test_close_keeps_session_usable(self):"},{"line_number":2451,"context_line":"        session \u003d client_session.Session()"},{"line_number":2452,"context_line":"        session.close()"},{"line_number":2453,"context_line":"        self.assertIsNotNone(session.session)"},{"line_number":2454,"context_line":""},{"line_number":2455,"context_line":"    def test_del_swallows_errors(self):"},{"line_number":2456,"context_line":"        session \u003d client_session.Session()"},{"line_number":2457,"context_line":"        session._session \u003d mock.Mock()"},{"line_number":2458,"context_line":"        session._session.close.side_effect \u003d RuntimeError(\u0027boom\u0027)"},{"line_number":2459,"context_line":"        self.assertIsNone(session.__del__())"}],"source_content_type":"text/x-python","patch_set":1,"id":"d58edd40_2292336a","line":2459,"range":{"start_line":2420,"start_character":0,"end_line":2459,"end_character":44},"in_reply_to":"0a5dd2e7_bcbac64f","updated":"2026-09-04 12:08:54.000000000","message":"I am actually more fan of separating tests as much as possible, but is also fine for me","commit_id":"a9a452afb04396da7c3720317932e0c24a67f31d"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"b0f40e9fce0c4c9d5c0d080c0c9b794eb81e3d74","unresolved":false,"context_lines":[{"line_number":2417,"context_line":"        session \u003d client_session.Session(tls_min_version\u003dNone)"},{"line_number":2418,"context_line":"        self.assertIsNone(session.tls_min_version)"},{"line_number":2419,"context_line":""},{"line_number":2420,"context_line":""},{"line_number":2421,"context_line":"class SessionCloseTests(utils.TestCase):"},{"line_number":2422,"context_line":"    def test_close_releases_owned_session(self):"},{"line_number":2423,"context_line":"        session \u003d client_session.Session()"},{"line_number":2424,"context_line":"        self.assertIsNotNone(session._session)"},{"line_number":2425,"context_line":"        with mock.patch.object("},{"line_number":2426,"context_line":"            session._session, \u0027close\u0027, wraps\u003dsession._session.close"},{"line_number":2427,"context_line":"        ) as close_mock:"},{"line_number":2428,"context_line":"            session.close()"},{"line_number":2429,"context_line":"        close_mock.assert_called_once()"},{"line_number":2430,"context_line":"        self.assertIsNone(session._session)"},{"line_number":2431,"context_line":""},{"line_number":2432,"context_line":"    def test_close_leaves_external_session(self):"},{"line_number":2433,"context_line":"        external \u003d requests.Session()"},{"line_number":2434,"context_line":"        session \u003d client_session.Session(session\u003dexternal)"},{"line_number":2435,"context_line":"        self.assertIsNone(session._session)"},{"line_number":2436,"context_line":"        with mock.patch.object(external, \u0027close\u0027) as close_mock:"},{"line_number":2437,"context_line":"            session.close()"},{"line_number":2438,"context_line":"        close_mock.assert_not_called()"},{"line_number":2439,"context_line":""},{"line_number":2440,"context_line":"    def test_close_is_idempotent(self):"},{"line_number":2441,"context_line":"        session \u003d client_session.Session()"},{"line_number":2442,"context_line":"        with mock.patch.object("},{"line_number":2443,"context_line":"            session._session, \u0027close\u0027, wraps\u003dsession._session.close"},{"line_number":2444,"context_line":"        ) as close_mock:"},{"line_number":2445,"context_line":"            session.close()"},{"line_number":2446,"context_line":"            session.close()"},{"line_number":2447,"context_line":"        close_mock.assert_called_once()"},{"line_number":2448,"context_line":"        self.assertIsNone(session._session)"},{"line_number":2449,"context_line":""},{"line_number":2450,"context_line":"    def test_close_keeps_session_usable(self):"},{"line_number":2451,"context_line":"        session \u003d client_session.Session()"},{"line_number":2452,"context_line":"        session.close()"},{"line_number":2453,"context_line":"        self.assertIsNotNone(session.session)"},{"line_number":2454,"context_line":""},{"line_number":2455,"context_line":"    def test_del_swallows_errors(self):"},{"line_number":2456,"context_line":"        session \u003d client_session.Session()"},{"line_number":2457,"context_line":"        session._session \u003d mock.Mock()"},{"line_number":2458,"context_line":"        session._session.close.side_effect \u003d RuntimeError(\u0027boom\u0027)"},{"line_number":2459,"context_line":"        self.assertIsNone(session.__del__())"}],"source_content_type":"text/x-python","patch_set":1,"id":"a8ba0470_af039b3d","line":2459,"range":{"start_line":2420,"start_character":0,"end_line":2459,"end_character":44},"in_reply_to":"d58edd40_2292336a","updated":"2026-09-04 12:50:17.000000000","message":"Acknowledged","commit_id":"a9a452afb04396da7c3720317932e0c24a67f31d"}],"releasenotes/notes/add-session-close-e25f5891b57846e7.yaml":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"36bf8b2c308cf2123ab536a152655ca3a74369b0","unresolved":true,"context_lines":[{"line_number":9,"context_line":"    constructor argument is left untouched, as its ownership belongs to"},{"line_number":10,"context_line":"    the caller. ``close()`` is idempotent and non-fatal: the session"},{"line_number":11,"context_line":"    remains usable after being closed, establishing new connections as"},{"line_number":12,"context_line":"    needed, and no re-authentication is required. ``Session.__del__``"},{"line_number":13,"context_line":"    now delegates to ``close()``."}],"source_content_type":"text/x-yaml","patch_set":1,"id":"62c1da47_169b530d","line":13,"range":{"start_line":12,"start_character":50,"end_line":13,"end_character":33},"updated":"2026-09-04 11:07:19.000000000","message":"This is an implementation detail, no? Can we drop it?","commit_id":"a9a452afb04396da7c3720317932e0c24a67f31d"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"3d9f08face1be958e38ce7ab7d9db6050dc63d08","unresolved":false,"context_lines":[{"line_number":9,"context_line":"    constructor argument is left untouched, as its ownership belongs to"},{"line_number":10,"context_line":"    the caller. ``close()`` is idempotent and non-fatal: the session"},{"line_number":11,"context_line":"    remains usable after being closed, establishing new connections as"},{"line_number":12,"context_line":"    needed, and no re-authentication is required. ``Session.__del__``"},{"line_number":13,"context_line":"    now delegates to ``close()``."}],"source_content_type":"text/x-yaml","patch_set":1,"id":"cb654e2f_e3d282d0","line":13,"range":{"start_line":12,"start_character":50,"end_line":13,"end_character":33},"in_reply_to":"62c1da47_169b530d","updated":"2026-09-04 12:08:54.000000000","message":"Done","commit_id":"a9a452afb04396da7c3720317932e0c24a67f31d"}]}
