summary refs log tree commit diff stats
path: root/python/qemu/qmp/events.py
diff options
context:
space:
mode:
authorRichard Henderson <richard.henderson@linaro.org>2025-09-16 10:10:29 -0700
committerRichard Henderson <richard.henderson@linaro.org>2025-09-16 10:10:29 -0700
commit41511ed734dbf32f3c42ece60db0b86e081de4d2 (patch)
treed90bd5d4856fba8269d7b2be2f59b2aef5f718b7 /python/qemu/qmp/events.py
parent5bf071485af9340fb7f387d071da0494f80e20d1 (diff)
parent9a494d83538680651197651031375c2b6fa2490b (diff)
downloadfocaccia-qemu-41511ed734dbf32f3c42ece60db0b86e081de4d2.tar.gz
focaccia-qemu-41511ed734dbf32f3c42ece60db0b86e081de4d2.zip
Merge tag 'python-pull-request' of https://gitlab.com/jsnow/qemu into staging
Python Pull Request

Python 3.14 support & synchronize with python-qemu-qmp repo

# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCgAdFiEE+ber27ys35W+dsvQfe+BBqr8OQ4FAmjJjxIACgkQfe+BBqr8
# OQ48aA/+JRRIEN8LMbNDRvPTTkvCxstSAb2q8yA+8ccWg0H+EGcewjd+oCoPOqjC
# SwIMAGYJ6Dv2LW6c+rK6VjKw1Da8J9WgEpKmfoWu+1Pef8odU5PoRhAvvZdMq+Eh
# Kqk0r1f87fTiZK1gCBhBUIO0oTroOYxDvIYV0B6UFDPArL8jJ5eTpGLCVAYuk8tH
# MuzQD0IcxCBoraOx9vqVMbKIHwMH/m9pJ2IqINzIStpLoFgT1d5V9CoKXImMVXmF
# XovcMWQzFz1a/lm0ybSAzhgXcpW/vNjstb1IcrigYjQWXU6S+/bRpq17c2WqAJtG
# 78Dal7heSjpvWyyCCii+QO+BegH53Mgz3W+aQN7+fkcepjivVYy8tnxOrSjJR+pX
# DqRhMNSc4CrLvJH4BOHKUsJaWMxjd4oJiNhUmhJ7MxZhPTHZvERsOo9kpoJo4eTw
# GhRV98FnJbotgs2kjQpSBF8FDj9LZqPwTfMuEU2NUsIB9o7/Iqj36RDe9L+2r9Ch
# 2UKhnUg58y4eYFoC4CO8yCfjsR6HzLdqiVaDhcu5pdQM0Dw1pxrSIHb6faNmSLL5
# v0brhgJGujWt6wAc2c3ASMf8qpWkBrlVfHybodOB2cUDcRgNk85M/s41PnGShqBZ
# Qq7VW9zR4sejwof9dTwYKuwsNzxzFdS2nLwPPkud5aDngrLsNn0=
# =jZpa
# -----END PGP SIGNATURE-----
# gpg: Signature made Tue 16 Sep 2025 09:23:46 AM PDT
# gpg:                using RSA key F9B7ABDBBCACDF95BE76CBD07DEF8106AAFC390E
# gpg: Good signature from "John Snow (John Huston) <jsnow@redhat.com>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: FAEB 9711 A12C F475 812F  18F2 88A9 064D 1835 61EB
#      Subkey fingerprint: F9B7 ABDB BCAC DF95 BE76  CBD0 7DEF 8106 AAFC 390E

* tag 'python-pull-request' of https://gitlab.com/jsnow/qemu:
  iotests/check: always enable all python warnings
  iotests/151: ensure subprocesses are cleaned up
  iotests/147: ensure temporary sockets are closed before exiting
  python: ensure QEMUQtestProtocol closes its socket
  iotests: drop compat for old version context manager
  python: synchronize qemu.qmp documentation
  python: backport 'avoid creating additional event loops per thread'
  python: backport 'Remove deprecated get_event_loop calls'
  python: backport 'qmp-tui: Do not crash if optional dependencies are not met'
  python: backport 'qmp-shell-wrap: handle missing binary gracefully'
  python: backport 'make require() preserve async-ness'
  python: backport 'feat: allow setting read buffer limit'
  python: backport 'qmp-shell: add common_parser()'
  python: backport 'Use @asynciocontextmanager'
  python: backport 'drop Python3.6 workarounds'
  python: backport 'protocol: adjust logging name when changing client name'
  python: backport 'kick event queue on legacy event_pull()'
  python: backport 'EventListener: add __repr__ method'
  python: backport 'Change error classes to have better repr methods'

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Diffstat (limited to 'python/qemu/qmp/events.py')
-rw-r--r--python/qemu/qmp/events.py50
1 files changed, 42 insertions, 8 deletions
diff --git a/python/qemu/qmp/events.py b/python/qemu/qmp/events.py
index 6199776cc6..cfb5f0ac62 100644
--- a/python/qemu/qmp/events.py
+++ b/python/qemu/qmp/events.py
@@ -12,7 +12,14 @@ EventListener Tutorial
 ----------------------
 
 In all of the following examples, we assume that we have a `QMPClient`
-instantiated named ``qmp`` that is already connected.
+instantiated named ``qmp`` that is already connected. For example:
+
+.. code:: python
+
+   from qemu.qmp import QMPClient
+
+   qmp = QMPClient('example-vm')
+   await qmp.connect('127.0.0.1', 1234)
 
 
 `listener()` context blocks with one name
@@ -87,7 +94,9 @@ This is analogous to the following code:
            event = listener.get()
            print(f"Event arrived: {event['event']}")
 
-This event stream will never end, so these blocks will never terminate.
+This event stream will never end, so these blocks will never
+terminate. Even if the QMP connection errors out prematurely, this
+listener will go silent without raising an error.
 
 
 Using asyncio.Task to concurrently retrieve events
@@ -227,16 +236,20 @@ Clearing listeners
 .. code:: python
 
    await qmp.execute('stop')
-   qmp.events.clear()
+   discarded = qmp.events.clear()
    await qmp.execute('cont')
    event = await qmp.events.get()
    assert event['event'] == 'RESUME'
+   assert discarded[0]['event'] == 'STOP'
 
 `EventListener` objects are FIFO queues. If events are not consumed,
 they will remain in the queue until they are witnessed or discarded via
 `clear()`. FIFO queues will be drained automatically upon leaving a
 context block, or when calling `remove_listener()`.
 
+Any events removed from the queue in this fashion will be returned by
+the clear call.
+
 
 Accessing listener history
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -350,6 +363,12 @@ While `listener()` is only capable of creating a single listener,
                break
 
 
+Note that in the above example, we explicitly wait on jobA to conclude
+first, and then wait for jobB to do the same. All we have guaranteed is
+that the code that waits for jobA will not accidentally consume the
+event intended for the jobB waiter.
+
+
 Extending the `EventListener` class
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -407,13 +426,13 @@ Experimental Interfaces & Design Issues
 These interfaces are not ones I am sure I will keep or otherwise modify
 heavily.
 
-qmp.listener()’s type signature
+qmp.listen()’s type signature
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-`listener()` does not return anything, because it was assumed the caller
+`listen()` does not return anything, because it was assumed the caller
 already had a handle to the listener. However, for
-``qmp.listener(EventListener())`` forms, the caller will not have saved
-a handle to the listener.
+``qmp.listen(EventListener())`` forms, the caller will not have saved a
+handle to the listener.
 
 Because this function can accept *many* listeners, I found it hard to
 accurately type in a way where it could be used in both “one” or “many”
@@ -497,6 +516,21 @@ class EventListener:
         #: Optional, secondary event filter.
         self.event_filter: Optional[EventFilter] = event_filter
 
+    def __repr__(self) -> str:
+        args: List[str] = []
+        if self.names:
+            args.append(f"names={self.names!r}")
+        if self.event_filter:
+            args.append(f"event_filter={self.event_filter!r}")
+
+        if self._queue.qsize():
+            state = f"<pending={self._queue.qsize()}>"
+        else:
+            state = ''
+
+        argstr = ", ".join(args)
+        return f"{type(self).__name__}{state}({argstr})"
+
     @property
     def history(self) -> Tuple[Message, ...]:
         """
@@ -618,7 +652,7 @@ class Events:
     def __init__(self) -> None:
         self._listeners: List[EventListener] = []
 
-        #: Default, all-events `EventListener`.
+        #: Default, all-events `EventListener`. See `qmp.events` for more info.
         self.events: EventListener = EventListener()
         self.register_listener(self.events)