Skip to content

Fix Python 3.12 compatibility: event loop,#362

Open
marcfischer wants to merge 1 commit into
ctera:masterfrom
marcfischer:fix/python312-compat-serializer-client
Open

Fix Python 3.12 compatibility: event loop,#362
marcfischer wants to merge 1 commit into
ctera:masterfrom
marcfischer:fix/python312-compat-serializer-client

Conversation

@marcfischer

Copy link
Copy Markdown

Discovered via: Using the ctera.ctera Ansible collection (v2.3.9)
against a fresh SDK install on Python 3.12+. The collection is no
longer actively maintained and targets an older SDK version where
CTERAException was dict-like — these bugs are therefore unlikely to
surface through the SDK's own test suite.

Reproduction: Install the Ansible collection, run any portal module
(e.g. ctera_portal_storage_node) on Python 3.12, and observe the
errors in sequence:

  1. RuntimeError: There is no current event loop in thread
    'MainThread'
  2. AttributeError: 'AuthenticationError' object has no attribute
    'get'
  3. TypeError: Object of type AuthenticationError is not JSON
    serializable
  4. TypeError: 'NoneType' object is not callable (on delete
    operations)

JSON encoder, and delete serializer

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.

  JSON encoder, and delete serializer

  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.
@marcfischer

marcfischer commented Jun 18, 2026

Copy link
Copy Markdown
Author

without this pr it is not possible to remove a StorageNode:

This fails without this PR:

portal = GlobalAdmin('192.168.100.91')
portal.login('admin', 'password1!')
portal.portals.browse_global_admin()
portal.buckets.delete('MainStorage')

Error:

Traceback (most recent call last):
  File "/data/test_storage.py", line 12, in <module>
    portal.buckets.delete('MainStorage')
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/cterasdk/core/buckets.py", line 120, in delete
    response = self._core.api.delete(f'/locations/{name}')
  File "/usr/local/lib/python3.13/site-packages/cterasdk/clients/clients.py", line 329, in delete
    response = super().delete(path, data, on_error=XMLHandler(), **kwargs)
  File "/usr/local/lib/python3.13/site-packages/cterasdk/clients/decorators.py", line 19, in authenticate_then_execute
    return execute_request(self, *args, **kwargs)
  File "/usr/local/lib/python3.13/site-packages/cterasdk/clients/clients.py", line 240, in delete
    request = async_requests.DeleteRequest(self._builder(path), data=data_serializer(data), **kwargs)
                                                                     ~~~~~~~~~~~~~~~^^^^^^
TypeError: 'NoneType' object is not callable
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7f3d44036e40>
Unclosed connector
connections: ['deque([(<aiohttp.client_proto.ResponseHandler object at 0x7f3d4458d780>, 1168792.570529471)])']
connector: <aiohttp.connector.TCPConnector object at 0x7f3d44036900>
ctera-initial-config-d9cc8789c-d8blw:/data$ python test_storage.py
Traceback (most recent call last):
  File "/data/test_storage.py", line 12, in <module>
    portal.buckets.delete('MainStorage')
    ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/cterasdk/core/buckets.py", line 120, in delete
    response = self._core.api.delete(f'/locations/{name}')
  File "/usr/local/lib/python3.13/site-packages/cterasdk/clients/clients.py", line 329, in delete
    response = super().delete(path, data, on_error=XMLHandler(), **kwargs)
  File "/usr/local/lib/python3.13/site-packages/cterasdk/clients/decorators.py", line 19, in authenticate_then_execute
    return execute_request(self, *args, **kwargs)
  File "/usr/local/lib/python3.13/site-packages/cterasdk/clients/clients.py", line 240, in delete
    request = async_requests.DeleteRequest(self._builder(path), data=data_serializer(data), **kwargs)
                                                                     ~~~~~~~~~~~~~~~^^^^^^
TypeError: 'NoneType' object is not callable
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x7f5ea9496e40>
Unclosed connector
connections: ['deque([(<aiohttp.client_proto.ResponseHandler object at 0x7f5ea99d16a0>, 1168911.387573209)])']
connector: <aiohttp.connector.TCPConnector object at 0x7f5ea9496900>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant