From 7767ca7663b34c35b7ac788c5d33155c57a8db35 Mon Sep 17 00:00:00 2001 From: Marc Fischer Date: Mon, 15 Jun 2026 17:45:46 +0200 Subject: [PATCH] Fix Python 3.12 compatibility: event loop, JSON encoder, and delete serializer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs surfaced when running against Python 3.12 with the redesigned exception hierarchy (CTERAException is now a plain Exception subclass, no longer dict-like): 1. clients.py – execute(): asyncio.get_event_loop() raises RuntimeError in Python 3.12 when no event loop is set for the current thread. The module already creates a dedicated event_loop at import time; use it directly instead of going through asyncio.get_event_loop(). 2. serializers.py – Encoder.default(): called o.get('__dict__', None), treating the object as a dict. SDK Object instances and Exception subclasses are not dict-like in the current codebase. Replace with getattr(o, '__dict__', None) and an explicit Exception branch that returns str(o), so CTERAException subclasses (e.g. AuthenticationError) serialize to their message string instead of raising TypeError or AttributeError. 3. clients.py – Client.delete(): unconditionally called data_serializer(data) even when data_serializer is None (the default). Callers such as XML.delete() intentionally omit the serializer; guard the call with a None check. --- cterasdk/clients/clients.py | 8 +++----- cterasdk/convert/serializers.py | 4 +++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cterasdk/clients/clients.py b/cterasdk/clients/clients.py index 3df1e8ce..de31961f 100644 --- a/cterasdk/clients/clients.py +++ b/cterasdk/clients/clients.py @@ -237,7 +237,7 @@ def multipart(self, path, form, *, on_response=None, on_error=None, **kwargs): @decorators.authenticated def delete(self, path, data=None, *, data_serializer=None, on_response=None, on_error=None, **kwargs): - request = async_requests.DeleteRequest(self._builder(path), data=data_serializer(data), **kwargs) + request = async_requests.DeleteRequest(self._builder(path), data=data_serializer(data) if data_serializer else None, **kwargs) return self.request(request, on_response=on_response, on_error=on_error) def _request(self, request, *, on_response=None, on_error=None): @@ -407,10 +407,8 @@ def login(self): def execute(target, *args, **kwargs): - loop = asyncio.get_event_loop() - - if not loop.is_running(): - return loop.run_until_complete(target(*args, **kwargs)) + if not event_loop.is_running(): + return event_loop.run_until_complete(target(*args, **kwargs)) return run_threadsafe(event_loop, target, *args, **kwargs) diff --git a/cterasdk/convert/serializers.py b/cterasdk/convert/serializers.py index f15887d9..6d6f041d 100644 --- a/cterasdk/convert/serializers.py +++ b/cterasdk/convert/serializers.py @@ -39,7 +39,9 @@ def _to_protected_dict(o): class Encoder(json.JSONEncoder): def default(self, o): - d = o.get('__dict__', None) + if isinstance(o, Exception): + return str(o) + d = getattr(o, '__dict__', None) if d: if '_classname' in d: d['$class'] = d.pop('_classname')