aioxlib 0.0.3__tar.gz → 0.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aioxlib
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Human friendly interface to XLib subsystems using asyncio python
5
5
  Author-email: Jose Tiago Macara Coutinho <coutinhotiago@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -58,6 +58,7 @@ pip install aioxlib
58
58
  import asyncio
59
59
  import aioxlib
60
60
 
61
+
61
62
  async def main():
62
63
  display = await aioxlib.get_display()
63
64
  async with display:
@@ -72,11 +73,14 @@ async def main():
72
73
  await wnd.set_wm_protocols([delete])
73
74
 
74
75
  async for event in display.events():
75
- if (event["code"] == aioxlib.Event.ClientMessage
76
- and event["type"] == protocols
77
- and event["data"][0] == delete):
76
+ if (
77
+ event["code"] == aioxlib.Event.ClientMessage
78
+ and event["type"] == protocols
79
+ and event["data"][0] == delete
80
+ ):
78
81
  break
79
82
 
83
+
80
84
  asyncio.run(main())
81
85
  ```
82
86
 
@@ -34,6 +34,7 @@ pip install aioxlib
34
34
  import asyncio
35
35
  import aioxlib
36
36
 
37
+
37
38
  async def main():
38
39
  display = await aioxlib.get_display()
39
40
  async with display:
@@ -48,11 +49,14 @@ async def main():
48
49
  await wnd.set_wm_protocols([delete])
49
50
 
50
51
  async for event in display.events():
51
- if (event["code"] == aioxlib.Event.ClientMessage
52
- and event["type"] == protocols
53
- and event["data"][0] == delete):
52
+ if (
53
+ event["code"] == aioxlib.Event.ClientMessage
54
+ and event["type"] == protocols
55
+ and event["data"][0] == delete
56
+ ):
54
57
  break
55
58
 
59
+
56
60
  asyncio.run(main())
57
61
  ```
58
62
 
@@ -4,7 +4,7 @@
4
4
  # Copyright (c) 2026 Tiago Coutinho
5
5
  # Distributed under the GPLv3 license. See LICENSE for more info.
6
6
 
7
- __version__ = "0.0.3"
7
+ __version__ = "0.0.5"
8
8
 
9
9
  import asyncio
10
10
  import enum
@@ -27,9 +27,7 @@ PROTOCOL_MINOR = 0
27
27
 
28
28
  TYPE = b"MIT-MAGIC-COOKIE-1"
29
29
  SUPPORTED_PROTOCOLS = "unix", "tcp"
30
- DISPLAY_RE = re.compile(
31
- r"^((?P<protocol>tcp|unix)/)?(?P<host>[-a-zA-Z0-9._]*):(?P<port>\d+)(\.(?P<screen>\d+))?$"
32
- )
30
+ DISPLAY_RE = re.compile(r"^((?P<protocol>tcp|unix)/)?(?P<host>[-a-zA-Z0-9._]*):(?P<port>\d+)(\.(?P<screen>\d+))?$")
33
31
 
34
32
  BASE_TCP_PORT = 6000
35
33
  UNIX_PATH_TEMPLATE = "/tmp/.X11-unix/X{}"
@@ -541,10 +539,12 @@ YSorted = 1
541
539
  YXSorted = 2
542
540
  YXBanded = 3
543
541
 
542
+
544
543
  class CoordinateMode(enum.IntEnum):
545
544
  Origin = 0
546
545
  Previous = 1
547
546
 
547
+
548
548
  Complex = 0
549
549
  Nonconvex = 1
550
550
  Convex = 2
@@ -698,7 +698,7 @@ FONT_FIELDS = (
698
698
  def parse_font_name(name):
699
699
  parts = name.split("-")[1:15]
700
700
  result = {"name": name}
701
- for key, value in zip(FONT_FIELDS, parts):
701
+ for key, value in zip(FONT_FIELDS, parts, strict=True):
702
702
  if value == "":
703
703
  result[key] = None
704
704
  else:
@@ -977,7 +977,7 @@ def pack_list_properties(wid) -> bytes:
977
977
 
978
978
  def unpack_list_properties(data, offset=0):
979
979
  offset += 8
980
- nb_atoms, = struct.unpack_from("H", data, offset)
980
+ (nb_atoms,) = struct.unpack_from("H", data, offset)
981
981
  offset += 2 + 22
982
982
  return struct.unpack_from(f"{nb_atoms}I", data, offset), offset + nb_atoms * 4
983
983
 
@@ -989,7 +989,7 @@ def pack_get_font_path():
989
989
  def unpack_get_font_path(data, offset=0):
990
990
  result, offset = unpack_reply_head(data, offset)
991
991
  assert result["reply"] == 1
992
- n, = struct.unpack_from("H", data, offset)
992
+ (n,) = struct.unpack_from("H", data, offset)
993
993
  offset += 2 + 22
994
994
  result["path"], offset = unpack_list_strings(data, n, offset)
995
995
  return result, offset + 32 + result["length"] * 4
@@ -1000,9 +1000,7 @@ def pack_intern_atom(name, only_if_exists=False):
1000
1000
  n = len(name)
1001
1001
  p = pad(n)
1002
1002
  length = 2 + (n + p) // 4
1003
- return struct.pack(
1004
- f"BBHH2x{n+p}s", Command.InternAtom, int(only_if_exists), length, n, name
1005
- )
1003
+ return struct.pack(f"BBHH2x{n + p}s", Command.InternAtom, int(only_if_exists), length, n, name)
1006
1004
 
1007
1005
 
1008
1006
  def pack_poly_fill_rectangles(drawable, gc, rectangles) -> bytes:
@@ -1052,7 +1050,7 @@ def pack_open_font(fid, name, encoding=DEFAULT_STRING_ENCODING):
1052
1050
  length = 3 + (n + p) // 4
1053
1051
 
1054
1052
  return struct.pack(
1055
- f"BxHIHxx{n+p}s",
1053
+ f"BxHIHxx{n + p}s",
1056
1054
  Command.OpenFont, # OpenFont
1057
1055
  length,
1058
1056
  fid,
@@ -1147,9 +1145,7 @@ def pack_create_gc(cid, drawable, **kwargs):
1147
1145
  fmts.append(fmt)
1148
1146
  vals.append(val)
1149
1147
  length = 4 + len(kwargs)
1150
- return struct.pack(
1151
- "".join(fmts), Command.CreateGC, length, cid, drawable, mask, *vals
1152
- )
1148
+ return struct.pack("".join(fmts), Command.CreateGC, length, cid, drawable, mask, *vals)
1153
1149
 
1154
1150
 
1155
1151
  def pack_free_gc(gc):
@@ -1224,7 +1220,7 @@ def pack_query_extension(name) -> bytes:
1224
1220
  n = len(name)
1225
1221
  p = pad(n)
1226
1222
  length = 2 + (n + p) // 4
1227
- return struct.pack(f"BxHH2x{n+p}s", Command.QueryExtension, length, n, name)
1223
+ return struct.pack(f"BxHH2x{n + p}s", Command.QueryExtension, length, n, name)
1228
1224
 
1229
1225
 
1230
1226
  def unpack_query_extension(data: bytes, offset: int = 0):
@@ -1241,7 +1237,7 @@ def pack_list_fonts(pattern, nmax=2**16):
1241
1237
  pattern = pattern.encode(DEFAULT_STRING_ENCODING)
1242
1238
  n = len(pattern)
1243
1239
  p = pad(n)
1244
- fmt = f"BxBHH{n+p}s"
1240
+ fmt = f"BxBHH{n + p}s"
1245
1241
  length = 2 + (n + p) // 4
1246
1242
  return struct.pack(fmt, Command.ListFonts, length, nmax, n, pattern)
1247
1243
 
@@ -1261,7 +1257,7 @@ def pack_image_text(wnd, gc, x, y, text):
1261
1257
  n = len(text)
1262
1258
  p = pad(n)
1263
1259
  length = 4 + (n + p) // 4
1264
- fmt = f"BBHIIhh{n+p}s"
1260
+ fmt = f"BBHIIhh{n + p}s"
1265
1261
  return struct.pack(fmt, Command.ImageText8, n, length, wnd, gc, x, y, text)
1266
1262
 
1267
1263
 
@@ -1270,9 +1266,7 @@ def pack_image(format, drawable, gc, w, h, x, y, left_pad, depth, data):
1270
1266
  n = len(data)
1271
1267
  p = pad(n)
1272
1268
  length = 6 + (n + p) // 4
1273
- head = struct.pack(
1274
- fmt, 72, format, length, drawable, gc, w, h, x, y, left_pad, depth
1275
- )
1269
+ head = struct.pack(fmt, 72, format, length, drawable, gc, w, h, x, y, left_pad, depth)
1276
1270
  return head, data, p * b"\x00"
1277
1271
 
1278
1272
 
@@ -1312,9 +1306,7 @@ def get_auth_config(filename: str) -> list[dict]:
1312
1306
  offset += 2
1313
1307
  data = raw[offset : offset + datalen]
1314
1308
  offset += datalen
1315
- entries.append(
1316
- {"family": family, "address": addr, "port": num, "name": name, "data": data}
1317
- )
1309
+ entries.append({"family": family, "address": addr, "port": num, "name": name, "data": data})
1318
1310
  return entries
1319
1311
 
1320
1312
 
@@ -1468,9 +1460,7 @@ class Window(Drawable):
1468
1460
  async def set_name(self, name):
1469
1461
  await self.change_text_property(Atom.WM_NAME, name)
1470
1462
 
1471
- async def change_text_property(
1472
- self, property, data, property_type=Atom.STRING, mode=PropMode.Replace
1473
- ):
1463
+ async def change_text_property(self, property, data, property_type=Atom.STRING, mode=PropMode.Replace):
1474
1464
  if not isinstance(data, bytes):
1475
1465
  if property_type == Atom.STRING:
1476
1466
  data = data.encode(DEFAULT_STRING_ENCODING)
@@ -1490,12 +1480,8 @@ class Window(Drawable):
1490
1480
  async def destroy(self):
1491
1481
  return await self.display.destroy_window(self.id)
1492
1482
 
1493
- async def change_property(
1494
- self, property, property_type, format, data, mode=PropMode.Replace
1495
- ):
1496
- payload = pack_change_property(
1497
- self.id, property, property_type, format, data, mode
1498
- )
1483
+ async def change_property(self, property, property_type, format, data, mode=PropMode.Replace):
1484
+ payload = pack_change_property(self.id, property, property_type, format, data, mode)
1499
1485
  self.display.connection.write(payload)
1500
1486
 
1501
1487
  async def set_wm_protocols(self, protocols):
@@ -1509,7 +1495,7 @@ class Window(Drawable):
1509
1495
  return await self.display.list_properties(self)
1510
1496
 
1511
1497
  async def clear_area(self, x, y, w, h, exposure=False):
1512
- await self.display.clear_area(self , x, y, w, h, exposure)
1498
+ await self.display.clear_area(self, x, y, w, h, exposure)
1513
1499
 
1514
1500
  async def clear(self, exposure=0):
1515
1501
  await self.clear_area(0, 0, 0, 0, exposure)
@@ -1539,19 +1525,6 @@ class GC(Resource):
1539
1525
  self.display.connection.write(payload)
1540
1526
 
1541
1527
 
1542
- class LogBytes:
1543
- __slots__ = ["data"]
1544
- Limit = 32
1545
-
1546
- def __init__(self, data):
1547
- self.data = data
1548
-
1549
- def __str__(self):
1550
- if len(self.data) > self.Limit:
1551
- return " ".join(f"{b:02X}" for b in self.data[: self.Limit]) + " [...]"
1552
- return " ".join(f"{b:02X}" for b in self.data)
1553
-
1554
-
1555
1528
  class Connection:
1556
1529
  def __init__(self, reader, writer, config):
1557
1530
  self.reader = reader
@@ -1586,13 +1559,6 @@ class Connection:
1586
1559
  self.writer.write(payload)
1587
1560
 
1588
1561
  def writelines(self, lines):
1589
- l0 = lines[0]
1590
- code = l0[0]
1591
- if code in {LITTLE_ENDIAN, BIG_ENDIAN}:
1592
- cmd_name = "Setup"
1593
- else:
1594
- cmd_name = Command(code).name
1595
- log.debug("WRITEL(%3d): %s %s", len(lines), cmd_name, LogBytes(l0))
1596
1562
  self.writer.writelines(lines)
1597
1563
 
1598
1564
  async def flush(self):
@@ -1646,11 +1612,7 @@ class Xauthority:
1646
1612
  ename, eport = entry["name"], entry["port"]
1647
1613
  if not eport and entry["name"] not in matches:
1648
1614
  eport = bport
1649
- if (
1650
- entry["family"] == family
1651
- and entry["address"] == address
1652
- and eport == bport
1653
- ):
1615
+ if entry["family"] == family and entry["address"] == address and eport == bport:
1654
1616
  matches[ename] = entry
1655
1617
  for type_ in types:
1656
1618
  try:
@@ -1708,9 +1670,7 @@ def pack_shm_attach(opcode, rid, shmid, read_only=True):
1708
1670
  def pack_shm_attach_fd(opcode, fd, read_only=True):
1709
1671
  fmt = "BBHIB3x"
1710
1672
  length = struct.calcsize(fmt) // 4
1711
- return struct.pack(
1712
- fmt, opcode, SharedMemoryOperation.AttachFd, length, fd, 1 if read_only else 0
1713
- )
1673
+ return struct.pack(fmt, opcode, SharedMemoryOperation.AttachFd, length, fd, 1 if read_only else 0)
1714
1674
 
1715
1675
 
1716
1676
  def pack_shm_version(opcode):
@@ -1858,7 +1818,213 @@ class SharedMemory(Extension):
1858
1818
  self.display.write(payload)
1859
1819
 
1860
1820
 
1861
- EXTENSIONS = {"MIT-SHM": SharedMemory}
1821
+ class DoubleBufferOperation(enum.IntEnum):
1822
+ GetVersion = 0
1823
+ AllocateBackBufferName = 1
1824
+ DeallocateBackBufferName = 2
1825
+ SwapBuffers = 3
1826
+ BeginIdiom = 4
1827
+ EndIdiom = 5
1828
+ GetVisualInfo = 6
1829
+ GetBackBufferAttributes = 7
1830
+
1831
+
1832
+ class SwapAction(enum.IntEnum):
1833
+ """What to do with the new back buffer after a swap."""
1834
+
1835
+ Undefined = 0
1836
+ Background = 1
1837
+ Untouched = 2
1838
+ Copied = 3
1839
+
1840
+
1841
+ def pack_dbe_get_version(opcode, client_major=1, client_minor=0):
1842
+ return struct.pack(
1843
+ "BBHBBH",
1844
+ opcode,
1845
+ DoubleBufferOperation.GetVersion,
1846
+ 2,
1847
+ client_major,
1848
+ client_minor,
1849
+ 0,
1850
+ )
1851
+
1852
+
1853
+ def unpack_dbe_get_version(data, offset=0):
1854
+ result, offset = unpack_reply_head(data, offset)
1855
+ major, minor = struct.unpack_from("=BB", data, offset)
1856
+ offset += 2 + 22
1857
+ result["major_version"] = major
1858
+ result["minor_version"] = minor
1859
+ return result, offset
1860
+
1861
+
1862
+ def pack_dbe_allocate_back_buffer(opcode, window, buffer, swap_action=SwapAction.Undefined):
1863
+ return struct.pack(
1864
+ "BBHIIB3x",
1865
+ opcode,
1866
+ DoubleBufferOperation.AllocateBackBufferName,
1867
+ 4,
1868
+ int(window),
1869
+ int(buffer),
1870
+ int(swap_action),
1871
+ )
1872
+
1873
+
1874
+ def pack_dbe_deallocate_back_buffer(opcode, buffer):
1875
+ return struct.pack(
1876
+ "BBHI",
1877
+ opcode,
1878
+ DoubleBufferOperation.DeallocateBackBufferName,
1879
+ 2,
1880
+ int(buffer),
1881
+ )
1882
+
1883
+
1884
+ def pack_dbe_swap_buffers(opcode, actions):
1885
+ """actions: sequence of (window, swap_action) pairs."""
1886
+ n = len(actions)
1887
+ length = 2 + 2 * n
1888
+ parts = [struct.pack("=BBHI", opcode, DoubleBufferOperation.SwapBuffers, length, n)]
1889
+ for window, action in actions:
1890
+ parts.append(struct.pack("=IB3x", int(window), int(action)))
1891
+ return b"".join(parts)
1892
+
1893
+
1894
+ def pack_dbe_begin_idiom(opcode):
1895
+ return struct.pack("=BBH", opcode, DoubleBufferOperation.BeginIdiom, 1)
1896
+
1897
+
1898
+ def pack_dbe_end_idiom(opcode):
1899
+ return struct.pack("=BBH", opcode, DoubleBufferOperation.EndIdiom, 1)
1900
+
1901
+
1902
+ def pack_dbe_get_visual_info(opcode, drawables):
1903
+ """drawables: sequence of drawable ids (empty = all screens)."""
1904
+ n = len(drawables)
1905
+ length = 2 + n
1906
+ header = struct.pack("=BBHI", opcode, DoubleBufferOperation.GetVisualInfo, length, n)
1907
+ body = b"".join(struct.pack("=I", int(d)) for d in drawables)
1908
+ return header + body
1909
+
1910
+
1911
+ def unpack_dbe_get_visual_info(data, offset=0):
1912
+ result, offset = unpack_reply_head(data, offset)
1913
+ (n_screens,) = struct.unpack_from("=I", data, offset)
1914
+ offset += 4 + 20
1915
+ screens = []
1916
+ for _ in range(n_screens):
1917
+ (n_infos,) = struct.unpack_from("=I", data, offset)
1918
+ offset += 4
1919
+ infos = []
1920
+ for _ in range(n_infos):
1921
+ visual, depth, perflevel = struct.unpack_from("=IBB2x", data, offset)
1922
+ offset += 8
1923
+ infos.append({"visual": visual, "depth": depth, "perflevel": perflevel})
1924
+ screens.append(infos)
1925
+ result["screens"] = screens
1926
+ return result, offset
1927
+
1928
+
1929
+ def pack_dbe_get_back_buffer_attributes(opcode, buffer):
1930
+ return struct.pack(
1931
+ "BBHI",
1932
+ opcode,
1933
+ DoubleBufferOperation.GetBackBufferAttributes,
1934
+ 2,
1935
+ int(buffer),
1936
+ )
1937
+
1938
+
1939
+ def unpack_dbe_get_back_buffer_attributes(data, offset=0):
1940
+ result, offset = unpack_reply_head(data, offset)
1941
+ (window,) = struct.unpack_from("=I", data, offset)
1942
+ offset += 4 + 20
1943
+ result["window"] = window
1944
+ return result, offset
1945
+
1946
+
1947
+ class BackBuffer(Drawable):
1948
+ def __init__(self, bid, window, extension):
1949
+ super().__init__(extension.display, bid)
1950
+ self.window = window
1951
+ self.extension = extension
1952
+
1953
+ async def swap(self, swap_action=SwapAction.Undefined):
1954
+ return await self.extension.swap(self.window, swap_action)
1955
+
1956
+ async def __aenter__(self):
1957
+ return self
1958
+
1959
+ async def __aexit__(self, *_):
1960
+ await self.extension.deallocate_back_buffer(self.id)
1961
+
1962
+
1963
+ class DoubleBuffer(Extension):
1964
+ """X Double Buffer Extension (DOUBLE-BUFFER / DBE).
1965
+
1966
+ Provides front/back buffers for flicker-free animation. Query with::
1967
+
1968
+ dbe = await display.query_extension("DOUBLE-BUFFER")
1969
+ """
1970
+
1971
+ BaseBadBuffer = 0
1972
+
1973
+ def __init__(self, display, info):
1974
+ super().__init__(display, info)
1975
+ self.BadBuffer = info["base_error"] + self.BaseBadBuffer
1976
+ self.errors = {self.BadBuffer: "BadBuffer"}
1977
+ self.commands = {self.code: "Dbe"}
1978
+
1979
+ async def version(self, client_major=1, client_minor=0):
1980
+ payload = pack_dbe_get_version(self.code, client_major, client_minor)
1981
+ return await self.display.write_read(payload, unpack_dbe_get_version)
1982
+
1983
+ async def allocate_back_buffer(self, window, swap_action=SwapAction.Undefined, buffer=None):
1984
+ """Allocate a back-buffer XID for *window*.
1985
+
1986
+ Returns the back-buffer resource id (drawable usable with core
1987
+ drawing requests).
1988
+ """
1989
+ if buffer is None:
1990
+ buffer = self.display.resources.allocate()
1991
+ payload = pack_dbe_allocate_back_buffer(self.code, window, buffer, swap_action)
1992
+ self.display.write(payload)
1993
+ return BackBuffer(buffer, window, self)
1994
+
1995
+ async def deallocate_back_buffer(self, buffer):
1996
+ payload = pack_dbe_deallocate_back_buffer(self.code, buffer)
1997
+ self.display.write(payload)
1998
+
1999
+ async def swap_buffers(self, actions):
2000
+ """Swap front/back buffers.
2001
+
2002
+ *actions* is a sequence of ``(window, swap_action)`` pairs.
2003
+ """
2004
+ payload = pack_dbe_swap_buffers(self.code, actions)
2005
+ self.display.write(payload)
2006
+
2007
+ async def swap(self, window, swap_action=SwapAction.Undefined):
2008
+ """Convenience: swap a single window."""
2009
+ await self.swap_buffers([(window, swap_action)])
2010
+
2011
+ async def begin_idiom(self):
2012
+ self.display.write(pack_dbe_begin_idiom(self.code))
2013
+
2014
+ async def end_idiom(self):
2015
+ self.display.write(pack_dbe_end_idiom(self.code))
2016
+
2017
+ async def get_visual_info(self, drawables=()):
2018
+ """Return per-screen lists of visuals that support double buffering."""
2019
+ payload = pack_dbe_get_visual_info(self.code, drawables)
2020
+ return await self.display.write_read(payload, unpack_dbe_get_visual_info)
2021
+
2022
+ async def get_back_buffer_attributes(self, buffer):
2023
+ payload = pack_dbe_get_back_buffer_attributes(self.code, buffer)
2024
+ return await self.display.write_read(payload, unpack_dbe_get_back_buffer_attributes)
2025
+
2026
+
2027
+ EXTENSIONS = {"MIT-SHM": SharedMemory, "DOUBLE-BUFFER": DoubleBuffer}
1862
2028
 
1863
2029
 
1864
2030
  class Pipeline:
@@ -1956,9 +2122,7 @@ class Display:
1956
2122
  await self.flush()
1957
2123
  info = await read_setup(self)
1958
2124
  self.info = info
1959
- self.resources = ResourceManager(
1960
- info["resource_id_base"], info["resource_id_mask"]
1961
- )
2125
+ self.resources = ResourceManager(info["resource_id_base"], info["resource_id_mask"])
1962
2126
  self.screens = [Screen(self, sinfo) for sinfo in info["screens"]]
1963
2127
  self._task = asyncio.create_task(self._loop())
1964
2128
  return info
@@ -2011,9 +2175,7 @@ class Display:
2011
2175
  return self._atom_cache[name]
2012
2176
 
2013
2177
  atom = await self.intern_atom(name, only_if_exists)
2014
- if (
2015
- atom != NONE
2016
- ): # don't cache NONE responses in case someone creates this later
2178
+ if atom != NONE: # don't cache NONE responses in case someone creates this later
2017
2179
  self._atom_cache[name] = atom
2018
2180
  return atom
2019
2181
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aioxlib
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Human friendly interface to XLib subsystems using asyncio python
5
5
  Author-email: Jose Tiago Macara Coutinho <coutinhotiago@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -58,6 +58,7 @@ pip install aioxlib
58
58
  import asyncio
59
59
  import aioxlib
60
60
 
61
+
61
62
  async def main():
62
63
  display = await aioxlib.get_display()
63
64
  async with display:
@@ -72,11 +73,14 @@ async def main():
72
73
  await wnd.set_wm_protocols([delete])
73
74
 
74
75
  async for event in display.events():
75
- if (event["code"] == aioxlib.Event.ClientMessage
76
- and event["type"] == protocols
77
- and event["data"][0] == delete):
76
+ if (
77
+ event["code"] == aioxlib.Event.ClientMessage
78
+ and event["type"] == protocols
79
+ and event["data"][0] == delete
80
+ ):
78
81
  break
79
82
 
83
+
80
84
  asyncio.run(main())
81
85
  ```
82
86
 
@@ -4,4 +4,5 @@ aioxlib/__init__.py
4
4
  aioxlib.egg-info/PKG-INFO
5
5
  aioxlib.egg-info/SOURCES.txt
6
6
  aioxlib.egg-info/dependency_links.txt
7
- aioxlib.egg-info/top_level.txt
7
+ aioxlib.egg-info/top_level.txt
8
+ tests/test_aioxlib.py
@@ -49,9 +49,16 @@ docs = [
49
49
  "pymdown-extensions",
50
50
  "mkdocs-coverage",
51
51
  ]
52
+ test = [
53
+ "pytest>=7",
54
+ "pytest-cov>=4",
55
+ "coverage>=7",
56
+ "pytest-asyncio>=1.4",
57
+ ]
52
58
  dev = [
53
59
  {include-group = "build-deploy"},
54
60
  {include-group = "docs"},
61
+ {include-group = "test"},
55
62
  ]
56
63
 
57
64
  [project.scripts]
@@ -89,3 +96,14 @@ target-version = "py312"
89
96
 
90
97
  [tool.ruff.lint.isort]
91
98
  combine-as-imports = true
99
+
100
+ [tool.pytest.ini_options]
101
+ minversion = "7.0"
102
+ testpaths = ["tests"]
103
+ addopts = [
104
+ "-ra",
105
+ "--strict-markers",
106
+ "--cov=aioxlib",
107
+ "--cov-report=term-missing",
108
+ "--cov-report=html"
109
+ ]