From e10b2425a004e1780586a664a0a7c65292b91de3 Mon Sep 17 00:00:00 2001
From: Moul <moul@moul.re>
Date: Tue, 12 Nov 2024 16:12:43 +0100
Subject: [PATCH] Fix API.request_url() with Py3.13 (#208)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Since Python 3.13, the IOBase finalizer now logs
any errors raised by the close() method
https://docs.python.org/3/whatsnew/3.13.html#io

According to
https://docs.python.org/3.13/library/io.html#io.IOBase.close
if the file (descriptor) is accessed after being close,
`ValueError` is raised.

The `fd` is copied with `copy.copy()`.
Both gets automatically closed once the function ends,
the second can’t close a second time the same fd,
that’s why we get this error.

Reorganise complex function: don’t use `with` since the function
is designed to allow returning the fd
Drop `copy.copy()` used previously to return the fd, causing the issue
Cleanly separate three return types and function’s returns
---
 duniterpy/api/client.py | 51 +++++++++++++++++------------------------
 1 file changed, 21 insertions(+), 30 deletions(-)

diff --git a/duniterpy/api/client.py b/duniterpy/api/client.py
index ac59069..a5344e1 100644
--- a/duniterpy/api/client.py
+++ b/duniterpy/api/client.py
@@ -13,10 +13,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-import copy
 import json
 import logging
-from http.client import HTTPResponse
 from typing import Any, Callable, Dict, Optional, Union
 from urllib import parse, request
 
@@ -252,39 +250,32 @@ class API:
                 self.connection_handler.proxy, self.connection_handler.http_scheme
             )
 
-        with request.urlopen(
+        response = request.urlopen(  # pylint: disable=consider-using-with
             duniter_request, timeout=15
-        ) as response:  # type: HTTPResponse
-            if response.status != 200:
-                content = response.read().decode("utf-8")
-                if bma_errors:
-                    try:
-                        error_data = parse_error(content)
-                        raise DuniterError(error_data)
-                    except (TypeError, jsonschema.ValidationError) as exception:
-                        raise ValueError(
-                            f"status code != 200 => {response.status} ({content})"
-                        ) from exception
-
-                raise ValueError(f"status code != 200 => {response.status} ({content})")
-
-            # get response content
-            return_response = copy.copy(response)
+        )
+        if response.status != 200:
             content = response.read().decode("utf-8")
-
-        # if schema supplied...
-        if schema is not None:
+            if bma_errors:
+                try:
+                    error_data = parse_error(content)
+                    raise DuniterError(error_data)
+                except (TypeError, jsonschema.ValidationError) as exception:
+                    raise ValueError(
+                        f"status code != 200 => {response.status} ({content})"
+                    ) from exception
+
+            raise ValueError(f"status code != 200 => {response.status} ({content})")
+
+        if rtype == RESPONSE_HTTP:
+            return response
+        # if schema supplied…
+        content = response.read().decode("utf-8")
+        if schema:
             # validate response
             parse_response(content, schema)
-
-        # return the chosen type
-        result = return_response  # type: Any
         if rtype == RESPONSE_TEXT:
-            result = content
-        elif rtype == RESPONSE_JSON:
-            result = json.loads(content)
-
-        return result
+            return content
+        return json.loads(content)
 
     def connect_ws(self, path: str) -> WSConnection:
         """
-- 
GitLab