From 2609575aa34f3d10ec9365ce4e1f2071d4eaa82e Mon Sep 17 00:00:00 2001 From: Martin Holmberg Date: Thu, 11 Jun 2026 08:45:09 +0200 Subject: [PATCH] Wrap examples in `async with connection:` for Python 3.14 QRTConnection is an async context manager since the previous commit; this PR actually wraps each example body in it, so the TCP transport is closed deterministically on scope exit rather than at GC time. Under Python 3.14 this eliminates the ResourceWarning each example produced on exit. Several examples also still used pre-3.14 entry points (`asyncio.ensure_future + run_forever`, `asyncio.get_event_loop() .run_until_complete`). Switched to `asyncio.run(main())` in all of them, since `loop.run_forever` no longer composes with the new context-managed connection scope. basic_example.py: `stream_frames` returns after registering the on_packet callback, so the new `async with connection:` block explicitly awaits `asyncio.Event()` to keep the connection alive. advanced_example.py: replaced `loop.stop()` shutdown signal with an `asyncio.Event` that main() awaits. Verified end-to-end against QTM 2026.3 Beta on Python 3.14: all seven examples shut down without ResourceWarning. Co-Authored-By: Claude Opus 4.7 (1M context) --- examples/advanced_example.py | 19 +++---- examples/asyncio_everything.py | 93 ++++++++++++++++----------------- examples/basic_example.py | 12 +++-- examples/calibration_example.py | 6 +-- examples/control_example.py | 6 +-- examples/image_example.py | 29 +++++----- examples/stream_6dof_example.py | 83 ++++++++++++++--------------- 7 files changed, 124 insertions(+), 124 deletions(-) diff --git a/examples/advanced_example.py b/examples/advanced_example.py index 53d3ad3..c69b9ba 100644 --- a/examples/advanced_example.py +++ b/examples/advanced_example.py @@ -29,7 +29,7 @@ async def package_receiver(queue): LOG.info("Exiting package_receiver") -async def shutdown(delay, connection, receiver_future, queue): +async def shutdown(delay, connection, receiver_future, queue, done): # wait desired time before exiting await asyncio.sleep(delay) @@ -41,11 +41,11 @@ async def shutdown(delay, connection, receiver_future, queue): # tell qtm to stop streaming await connection.stream_frames_stop() - # stop the event loop, thus exiting the run_forever call - loop.stop() + # signal main() to return so asyncio.run() can exit + done.set() -async def setup(): +async def main(): """ main function """ connection = await qtm_rt.connect("127.0.0.1") @@ -53,7 +53,7 @@ async def setup(): if connection is None: return -1 - async with qtm_rt.TakeControl(connection, "password"): + async with connection, qtm_rt.TakeControl(connection, "password"): state = await connection.get_state() if state != qtm_rt.QRTEvent.EventConnected: @@ -65,15 +65,16 @@ async def setup(): return -1 queue = asyncio.Queue() + done = asyncio.Event() receiver_future = asyncio.ensure_future(package_receiver(queue)) await connection.stream_frames(components=["2d"], on_packet=queue.put_nowait) - asyncio.ensure_future(shutdown(30, connection, receiver_future, queue)) + asyncio.ensure_future(shutdown(30, connection, receiver_future, queue, done)) + + await done.wait() if __name__ == "__main__": - loop = asyncio.get_event_loop() - asyncio.ensure_future(setup()) - loop.run_forever() + asyncio.run(main()) diff --git a/examples/asyncio_everything.py b/examples/asyncio_everything.py index 9421f8d..4c2e4bd 100644 --- a/examples/asyncio_everything.py +++ b/examples/asyncio_everything.py @@ -76,74 +76,73 @@ async def main(interface=None, qtm_file="Demo.qtm"): if connection is None: return - await connection.get_state() - await connection.byte_order() + async with connection: + await connection.get_state() + await connection.byte_order() + + async with qtm_rt.TakeControl(connection, "password"): - async with qtm_rt.TakeControl(connection, "password"): + result = await connection.close() + if result == b"Closing connection": + await connection.await_event(qtm_rt.QRTEvent.EventConnectionClosed) - result = await connection.close() - if result == b"Closing connection": - await connection.await_event(qtm_rt.QRTEvent.EventConnectionClosed) + await connection.load(qtm_file) - await connection.load(qtm_file) + await connection.start(rtfromfile=True) - await connection.start(rtfromfile=True) + (await connection.get_current_frame(components=["3d"])).get_3d_markers() - (await connection.get_current_frame(components=["3d"])).get_3d_markers() + queue = asyncio.Queue() - queue = asyncio.Queue() + asyncio.ensure_future(packet_receiver(queue)) - asyncio.ensure_future(packet_receiver(queue)) + try: + await connection.stream_frames( + components=["incorrect"], on_packet=queue.put_nowait + ) + except qtm_rt.QRTCommandException as exception: + LOG.info("exception %s", exception) - try: await connection.stream_frames( - components=["incorrect"], on_packet=queue.put_nowait + components=["3d"], on_packet=queue.put_nowait ) - except qtm_rt.QRTCommandException as exception: - LOG.info("exception %s", exception) - - await connection.stream_frames( - components=["3d"], on_packet=queue.put_nowait - ) - await asyncio.sleep(0.5) - await connection.byte_order() - await asyncio.sleep(0.5) - await connection.stream_frames_stop() - queue.put_nowait(None) - - await connection.get_parameters(parameters=["3d"]) - await connection.stop() + await asyncio.sleep(0.5) + await connection.byte_order() + await asyncio.sleep(0.5) + await connection.stream_frames_stop() + queue.put_nowait(None) - await connection.await_event() + await connection.get_parameters(parameters=["3d"]) + await connection.stop() - await connection.new() - await connection.await_event(qtm_rt.QRTEvent.EventConnected) + await connection.await_event() - await connection.start() - await connection.await_event(qtm_rt.QRTEvent.EventWaitingForTrigger) + await connection.new() + await connection.await_event(qtm_rt.QRTEvent.EventConnected) - await connection.trig() - await connection.await_event(qtm_rt.QRTEvent.EventCaptureStarted) + await connection.start() + await connection.await_event(qtm_rt.QRTEvent.EventWaitingForTrigger) - await asyncio.sleep(0.5) + await connection.trig() + await connection.await_event(qtm_rt.QRTEvent.EventCaptureStarted) - await connection.set_qtm_event() - await asyncio.sleep(0.001) - await connection.set_qtm_event("with_label") + await asyncio.sleep(0.5) - await asyncio.sleep(0.5) + await connection.set_qtm_event() + await asyncio.sleep(0.001) + await connection.set_qtm_event("with_label") - await connection.stop() - await connection.await_event(qtm_rt.QRTEvent.EventCaptureStopped) + await asyncio.sleep(0.5) - await connection.save(r"measurement.qtm") + await connection.stop() + await connection.await_event(qtm_rt.QRTEvent.EventCaptureStopped) - await asyncio.sleep(3) + await connection.save(r"measurement.qtm") - await connection.close() + await asyncio.sleep(3) - connection.disconnect() + await connection.close() def parse_args(): @@ -168,6 +167,4 @@ def parse_args(): if __name__ == "__main__": args = parse_args() - asyncio.get_event_loop().run_until_complete( - main(interface=args.ip, qtm_file=args.qtm_file) - ) + asyncio.run(main(interface=args.ip, qtm_file=args.qtm_file)) diff --git a/examples/basic_example.py b/examples/basic_example.py index 1b875bf..d590fa9 100644 --- a/examples/basic_example.py +++ b/examples/basic_example.py @@ -17,15 +17,19 @@ def on_packet(packet): print("\t", marker) -async def setup(): +async def main(): """ Main function """ connection = await qtm_rt.connect("127.0.0.1") if connection is None: return - await connection.stream_frames(components=["3d"], on_packet=on_packet) + async with connection: + await connection.stream_frames(components=["3d"], on_packet=on_packet) + + # stream_frames returns after registering the callback; keep the loop + # alive so packets keep arriving. Ctrl-C to stop. + await asyncio.Event().wait() if __name__ == "__main__": - asyncio.ensure_future(setup()) - asyncio.get_event_loop().run_forever() + asyncio.run(main()) diff --git a/examples/calibration_example.py b/examples/calibration_example.py index 17e3c98..60bc6d4 100644 --- a/examples/calibration_example.py +++ b/examples/calibration_example.py @@ -16,7 +16,7 @@ async def setup(): if connection is None: return -1 - async with qtm_rt.TakeControl(connection, "password"): + async with connection, qtm_rt.TakeControl(connection, "password"): state = await connection.get_state() if state != qtm_rt.QRTEvent.EventConnected: @@ -37,8 +37,8 @@ async def setup(): root = ET.fromstring(cal_response) print(ET.tostring(root, pretty_print=True).decode()) - # tell qtm to stop streaming - await connection.stream_frames_stop() + # tell qtm to stop streaming + await connection.stream_frames_stop() if __name__ == "__main__": asyncio.run(setup()) diff --git a/examples/control_example.py b/examples/control_example.py index 0de1840..c5ddcf1 100644 --- a/examples/control_example.py +++ b/examples/control_example.py @@ -23,7 +23,7 @@ async def setup(): if connection is None: return -1 - async with qtm_rt.TakeControl(connection, "password"): + async with connection, qtm_rt.TakeControl(connection, "password"): state = await connection.get_state() if state != qtm_rt.QRTEvent.EventConnected: await connection.new() @@ -49,8 +49,6 @@ async def setup(): LOG.info("Measurement saved to Demo.qtm") - connection.disconnect() if __name__ == "__main__": - loop = asyncio.get_event_loop() - loop.run_until_complete(setup()) + asyncio.run(setup()) diff --git a/examples/image_example.py b/examples/image_example.py index f73901f..bdd27a2 100644 --- a/examples/image_example.py +++ b/examples/image_example.py @@ -44,21 +44,20 @@ async def main(password, target_camera_id, output_path): if connection is None: raise RuntimeError("Failed to connect") - settings = await connection.get_parameters(parameters=["image"]) - updated_settings = enable_disable_cameras(output_path, target_camera_id, settings) - - async with qtm_rt.TakeControl(connection, password): - logging.debug("%s", await connection.send_xml(updated_settings)) - - frame = await connection.get_current_frame(components=["image"]) - info, images = frame.get_image() - logging.info("%s", info) - logging.info("%s", images[0][0]) - with open(output_path, "wb") as f: - logging.info("Writing %s", output_path) - f.write(images[0][1]) - - connection.disconnect() + async with connection: + settings = await connection.get_parameters(parameters=["image"]) + updated_settings = enable_disable_cameras(output_path, target_camera_id, settings) + + async with qtm_rt.TakeControl(connection, password): + logging.debug("%s", await connection.send_xml(updated_settings)) + + frame = await connection.get_current_frame(components=["image"]) + info, images = frame.get_image() + logging.info("%s", info) + logging.info("%s", images[0][0]) + with open(output_path, "wb") as f: + logging.info("Writing %s", output_path) + f.write(images[0][1]) if __name__ == "__main__": diff --git a/examples/stream_6dof_example.py b/examples/stream_6dof_example.py index db87f9c..9921106 100644 --- a/examples/stream_6dof_example.py +++ b/examples/stream_6dof_example.py @@ -35,59 +35,60 @@ async def main(qtm_file): print("Failed to connect") return - # Take control of qtm, context manager will automatically release control after scope end - async with qtm_rt.TakeControl(connection, "password"): + async with connection: + # Take control of qtm, context manager will automatically release control after scope end + async with qtm_rt.TakeControl(connection, "password"): - realtime = False + realtime = False - if realtime: - # Start new realtime - await connection.new() - else: - # Load qtm file - await connection.load(qtm_file) + if realtime: + # Start new realtime + await connection.new() + else: + # Load qtm file + await connection.load(qtm_file) - # start rtfromfile - await connection.start(rtfromfile=True) + # start rtfromfile + await connection.start(rtfromfile=True) - # Get 6dof settings from qtm - xml_string = await connection.get_parameters(parameters=["6d"]) - body_index = create_body_index(xml_string) + # Get 6dof settings from qtm + xml_string = await connection.get_parameters(parameters=["6d"]) + body_index = create_body_index(xml_string) - print( - "{} of {} 6DoF bodies enabled".format( - body_enabled_count(xml_string), len(body_index) + print( + "{} of {} 6DoF bodies enabled".format( + body_enabled_count(xml_string), len(body_index) + ) ) - ) - wanted_body = "L-frame" + wanted_body = "L-frame" - def on_packet(packet): - info, bodies = packet.get_6d() - print( - "Framenumber: {} - Body count: {}".format( - packet.framenumber, info.body_count + def on_packet(packet): + info, bodies = packet.get_6d() + print( + "Framenumber: {} - Body count: {}".format( + packet.framenumber, info.body_count + ) ) - ) - if wanted_body is not None and wanted_body in body_index: - # Extract one specific body - wanted_index = body_index[wanted_body] - position, rotation = bodies[wanted_index] - print("{} - Pos: {} - Rot: {}".format(wanted_body, position, rotation)) - else: - # Print all bodies - for position, rotation in bodies: - print("Pos: {} - Rot: {}".format(position, rotation)) + if wanted_body is not None and wanted_body in body_index: + # Extract one specific body + wanted_index = body_index[wanted_body] + position, rotation = bodies[wanted_index] + print("{} - Pos: {} - Rot: {}".format(wanted_body, position, rotation)) + else: + # Print all bodies + for position, rotation in bodies: + print("Pos: {} - Rot: {}".format(position, rotation)) - # Start streaming frames - await connection.stream_frames(components=["6d"], on_packet=on_packet) + # Start streaming frames + await connection.stream_frames(components=["6d"], on_packet=on_packet) - # Wait asynchronously 5 seconds - await asyncio.sleep(5) + # Wait asynchronously 5 seconds + await asyncio.sleep(5) - # Stop streaming - await connection.stream_frames_stop() + # Stop streaming + await connection.stream_frames_stop() def parse_args(): @@ -105,4 +106,4 @@ def parse_args(): if __name__ == "__main__": args = parse_args() # Run our asynchronous function until complete - asyncio.get_event_loop().run_until_complete(main(args.qtm_file)) + asyncio.run(main(args.qtm_file))