Remove decorators and use async/await instead

This commit is contained in:
Arjan Molenaar 2024-08-18 16:55:28 +02:00
parent a3627e7547
commit 7d189ce2e1
18 changed files with 260 additions and 440 deletions

View File

@ -1,4 +0,0 @@
Decorators
==========
.. autoclass:: gaphas.decorators.g_async

View File

@ -57,7 +57,6 @@ Gaphas is released under the terms of the Apache Software License, version 2.0.
quadtree
table
tree
decorators
.. _Cairo: https://cairographics.org
.. _Model-View-Controller: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

View File

@ -10,13 +10,16 @@ It sports a small canvas and some trivial operations:
- Delete focused item
- Exports to SVG and PNG
"""
import asyncio
import math
import sys
import cairo
import gi
from gi.events import GLibEventLoopPolicy
# fmt: off
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk
# fmt: on
@ -417,6 +420,7 @@ def main():
app.connect("activate", activate)
asyncio.set_event_loop_policy(GLibEventLoopPolicy())
app.run()

View File

@ -1,103 +0,0 @@
"""Custom decorators."""
import functools
import inspect
import logging
import threading
from gi.repository import GLib
log = logging.getLogger(__name__)
class g_async:
"""Instead of calling the function, schedule an idle handler at a given
priority. This requires the async'ed method to be called from within the
GTK main loop. Otherwise the method is executed directly.
If a function's first argument is "self", it's considered a method.
Calling the async function from outside the gtk main loop will
yield immediate execution.
A function can also be a generator. The generator will be fully executed.
If run in the main loop, an empty iterator will be returned.
A generator is "single" by default. Because of the nature of generators
the first invocation will run till completion.
"""
def __init__(
self,
single: bool = False,
timeout: int = 0,
priority: int = GLib.PRIORITY_DEFAULT_IDLE,
) -> None:
self.single = single
self.timeout = timeout
self.priority = priority
def source(self, func):
timeout = self.timeout
s = GLib.Timeout(timeout) if timeout > 0 else GLib.Idle()
s.set_callback(func)
s.priority = self.priority
return s
def __call__(self, func):
is_method = inspect.getfullargspec(func).args[:1] == ["self"]
is_generator = inspect.isgeneratorfunction(func)
source_attr = f"__g_async__{func.__name__}"
@functools.wraps(func)
def wrapper(*args, **kwargs):
# execute directly if we're not in the main loop
if GLib.main_depth() == 0:
return func(*args, **kwargs)
elif is_generator:
# We can only run one generator at a time
holder = args[0] if is_method else func
source = getattr(holder, source_attr, 0)
if source:
return
iterator = func(*args, **kwargs)
def async_wrapper(*_args):
try:
next(iterator)
except Exception:
delattr(holder, source_attr)
return GLib.SOURCE_REMOVE
return GLib.SOURCE_CONTINUE
source = self.source(async_wrapper)
setattr(holder, source_attr, source)
source.attach()
return ()
elif self.single:
# Idle handlers should be registered per instance
holder = args[0] if is_method else func
source = getattr(holder, source_attr, 0)
if source:
return
def async_wrapper(*_args):
log.debug("async: %s %s %s", func, args, kwargs)
try:
func(*args, **kwargs)
finally:
delattr(holder, source_attr)
return GLib.SOURCE_REMOVE
source = self.source(async_wrapper)
setattr(holder, source_attr, source)
source.attach()
else:
def async_wrapper(*_args):
log.debug("async: %s %s %s", func, args, kwargs)
func(*args, **kwargs)
return GLib.SOURCE_REMOVE
self.source(async_wrapper).attach()
return wrapper

View File

@ -1,13 +1,13 @@
"""This module contains everything to display a model on a screen."""
from __future__ import annotations
import asyncio
from math import isclose
from collections.abc import Collection, Iterable
import cairo
from gi.repository import Graphene, GLib, GObject, Gtk
from gi.repository import Graphene, GObject, Gtk
from gaphas.decorators import g_async
from gaphas.geometry import Rect, Rectangle
from gaphas.item import Item
from gaphas.matrix import Matrix
@ -88,6 +88,8 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable):
self._back_buffer: cairo.Surface | None = None
self._back_buffer_needs_resizing = True
self._update_task: asyncio.Task | None = None
self._controllers: set[Gtk.EventController] = set()
self.set_can_focus(True)
@ -152,6 +154,8 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable):
self._selection.clear()
self._dirty_items.clear()
self._qtree.clear()
if self._update_task:
self._update_task.cancel()
self._model = model
@ -277,22 +281,31 @@ class GtkView(Gtk.DrawingArea, Gtk.Scrollable):
if items or removed_items:
self.update()
@g_async(single=True, priority=GLib.PRIORITY_DEFAULT)
def update(self) -> None:
def update(self) -> asyncio.Task:
"""Update view status according to the items updated in the model."""
model = self._model
if not model:
return
dirty_items = self.all_dirty_items()
model.update_now(dirty_items)
dirty_items |= self.all_dirty_items()
async def _update():
model = self._model
if not model:
return
old_bb = self._qtree.soft_bounds
self.update_bounding_box(dirty_items)
if self._qtree.soft_bounds != old_bb:
self.update_scrolling()
self.update_back_buffer()
dirty_items = self.all_dirty_items()
model.update_now(dirty_items)
dirty_items |= self.all_dirty_items()
old_bb = self._qtree.soft_bounds
self.update_bounding_box(dirty_items)
if self._qtree.soft_bounds != old_bb:
self.update_scrolling()
self.update_back_buffer()
def clear_task(task):
self._update_task = None
if not self._update_task:
self._update_task = asyncio.create_task(_update())
self._update_task.add_done_callback(clear_task)
return self._update_task
def all_dirty_items(self) -> set[Item]:
"""Return all dirty items, clearing the marked items."""

232
poetry.lock generated
View File

@ -13,13 +13,13 @@ files = [
[[package]]
name = "babel"
version = "2.15.0"
version = "2.16.0"
description = "Internationalization utilities"
optional = false
python-versions = ">=3.8"
files = [
{file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"},
{file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"},
{file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"},
{file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"},
]
[package.extras]
@ -169,63 +169,83 @@ files = [
[[package]]
name = "coverage"
version = "7.5.3"
version = "7.6.1"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"},
{file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"},
{file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"},
{file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"},
{file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"},
{file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"},
{file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"},
{file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"},
{file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"},
{file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"},
{file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"},
{file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"},
{file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"},
{file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"},
{file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"},
{file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"},
{file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"},
{file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"},
{file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"},
{file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"},
{file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"},
{file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"},
{file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"},
{file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"},
{file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"},
{file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"},
{file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"},
{file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"},
{file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"},
{file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"},
{file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"},
{file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"},
{file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"},
{file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"},
{file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"},
{file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"},
{file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"},
{file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"},
{file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"},
{file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"},
{file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"},
{file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"},
{file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"},
{file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"},
{file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"},
{file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"},
{file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"},
{file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"},
{file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"},
{file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"},
{file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"},
{file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"},
{file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"},
{file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"},
{file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"},
{file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"},
{file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"},
{file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"},
{file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"},
{file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"},
{file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"},
{file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"},
{file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"},
{file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"},
{file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"},
{file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"},
{file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"},
{file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"},
{file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"},
{file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"},
{file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"},
{file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"},
{file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"},
{file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"},
{file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"},
{file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"},
{file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"},
{file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"},
{file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"},
{file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"},
{file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"},
{file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"},
{file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"},
{file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"},
{file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"},
{file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"},
{file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"},
{file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"},
{file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"},
{file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"},
{file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"},
{file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"},
{file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"},
{file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"},
{file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"},
{file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"},
{file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"},
{file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"},
{file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"},
{file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"},
{file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"},
{file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"},
{file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"},
{file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"},
{file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"},
{file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"},
{file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"},
{file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"},
{file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"},
{file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"},
{file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"},
{file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"},
{file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"},
{file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"},
{file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"},
{file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"},
{file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"},
{file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"},
{file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"},
{file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"},
{file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"},
{file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"},
{file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"},
{file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"},
]
[package.dependencies]
@ -247,13 +267,13 @@ files = [
[[package]]
name = "exceptiongroup"
version = "1.2.1"
version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
]
[package.extras]
@ -300,22 +320,22 @@ files = [
[[package]]
name = "importlib-metadata"
version = "7.1.0"
version = "8.2.0"
description = "Read metadata from Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"},
{file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"},
{file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"},
{file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"},
]
[package.dependencies]
zipp = ">=0.5"
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"]
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
[[package]]
name = "iniconfig"
@ -416,13 +436,13 @@ files = [
[[package]]
name = "packaging"
version = "24.0"
version = "24.1"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
]
[[package]]
@ -526,6 +546,24 @@ pytest = ">=7.2"
[package.extras]
dev = ["black", "check-manifest", "check-wheel-contents", "coverage", "flake8", "mypy", "pyroma"]
[[package]]
name = "pytest-asyncio"
version = "0.23.8"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"},
{file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"},
]
[package.dependencies]
pytest = ">=7.0.0,<9"
[package.extras]
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
[[package]]
name = "pytest-cov"
version = "5.0.0"
@ -578,13 +616,13 @@ files = [
[[package]]
name = "soupsieve"
version = "2.5"
version = "2.6"
description = "A modern CSS selector implementation for Beautiful Soup."
optional = false
python-versions = ">=3.8"
files = [
{file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"},
{file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"},
{file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"},
{file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"},
]
[[package]]
@ -642,49 +680,49 @@ docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-ta
[[package]]
name = "sphinxcontrib-applehelp"
version = "1.0.8"
version = "2.0.0"
description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
optional = false
python-versions = ">=3.9"
files = [
{file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"},
{file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"},
{file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"},
{file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"},
]
[package.extras]
lint = ["docutils-stubs", "flake8", "mypy"]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["pytest"]
[[package]]
name = "sphinxcontrib-devhelp"
version = "1.0.6"
version = "2.0.0"
description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents"
optional = false
python-versions = ">=3.9"
files = [
{file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"},
{file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"},
{file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"},
{file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"},
]
[package.extras]
lint = ["docutils-stubs", "flake8", "mypy"]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["pytest"]
[[package]]
name = "sphinxcontrib-htmlhelp"
version = "2.0.5"
version = "2.1.0"
description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
optional = false
python-versions = ">=3.9"
files = [
{file = "sphinxcontrib_htmlhelp-2.0.5-py3-none-any.whl", hash = "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"},
{file = "sphinxcontrib_htmlhelp-2.0.5.tar.gz", hash = "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015"},
{file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"},
{file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"},
]
[package.extras]
lint = ["docutils-stubs", "flake8", "mypy"]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["html5lib", "pytest"]
@ -704,33 +742,33 @@ test = ["flake8", "mypy", "pytest"]
[[package]]
name = "sphinxcontrib-qthelp"
version = "1.0.7"
version = "2.0.0"
description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents"
optional = false
python-versions = ">=3.9"
files = [
{file = "sphinxcontrib_qthelp-1.0.7-py3-none-any.whl", hash = "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"},
{file = "sphinxcontrib_qthelp-1.0.7.tar.gz", hash = "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6"},
{file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"},
{file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"},
]
[package.extras]
lint = ["docutils-stubs", "flake8", "mypy"]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["pytest"]
test = ["defusedxml (>=0.7.1)", "pytest"]
[[package]]
name = "sphinxcontrib-serializinghtml"
version = "1.1.10"
version = "2.0.0"
description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)"
optional = false
python-versions = ">=3.9"
files = [
{file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"},
{file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"},
{file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"},
{file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"},
]
[package.extras]
lint = ["docutils-stubs", "flake8", "mypy"]
lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
standalone = ["Sphinx (>=5)"]
test = ["pytest"]
@ -764,20 +802,20 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "zipp"
version = "3.19.1"
version = "3.20.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
optional = false
python-versions = ">=3.8"
files = [
{file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"},
{file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"},
{file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"},
{file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"},
]
[package.extras]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<4"
content-hash = "46ea46aa07ec956a146cb89e39ee7b860ce217e94b104f6b132c2069b1a7c113"
content-hash = "966c9edc8f3aceefd1b976959150f334452381a60c937bf2cfc78c11833c3239"

View File

@ -27,13 +27,14 @@ classifiers = [
[tool.poetry.dependencies]
python = ">=3.9,<4"
PyGObject = "^3.38.0"
PyGObject = "^3.50"
pycairo = "^1.20.0"
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = "^8.3"
pytest-cov = "^5.0"
pytest-archon = "^0.0.6"
pytest-asyncio = "^0.23.8"
[tool.poetry.group.docs]
optional=true

View File

@ -7,6 +7,7 @@ gi.require_version("Gtk", "4.0")
import pytest
import pytest_asyncio
from gi.repository import Gtk
from gaphas.canvas import Canvas
@ -32,17 +33,18 @@ def connections(canvas):
return canvas.connections
@pytest.fixture
def view(canvas):
# view.update()
return GtkView(canvas)
@pytest_asyncio.fixture
async def view(canvas):
view = GtkView(canvas)
await view.update()
return view
@pytest.fixture
def scrolled_window(view):
@pytest_asyncio.fixture
async def scrolled_window(view):
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.set_child(view)
view.update()
await view.update()
return scrolled_window
@ -54,10 +56,11 @@ def window(view):
window.destroy()
@pytest.fixture
def box(canvas, connections):
@pytest_asyncio.fixture
async def box(canvas, connections, view):
box = Box(connections)
canvas.add(box)
await view.update()
return box

View File

@ -1,177 +0,0 @@
from __future__ import annotations
import functools
import time
import pytest
from gi.repository import GLib
from gaphas.decorators import g_async
@g_async()
def async_function(self, token):
self.append(token)
@g_async(single=True)
def single_function(list, token):
list.append(token)
@g_async(timeout=10)
def timeout_function(list, token):
list.append(token)
@g_async()
def generator_function(list, tokens):
for token in tokens:
list.append(token)
yield
class Obj(list):
@g_async(single=True)
def single_method(self, token):
self.append(token)
@pytest.fixture
def obj():
return Obj()
def iteration():
ctx = GLib.main_context_default()
while ctx.pending():
ctx.iteration(False)
def in_main_context(func):
@functools.wraps(func)
def run_async():
GLib.idle_add(func)
iteration()
return run_async
@in_main_context
def test_in_main_context():
assert GLib.main_depth() == 1
def test_function_is_called_when_not_in_main_loop():
called: list[str] = []
async_function(called, "called")
assert "called" in called
def test_generator_is_called_when_not_in_main_loop():
called: list[str] = []
for _ in generator_function(called, ["one", "two"]):
pass
assert called == ["one", "two"]
@in_main_context
def test_function_is_not_called_directly_in_main_loop():
called: list[str] = []
async_function(called, "called")
assert "called" not in called
def test_function_is_called_from_main_loop():
called: list[str] = []
@in_main_context
def fn():
async_function(called, "called")
assert "called" not in called
fn()
assert "called" in called
def test_single_function_is_called_once():
called: list[str] = []
@in_main_context
def fn():
single_function(called, "first")
single_function(called, "second")
single_function(called, "third")
fn()
assert "first" in called
assert "second" not in called
assert "third" not in called
def test_single_method_is_called_once_per_instance():
first = Obj()
second = Obj()
@in_main_context
def fn():
first.single_method("first")
second.single_method("second")
fn()
assert "first" in first
assert "second" in second
assert "first" not in second
assert "second" not in first
def test_timeout_function():
called: list[str] = []
@in_main_context
def fn():
timeout_function(called, "first")
async_function(called, "second")
fn()
# wait a bit for timeout resource to trigger
time.sleep(0.01)
iteration()
assert called == ["second", "first"]
def test_run_generator_to_completion():
called: list[str] = []
@in_main_context
def fn():
for _ in generator_function(called, ["one", "two", "three"]):
pass
fn()
assert called == ["one", "two", "three"]
def test_run_first_generator_to_completion():
called: list[str] = []
@in_main_context
def fn():
generator_function(called, ["one", "two", "three"])
generator_function(called, ["four", "five"])
fn()
assert called == ["one", "two", "three"]

View File

@ -23,7 +23,8 @@ def test_box_handle_order(box):
@pytest.mark.parametrize("count", [1, 2, 10, 99])
def test_resize_by_dragging_se_handle(canvas, box, count):
@pytest.mark.asyncio
async def test_resize_by_dragging_se_handle(canvas, box, count):
h_nw, h_ne, h_se, h_sw = box.handles()
for _ in range(count):

View File

@ -75,7 +75,8 @@ def test_line_guide_horizontal(line, canvas):
assert 30.0 == guides[2]
def test_guide_item_in_motion(connections, canvas, view, window):
@pytest.mark.asyncio
async def test_guide_item_in_motion(connections, canvas, view, window):
e1 = Element(connections)
e2 = Element(connections)
e3 = Element(connections)
@ -88,6 +89,8 @@ def test_guide_item_in_motion(connections, canvas, view, window):
e2.matrix.translate(40, 40)
canvas.request_update(e2)
await view.update()
assert 40 == e2.matrix[4]
assert 40 == e2.matrix[5]
@ -108,7 +111,8 @@ def test_guide_item_in_motion(connections, canvas, view, window):
assert 20 == e3.matrix[5]
def test_guide_item_in_motion_2(connections, canvas, view):
@pytest.mark.asyncio
async def test_guide_item_in_motion_2(connections, canvas, view):
e1 = Element(connections)
e2 = Element(connections)
e3 = Element(connections)
@ -121,6 +125,8 @@ def test_guide_item_in_motion_2(connections, canvas, view):
e2.matrix.translate(40, 40)
canvas.request_update(e2)
await view.update()
assert 40 == e2.matrix[4]
assert 40 == e2.matrix[5]

View File

@ -1,14 +1,18 @@
import pytest
from gaphas.handlemove import ItemHandleMove
def test_can_connect(line, box, connections, view):
@pytest.mark.asyncio
async def test_can_connect(line, box, connections, view):
handle_move = ItemHandleMove(line, line.head, view)
handle_move.connect((0, 0))
assert connections.get_connection(line.head)
def test_handle_is_connected_and_constraint_removed_when_moved(
@pytest.mark.asyncio
async def test_handle_is_connected_and_constraint_removed_when_moved(
line, box, connections, view
):
handle_move = ItemHandleMove(line, line.head, view)
@ -21,7 +25,8 @@ def test_handle_is_connected_and_constraint_removed_when_moved(
assert constraint not in connections.solver.constraints
def test_connected_item_can_disconnect(line, box, connections, view):
@pytest.mark.asyncio
async def test_connected_item_can_disconnect(line, box, connections, view):
handle_move = ItemHandleMove(line, line.head, view)
handle_move.connect((0, 0))
@ -35,7 +40,8 @@ def test_connected_item_can_disconnect(line, box, connections, view):
assert orig_constraint not in connections.solver.constraints
def test_connected_item_can_reconnect(line, box, connections, view):
@pytest.mark.asyncio
async def test_connected_item_can_reconnect(line, box, connections, view):
handle_move = ItemHandleMove(line, line.head, view)
handle_move.connect((0, 0))

View File

@ -11,7 +11,8 @@ def item(connections):
return Element(connections)
def test_selection_move(canvas, view, item):
@pytest.mark.asyncio
async def test_selection_move(canvas, view, item):
"""Test the Selection role methods."""
canvas.add(item)
mover = Move(item, view)

View File

@ -123,7 +123,8 @@ def test_ports_after_split(canvas, line):
assert old_ports[1] == line.ports()[2]
def test_constraints_after_split(canvas, connections, line, view):
@pytest.mark.asyncio
async def test_constraints_after_split(canvas, connections, line, view):
"""Test if constraints are recreated after line split."""
# Connect line2 to self.line
line2 = Line(connections)
@ -189,7 +190,8 @@ def test_params_error_exc(canvas, connections):
# Test Line Merging
def test_merge_first_single(line, canvas, view):
@pytest.mark.asyncio
async def test_merge_first_single(line, canvas, view):
"""Test single line merging starting from 1st segment."""
line.handles()[1].pos = (20, 0)
segment = Segment(line, canvas)
@ -223,7 +225,8 @@ def test_merge_first_single(line, canvas, view):
assert (20, 0) == port.end.pos
def test_constraints_after_merge(canvas, connections, line, view):
@pytest.mark.asyncio
async def test_constraints_after_merge(canvas, connections, line, view):
"""Test if constraints are recreated after line merge."""
line2 = Line(connections)
canvas.add(line2)
@ -231,7 +234,7 @@ def test_constraints_after_merge(canvas, connections, line, view):
canvas.request_update(line)
canvas.request_update(line2)
view.update()
await view.update()
HandleMove(line2, head, view).connect((25, 25))
cinfo = connections.get_connection(head)

View File

@ -1,7 +1,10 @@
import pytest
from gaphas.tool.hover import hover_tool, on_motion
def test_hovers_item(view, box):
@pytest.mark.asyncio
async def test_hovers_item(view, box):
tool = hover_tool()
view.add_controller(tool)

View File

@ -1,5 +1,6 @@
import math
import pytest
from gi.repository import Gtk
from gaphas.handlemove import order_items
@ -47,7 +48,8 @@ def test_should_create_a_gesture():
assert isinstance(tool, Gtk.Gesture)
def test_select_item_on_click(view, box, window):
@pytest.mark.asyncio
async def test_select_item_on_click(view, box, window):
tool = MockGesture(view)
drag_state = DragState()
selection = view.selection
@ -58,7 +60,8 @@ def test_select_item_on_click(view, box, window):
assert box in selection.selected_items
def test_start_move_handle_on_click(view, box, window):
@pytest.mark.asyncio
async def test_start_move_handle_on_click(view, box, window):
tool = MockGesture(view)
drag_state = DragState()
@ -69,7 +72,8 @@ def test_start_move_handle_on_click(view, box, window):
assert next(iter(drag_state.moving)).handle is box.handles()[0]
def test_get_item_at_point(view, box):
@pytest.mark.asyncio
async def test_get_item_at_point(view, box):
"""Hover tool only reacts on motion-notify events."""
box.width = 50
box.height = 50
@ -79,7 +83,8 @@ def test_get_item_at_point(view, box):
assert next(item_at_point(view, (60, 10)), None) is None # type: ignore[call-overload]
def test_get_unselected_item_at_point(view, box):
@pytest.mark.asyncio
async def test_get_unselected_item_at_point(view, box):
box.width = 50
box.height = 50
view.selection.select_items(box)
@ -88,7 +93,8 @@ def test_get_unselected_item_at_point(view, box):
assert next(item_at_point(view, (10, 10), exclude=(box,)), None) is None # type: ignore[call-overload]
def test_get_item_at_point_overlayed_by_bigger_item(view, canvas, connections):
@pytest.mark.asyncio
async def test_get_item_at_point_overlayed_by_bigger_item(view, canvas, connections):
"""Hover tool only reacts on motion-notify events."""
below = Box(connections)
canvas.add(below)
@ -101,6 +107,7 @@ def test_get_item_at_point_overlayed_by_bigger_item(view, canvas, connections):
above.width = 100
above.height = 100
view.request_update((below, above))
await view.update()
assert next(item_at_point(view, (10, 10)), None) is below # type: ignore[call-overload]
assert next(item_at_point(view, (-1, -1)), None) is above # type: ignore[call-overload]
@ -112,26 +119,30 @@ def test_order_by_distance():
assert [e[0] for e in order_items(m)] == [0, -1, -3, 4, 5, 10]
def test_get_handle_at_point(view, canvas, connections):
@pytest.mark.asyncio
async def test_get_handle_at_point(view, canvas, connections):
box = Box(connections)
box.min_width = 20
box.min_height = 30
box.matrix.translate(20, 20)
box.matrix.rotate(math.pi / 1.5)
canvas.add(box)
await view.update()
i, h = handle_at_point(view, (20, 20))
assert i is box
assert h is box.handles()[0]
def test_get_handle_at_point_at_pi_div_2(view, canvas, connections):
@pytest.mark.asyncio
async def test_get_handle_at_point_at_pi_div_2(view, canvas, connections):
box = Box(connections)
box.min_width = 20
box.min_height = 30
box.matrix.translate(20, 20)
box.matrix.rotate(math.pi / 2)
canvas.add(box)
await view.update()
i, h = handle_at_point(view, (20, 20))
assert i is box

View File

@ -41,7 +41,8 @@ def test_begin_state(zoom_data, view):
assert zoom_data.sy == 1
def test_scaling(zoom_data, view):
@pytest.mark.asyncio
async def test_scaling(zoom_data, view):
tool = zoom_tool()
view.add_controller(tool)
@ -51,7 +52,8 @@ def test_scaling(zoom_data, view):
assert view.matrix[3] == pytest.approx(1.2)
def test_multiple_scaling_events(zoom_data, view):
@pytest.mark.asyncio
async def test_multiple_scaling_events(zoom_data, view):
tool = zoom_tool()
view.add_controller(tool)
@ -62,7 +64,8 @@ def test_multiple_scaling_events(zoom_data, view):
assert view.matrix[3] == pytest.approx(1.2)
def test_scaling_with_unequal_scaling_factor(zoom_data, view):
@pytest.mark.asyncio
async def test_scaling_with_unequal_scaling_factor(zoom_data, view):
tool = zoom_tool()
view.add_controller(tool)
@ -74,7 +77,8 @@ def test_scaling_with_unequal_scaling_factor(zoom_data, view):
assert view.matrix[3] == pytest.approx(1.2)
def test_zoom_should_center_around_mouse_cursor(zoom_data, view):
@pytest.mark.asyncio
async def test_zoom_should_center_around_mouse_cursor(zoom_data, view):
tool = zoom_tool()
view.add_controller(tool)
zoom_data.x0 = 100
@ -86,7 +90,8 @@ def test_zoom_should_center_around_mouse_cursor(zoom_data, view):
assert view.matrix[5] == pytest.approx(-10.0)
def test_zoom_out_should_be_limited_to_20_percent(zoom_data, view):
@pytest.mark.asyncio
async def test_zoom_out_should_be_limited_to_20_percent(zoom_data, view):
tool = zoom_tool()
view.add_controller(tool)
@ -96,7 +101,8 @@ def test_zoom_out_should_be_limited_to_20_percent(zoom_data, view):
assert view.matrix[3] == pytest.approx(0.2)
def test_zoom_in_should_be_limited_to_20_times(zoom_data, view):
@pytest.mark.asyncio
async def test_zoom_in_should_be_limited_to_20_times(zoom_data, view):
tool = zoom_tool()
view.add_controller(tool)

View File

@ -1,5 +1,6 @@
"""Test cases for the View class."""
import pytest
from gi.repository import Gtk
from gaphas.canvas import Canvas
@ -27,17 +28,21 @@ def test_custom_selection_setter():
assert view.selection is custom_selection
def test_item_removal(view, canvas, box):
@pytest.mark.asyncio
async def test_item_removal(view, canvas, box):
await view.update()
assert len(list(canvas.get_all_items())) == len(view._qtree)
view.selection.focused_item = box
canvas.remove(box)
await view.update()
assert not list(canvas.get_all_items())
assert len(view._qtree) == 0
def test_view_registration():
@pytest.mark.asyncio
async def test_view_registration():
canvas = Canvas()
# GTK view does register for updates though
@ -55,7 +60,8 @@ def test_view_registration():
assert len(canvas._registered_views) == 1
def test_view_registration_2(view, canvas, window):
@pytest.mark.asyncio
async def test_view_registration_2(view, canvas, window):
"""Test view registration and destroy when view is destroyed."""
window.present()
@ -67,7 +73,8 @@ def test_view_registration_2(view, canvas, window):
assert len(canvas._registered_views) == 0
def test_scroll_adjustments_signal(view, scrolled_window):
@pytest.mark.asyncio
async def test_scroll_adjustments_signal(view, scrolled_window):
assert view.hadjustment
assert view.vadjustment
assert view.hadjustment.get_value() == 0.0
@ -84,12 +91,14 @@ def test_scroll_adjustments_signal(view, scrolled_window):
assert view.vadjustment.get_page_size() == 0.0
def test_scroll_adjustments(view, scrolled_window):
@pytest.mark.asyncio
async def test_scroll_adjustments(view, scrolled_window):
assert scrolled_window.get_hadjustment() is view.hadjustment
assert scrolled_window.get_vadjustment() is view.vadjustment
def test_will_not_remove_lone_controller(view):
@pytest.mark.asyncio
async def test_will_not_remove_lone_controller(view):
ctrl = Gtk.EventControllerMotion.new()
removed = view.remove_controller(ctrl)