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