From eebe7f02e06cb02241083c9f453f534a1dd3abdc Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 19 Jul 2023 22:01:10 +0200 Subject: [PATCH 01/30] ff --- koil/errors.py | 6 +- koil/helpers.py | 67 +++++++++++++++++- koil/predicates.py | 43 ++++++++++++ koil/process.py | 170 +++++++++++++++++++++++++++++++++++++++++++++ koil/vars.py | 9 +++ 5 files changed, 290 insertions(+), 5 deletions(-) create mode 100644 koil/predicates.py create mode 100644 koil/process.py diff --git a/koil/errors.py b/koil/errors.py index 50916e4..0eef742 100644 --- a/koil/errors.py +++ b/koil/errors.py @@ -6,7 +6,6 @@ class KoilStopIteration(Exception): """Wrapper around StopIteration as it interacts badly with Futures""" - class ContextError(KoilError): pass @@ -16,8 +15,11 @@ class ThreadCancelledError(KoilError): be raised from within a thread that was spawned by koil.""" +class ProcessCancelledError(KoilError): + """Mimics ayncio.CancelledError for process if they accept cancellation. This should only + be raised from within a thread that was spawned by koil.""" + class CancelledError(KoilError): """Mimics ayncio.CancelledError for futures that are living in a koiled loop. Catch this as if you would catch asyncio.CancelledError.""" - diff --git a/koil/helpers.py b/koil/helpers.py index 6eaf45f..2478b36 100644 --- a/koil/helpers.py +++ b/koil/helpers.py @@ -1,20 +1,46 @@ import asyncio import threading - +from .process import KoiledProcess import janus from koil.errors import ( KoilError, KoilStopIteration, ThreadCancelledError, + ProcessCancelledError, ) from koil.task import KoilFuture, KoilRunner from koil.utils import run_threaded_with_context -from koil.vars import * +from koil.vars import ( + input_queue_context, + output_queue_context, + is_in_process, + current_loop, + current_cancel_event, +) +import contextvars import time import logging +from .process import send_to_queue, get_from_queue def unkoil_gen(iterator, *args, **kwargs): + if is_in_process(): + input_queue = input_queue_context.get() + out_queue = output_queue_context.get() + print("Sending to Main") + send_to_queue(input_queue, "iter", iterator, args, kwargs) + + while True: + print("Waiting for answer") + answer, args = get_from_queue(out_queue) + if answer == "yield": + yield args + elif answer == "done": + break + elif answer == "cancel": + raise ProcessCancelledError("Cancelled during loop back") + else: + raise KoilError(f"Unexpected answer: {answer}") loop = current_loop.get() try: @@ -70,6 +96,24 @@ async def next_on_ait(inside_args): def unkoil(coro, *args, **kwargs): + if is_in_process(): + input_queue = input_queue_context.get() + out_queue = output_queue_context.get() + + print("Sending to Main") + send_to_queue(input_queue, "call", coro, args, kwargs) + while True: + print("Waiting for answer") + answer, returns = get_from_queue(out_queue) + if answer == "return": + return returns[0] + elif answer == "err": + raise args + elif answer == "cancel": + raise ProcessCancelledError("Cancelled during loop back") + else: + raise KoilError(f"Unexpected answer: {answer}") + loop = current_loop.get() try: loop0 = asyncio.events.get_running_loop() @@ -278,7 +322,6 @@ def wrapper( try: while True: - it_task = asyncio.create_task(yield_queue.async_q.get()) finish, unfinished = await asyncio.wait( @@ -313,6 +356,24 @@ def wrapper( raise e +async def run_processed(func, *args, **kwargs): + p = KoiledProcess() + try: + result = await p.call(func, *args, **kwargs) + return result + finally: + p.quit() + + +async def iterate_processed(func, *args, **kwargs): + p = KoiledProcess() + try: + async for i in p.iter(func, *args, **kwargs): + yield i + finally: + p.quit() + + def create_task(coro, *args, **kwargs) -> KoilFuture: return KoilRunner(coro, preset_args=args, preset_kwargs=kwargs).run() diff --git a/koil/predicates.py b/koil/predicates.py new file mode 100644 index 0000000..90c69fa --- /dev/null +++ b/koil/predicates.py @@ -0,0 +1,43 @@ +from .vars import current_loop, current_process +import asyncio + + +def running_in_thread(): + """ + Check if the current code is running in a thread + """ + + +def running_in_same_loop(): + """ + Check if the current code is running in the same loop as the main thread + """ + loop = current_loop.get() + + try: + loop0 = asyncio.events.get_running_loop() + + if not loop: + # No loop found but we are running in a loop, so probably the main loop + # without a koiled loop + return True + + if loop0 == loop: + # We are running in the same loop as the main koiled loop, we can safely + return True + + return False + except RuntimeError: + return False + + +def running_in_process(): + """ + Check if the current code is running in a process + """ + return current_process.get() is not None + + +def koiled_loop_is_healthy(): + loop = current_loop.get() + return loop.is_closed() is False and loop.is_running() is True diff --git a/koil/process.py b/koil/process.py new file mode 100644 index 0000000..6519f91 --- /dev/null +++ b/koil/process.py @@ -0,0 +1,170 @@ +from .vars import output_queue_context, input_queue_context, in_process_context +import contextvars +import multiprocessing +from .errors import ProcessCancelledError +from concurrent.futures import ThreadPoolExecutor +import asyncio +import dill +import cloudpickle + +dill.Pickler.dumps, dill.Pickler.loads = dill.dumps, dill.loads +multiprocessing.reduction.ForkingPickler = dill.Pickler +multiprocessing.reduction.dump = dill.dump + + +def send_to_queue(queue, task, *args): + cloudpickle_args = cloudpickle.dumps(args) + queue.put((task, cloudpickle_args)) + + +def get_from_queue(queue): + task, cloudpickle_args = queue.get() + if cloudpickle_args is None: + args = () + else: + args = cloudpickle.loads(cloudpickle_args) + return task, args + + +def worker(context, input_queue, output_queue): + for ctx, value in context.items(): + ctx.set(value) + + output_queue_context.set(input_queue) + input_queue_context.set(output_queue) + in_process_context.set(True) + while True: + # Wait for a task + z = get_from_queue(input_queue) + task, args = z + if task == "call": + func, args, kwargs = args + try: + result = func(*args, **kwargs) + send_to_queue(output_queue, "return", result) + except Exception as e: + send_to_queue(output_queue, "err", e) + + if task == "iter": + func, args, kwargs = args + try: + for i in func(*args, **kwargs): + send_to_queue(output_queue, "yield", i) + send_to_queue(output_queue, "done", None) + except Exception as e: + send_to_queue(output_queue, "err", e) + + if task == "cancel": + z = ProcessCancelledError("from worker") + send_to_queue(output_queue, "err", z) + break + + if task == "quit": + break + + +class KoiledProcess: + def __init__(self): + cpu_count = multiprocessing.cpu_count() + self.executor = ThreadPoolExecutor(max_workers=cpu_count) + # Create queues for communication + self.input_queue = multiprocessing.Queue() + self.output_queue = multiprocessing.Queue() + + # Start the worker process + context = contextvars.copy_context() + self.worker_process = multiprocessing.Process( + target=worker, args=(context, self.input_queue, self.output_queue) + ) + self.worker_process.start() + + async def get_output(self): + print("GETTING OUTPUT") + return await asyncio.get_event_loop().run_in_executor( + self.executor, get_from_queue, self.output_queue + ) + + async def call(self, func, *args, **kwargs): + # Send the function and arguments to the worker process + try: + send_to_queue(self.input_queue, "call", func, args, kwargs) + + # Wait for the result and then execute the callback + while True: + task, args = await self.get_output() + print("TASK", task, args) + + if task == "return": + return args + + if task == "err": + raise args + + if task == "call": + func, args, kwargs = args + + try: + result = await func(*args, **kwargs) + send_to_queue(self.input_queue, "return", result) + except Exception as e: + send_to_queue(self.input_queue, "err", e) + + if task == "iter": + func, args, kwargs = args + async for result in func(*args, **kwargs): + send_to_queue(self.input_queue, "yield", result) + + send_to_queue(self.input_queue, "done", None) + + except asyncio.CancelledError as e: + send_to_queue(self.input_queue, "cancel", None) + task, args = await self.get_output() + if task == "err": + if isinstance(args, ProcessCancelledError): + raise e + else: + raise args + + async def iter(self, func, *args, **kwargs): + # Send the function and arguments to the worker process + try: + send_to_queue(self.input_queue, "iter", func, args, kwargs) + + # Wait for the result and then execute the callback + while True: + task, args = await self.get_output() + + if task == "done": + break + + if task == "yield": + yield args + + if task == "call": + func, args, kwargs = args + result = await func(*args, **kwargs) + send_to_queue(self.input_queue, "return", result) + + if task == "err": + raise args + + if task == "iter": + func, args, kwargs = args + async for result in func(*args, **kwargs): + send_to_queue(self.input_queue, "yield", result) + + send_to_queue(self.input_queue, "done", None) + + except asyncio.CancelledError as e: + send_to_queue(self.input_queue, "cancel", None) + task, args = await self.get_output() + if task == "err": + if isinstance(args, ProcessCancelledError): + raise e + else: + raise args + + def quit(self): + # Send quit message to worker process + self.input_queue.put_nowait(("quit", None)) + self.worker_process.join() diff --git a/koil/vars.py b/koil/vars.py index 66e1916..2461e6e 100644 --- a/koil/vars.py +++ b/koil/vars.py @@ -7,7 +7,16 @@ current_cancel_event = contextvars.ContextVar("current_cancel_event", default=None) +output_queue_context = contextvars.ContextVar("out_queue_context") +input_queue_context = contextvars.ContextVar("input_queue_context") +in_process_context = contextvars.ContextVar("in_process_context", default=False) + + def check_cancelled(): cancel_event = current_cancel_event.get() if cancel_event and cancel_event.is_set(): raise ThreadCancelledError("Task was cancelled") + + +def is_in_process(): + return in_process_context.get() From 544f50724760ea428e8398f9c93fef94212461c5 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 26 Jul 2023 12:30:15 +0200 Subject: [PATCH 02/30] tests --- koil/context.py | 3 +- koil/decorators.py | 6 +- koil/helpers.py | 57 ++---- koil/koil.py | 6 +- koil/process.py | 224 ++++++++++++++++------- koil/qt.py | 20 +- koil/vars.py | 13 +- pyproject.toml | 5 + tests/test_processed.py | 132 +++++++++++++ tests/test_qtkoil.py | 13 +- tests/{test_koil.py => test_threaded.py} | 10 +- 11 files changed, 335 insertions(+), 154 deletions(-) create mode 100644 tests/test_processed.py rename tests/{test_koil.py => test_threaded.py} (88%) diff --git a/koil/context.py b/koil/context.py index 2fc7631..b6ca5a1 100644 --- a/koil/context.py +++ b/koil/context.py @@ -1,8 +1,6 @@ from contextvars import ContextVar - - class Context: def __init__(self, value): self.__value: ContextVar = value @@ -19,6 +17,7 @@ def __get_validators__(cls): # one or more validators may be yielded which will be called in the # order to validate the input, deach validator will receive as an input # the value returned from the previous validator + # ss yield cls.validate @classmethod diff --git a/koil/decorators.py b/koil/decorators.py index edf284a..c1ab043 100644 --- a/koil/decorators.py +++ b/koil/decorators.py @@ -20,10 +20,14 @@ def koilable( """ Decorator to make an async generator koilable. + Args: + fieldname (str, optional): The name of the field to store the koil instance. Defaults to "__koil". + add_connectors (bool, optional): If True, it will add the connectors to the class. Defaults to False. + koil_class (Type[Koil], optional): The class of the koil to use. Defaults to Koil. + """ def real_cls_decorator(cls: Type[T]) -> Type[T]: - cls.__koilable__ = True assert hasattr(cls, "__aenter__"), "Class must implement __aenter__" assert hasattr(cls, "__aenter__"), "Class must implement __aexit__" diff --git a/koil/helpers.py b/koil/helpers.py index 2478b36..a9f777b 100644 --- a/koil/helpers.py +++ b/koil/helpers.py @@ -11,36 +11,25 @@ from koil.task import KoilFuture, KoilRunner from koil.utils import run_threaded_with_context from koil.vars import ( - input_queue_context, - output_queue_context, - is_in_process, current_loop, current_cancel_event, ) import contextvars import time import logging -from .process import send_to_queue, get_from_queue +from .process import ( + unkoil_process_gen, + unkoil_process_func, + is_in_process +) def unkoil_gen(iterator, *args, **kwargs): if is_in_process(): - input_queue = input_queue_context.get() - out_queue = output_queue_context.get() - print("Sending to Main") - send_to_queue(input_queue, "iter", iterator, args, kwargs) + for i in unkoil_process_gen(iterator, args, kwargs): + yield i - while True: - print("Waiting for answer") - answer, args = get_from_queue(out_queue) - if answer == "yield": - yield args - elif answer == "done": - break - elif answer == "cancel": - raise ProcessCancelledError("Cancelled during loop back") - else: - raise KoilError(f"Unexpected answer: {answer}") + return loop = current_loop.get() try: @@ -97,22 +86,7 @@ async def next_on_ait(inside_args): def unkoil(coro, *args, **kwargs): if is_in_process(): - input_queue = input_queue_context.get() - out_queue = output_queue_context.get() - - print("Sending to Main") - send_to_queue(input_queue, "call", coro, args, kwargs) - while True: - print("Waiting for answer") - answer, returns = get_from_queue(out_queue) - if answer == "return": - return returns[0] - elif answer == "err": - raise args - elif answer == "cancel": - raise ProcessCancelledError("Cancelled during loop back") - else: - raise KoilError(f"Unexpected answer: {answer}") + return unkoil_process_func(coro, args, kwargs) loop = current_loop.get() try: @@ -357,21 +331,14 @@ def wrapper( async def run_processed(func, *args, **kwargs): - p = KoiledProcess() - try: - result = await p.call(func, *args, **kwargs) - return result - finally: - p.quit() + async with KoiledProcess() as p: + return await p.call(func, *args, **kwargs) async def iterate_processed(func, *args, **kwargs): - p = KoiledProcess() - try: + async with KoiledProcess() as p: async for i in p.iter(func, *args, **kwargs): yield i - finally: - p.quit() def create_task(coro, *args, **kwargs) -> KoilFuture: diff --git a/koil/koil.py b/koil/koil.py index 1f8f36f..ed464d6 100644 --- a/koil/koil.py +++ b/koil/koil.py @@ -22,7 +22,6 @@ @contextmanager def _selector_policy(uvify=True): - original_policy = asyncio.get_event_loop_policy() try: @@ -43,7 +42,6 @@ def _selector_policy(uvify=True): def run_threaded_event_loop(loop): - try: loop.run_forever() finally: @@ -162,15 +160,13 @@ def __exit__(self, *args, **kwargs): self.running = False - @dataclass class Koil(KoilMixin): - creating_instance: Optional[Any] = None "The instance that created this class through entering" uvify: bool = False - """Shoul we spawn a new thread for each task?""" + """Shoul we run the loop with uvloop?""" name: str = "KoilLoop" """How would you like to name this loop""" diff --git a/koil/process.py b/koil/process.py index 6519f91..678aeec 100644 --- a/koil/process.py +++ b/koil/process.py @@ -1,29 +1,79 @@ from .vars import output_queue_context, input_queue_context, in_process_context import contextvars import multiprocessing -from .errors import ProcessCancelledError +from .errors import ProcessCancelledError, KoilError from concurrent.futures import ThreadPoolExecutor import asyncio -import dill -import cloudpickle -dill.Pickler.dumps, dill.Pickler.loads = dill.dumps, dill.loads -multiprocessing.reduction.ForkingPickler = dill.Pickler -multiprocessing.reduction.dump = dill.dump +try: + import cloudpickle +except ImportError: + import pickle as cloudpickle -def send_to_queue(queue, task, *args): - cloudpickle_args = cloudpickle.dumps(args) +RETURN = 0 +EXCEPTION = 1 +TRACEBACK = 2 +YIELD = 3 +CANCEL = 4 +ITER = 5 +DONE = 6 +QUIT = 7 +CALL = 8 + +def is_in_process(): + return in_process_context.get() + + +def unkoil_process_gen(iterator, args, kwargs): + input_queue = input_queue_context.get() + out_queue = output_queue_context.get() + send_to_queue(input_queue, ITER, (iterator, args, kwargs)) + + while True: + answer, args_kwargs_or_exception = get_from_queue(out_queue) + if answer == YIELD: + yield args_kwargs_or_exception + elif answer == EXCEPTION: + raise args_kwargs_or_exception + elif answer == DONE: + break + elif answer == CANCEL: + raise ProcessCancelledError("Cancelled during loop back") + else: + raise KoilError(f"Unexpected answer: {answer}") + + +def unkoil_process_func(coro, args, kwargs): + input_queue = input_queue_context.get() + out_queue = output_queue_context.get() + + send_to_queue(input_queue, CALL, (coro, args, kwargs)) + while True: + answer, func_args_kwargs_return_exception = get_from_queue(out_queue) + if answer == RETURN: + return func_args_kwargs_return_exception + elif answer == EXCEPTION: + raise func_args_kwargs_return_exception + elif answer == CANCEL: + raise ProcessCancelledError("Cancelled during loop back") + else: + raise KoilError(f"Unexpected answer: {answer}") + + + +def send_to_queue(queue, task, func_args_kwargs_return_exception): + cloudpickle_args = cloudpickle.dumps(func_args_kwargs_return_exception) queue.put((task, cloudpickle_args)) def get_from_queue(queue): task, cloudpickle_args = queue.get() if cloudpickle_args is None: - args = () + func_args_kwargs_return_exception = None else: - args = cloudpickle.loads(cloudpickle_args) - return task, args + func_args_kwargs_return_exception = cloudpickle.loads(cloudpickle_args) + return task, func_args_kwargs_return_exception def worker(context, input_queue, output_queue): @@ -35,38 +85,54 @@ def worker(context, input_queue, output_queue): in_process_context.set(True) while True: # Wait for a task - z = get_from_queue(input_queue) - task, args = z - if task == "call": - func, args, kwargs = args + task, func_args_kwargs_return_exception = get_from_queue(input_queue) + if task == CALL: + func, args, kwargs = func_args_kwargs_return_exception try: result = func(*args, **kwargs) - send_to_queue(output_queue, "return", result) + send_to_queue(output_queue, RETURN, result) except Exception as e: - send_to_queue(output_queue, "err", e) + send_to_queue(output_queue, EXCEPTION, e) - if task == "iter": - func, args, kwargs = args + if task == ITER: + gen, args, kwargs = func_args_kwargs_return_exception try: - for i in func(*args, **kwargs): - send_to_queue(output_queue, "yield", i) - send_to_queue(output_queue, "done", None) + for i in gen(*args, **kwargs): + send_to_queue(output_queue, YIELD, i) + + send_to_queue(output_queue, DONE, None) except Exception as e: - send_to_queue(output_queue, "err", e) + send_to_queue(output_queue, EXCEPTION, e) - if task == "cancel": - z = ProcessCancelledError("from worker") - send_to_queue(output_queue, "err", z) + if task == CANCEL: + exception = func_args_kwargs_return_exception + try: + raise ProcessCancelledError("from worker") from exception + except Exception as e: + send_to_queue(output_queue, EXCEPTION, e) + break - if task == "quit": + if task == EXCEPTION: + print("Unsupported path TERMINAL SHIT") + raise func_args_kwargs_return_exception + + if task == QUIT: break class KoiledProcess: - def __init__(self): - cpu_count = multiprocessing.cpu_count() - self.executor = ThreadPoolExecutor(max_workers=cpu_count) + """A class that allows to call functions in a separate process.""" + + def __init__(self, max_workers=1): + """Create a new KoiledProcess. + + Args: + max_workers (int, optional): The number of workers for the put to queue calls to use. Defaults to 1. + """ + + # Create the thread pool (we propably only need one thread?) + self.executor = ThreadPoolExecutor(max_workers=max_workers) # Create queues for communication self.input_queue = multiprocessing.Queue() self.output_queue = multiprocessing.Queue() @@ -76,7 +142,16 @@ def __init__(self): self.worker_process = multiprocessing.Process( target=worker, args=(context, self.input_queue, self.output_queue) ) + self.started = False + + async def __aenter__(self): + self.started = True self.worker_process.start() + return self + + async def __aexit__(self, *args, **kwargs): + if self.started: + self.quit() async def get_output(self): print("GETTING OUTPUT") @@ -85,80 +160,93 @@ async def get_output(self): ) async def call(self, func, *args, **kwargs): + assert self.started, "You need to start the KoilProcess first" # Send the function and arguments to the worker process try: - send_to_queue(self.input_queue, "call", func, args, kwargs) + send_to_queue(self.input_queue, CALL, (func, args, kwargs)) # Wait for the result and then execute the callback while True: - task, args = await self.get_output() - print("TASK", task, args) - - if task == "return": - return args + task, func_args_kwargs_return_exception = await self.get_output() - if task == "err": - raise args + if task == RETURN: + return func_args_kwargs_return_exception - if task == "call": - func, args, kwargs = args + if task == EXCEPTION: + raise func_args_kwargs_return_exception + if task == CALL: + func, args, kwargs = func_args_kwargs_return_exception try: result = await func(*args, **kwargs) - send_to_queue(self.input_queue, "return", result) + send_to_queue(self.input_queue, RETURN, result) except Exception as e: - send_to_queue(self.input_queue, "err", e) + send_to_queue(self.input_queue, EXCEPTION, e) - if task == "iter": - func, args, kwargs = args - async for result in func(*args, **kwargs): - send_to_queue(self.input_queue, "yield", result) + if task == ITER: + func, args, kwargs = func_args_kwargs_return_exception + try: + async for result in func(*args, **kwargs): + send_to_queue(self.input_queue, YIELD, result) + + + send_to_queue(self.input_queue, DONE, None) + except Exception as e: + send_to_queue(self.input_queue, EXCEPTION, e) - send_to_queue(self.input_queue, "done", None) except asyncio.CancelledError as e: - send_to_queue(self.input_queue, "cancel", None) + send_to_queue(self.input_queue, CANCEL, e) task, args = await self.get_output() - if task == "err": + if task == EXCEPTION: if isinstance(args, ProcessCancelledError): raise e else: raise args async def iter(self, func, *args, **kwargs): + assert self.started, "You need to start the KoilProcess first" # Send the function and arguments to the worker process try: - send_to_queue(self.input_queue, "iter", func, args, kwargs) + send_to_queue(self.input_queue, ITER, (func, args, kwargs)) # Wait for the result and then execute the callback while True: - task, args = await self.get_output() + task, func_args_kwargs_return_exception = await self.get_output() - if task == "done": + if task == DONE: break - if task == "yield": - yield args + if task == YIELD: + yield func_args_kwargs_return_exception - if task == "call": - func, args, kwargs = args - result = await func(*args, **kwargs) - send_to_queue(self.input_queue, "return", result) + if task == CALL: + func, args, kwargs = func_args_kwargs_return_exception + try: + result = await func(*args, **kwargs) + send_to_queue(self.input_queue, RETURN, result) + except Exception as e: + send_to_queue(self.input_queue, EXCEPTION, e) - if task == "err": - raise args + if task == EXCEPTION: + raise func_args_kwargs_return_exception - if task == "iter": - func, args, kwargs = args - async for result in func(*args, **kwargs): - send_to_queue(self.input_queue, "yield", result) + if task == ITER: + func, args, kwargs = func_args_kwargs_return_exception + try: + async for result in func(*args, **kwargs): + send_to_queue(self.input_queue, YIELD, result) + + + send_to_queue(self.input_queue, DONE, None) + except Exception as e: + send_to_queue(self.input_queue, EXCEPTION, e) - send_to_queue(self.input_queue, "done", None) except asyncio.CancelledError as e: - send_to_queue(self.input_queue, "cancel", None) + send_to_queue(self.input_queue, CANCEL, e) task, args = await self.get_output() - if task == "err": + if task == EXCEPTION: if isinstance(args, ProcessCancelledError): raise e else: @@ -166,5 +254,5 @@ async def iter(self, func, *args, **kwargs): def quit(self): # Send quit message to worker process - self.input_queue.put_nowait(("quit", None)) + send_to_queue(self.input_queue, QUIT, None) self.worker_process.join() diff --git a/koil/qt.py b/koil/qt.py index 02264ae..7357f18 100644 --- a/koil/qt.py +++ b/koil/qt.py @@ -28,13 +28,6 @@ class UnconnectedSignalError(Exception): pass -def get_receiver_length(qobject, qsignal, callstring): - try: - return qobject.receivers(qsignal) - except: - return qobject.receivers(callstring) - - class QtFuture: def __init__(self): self.id = uuid.uuid4().hex @@ -165,15 +158,6 @@ def __call__(self, *args): self.loop.call_soon_threadsafe(self.queue.put_nowait, args) -class Iterator: - def __init__(self, queue, timeout=None) -> None: - self.queue = queue - self.timeout = timeout - - def __anext__(self): - return self.next() - - class QtSignal(QtCore.QObject, Generic[T, P]): def __init__( self, @@ -326,6 +310,10 @@ def __enter__(self): return self +def async_generator_to_qt(func): + return QtGeneratorRunner(func) + + class WrappedObject(QtCore.QObject): def __init__(self, *args, koil: Koil = None, **kwargs): super().__init__(*args, **kwargs) diff --git a/koil/vars.py b/koil/vars.py index 2461e6e..e4f8c35 100644 --- a/koil/vars.py +++ b/koil/vars.py @@ -18,5 +18,14 @@ def check_cancelled(): raise ThreadCancelledError("Task was cancelled") -def is_in_process(): - return in_process_context.get() + +def get_process_queues(): + """Get the process queues. + + Returns the input and output queues of the current process. + + Returns: + multiprocessing.Queue: The input queue + multiprocessing.Queue: The output queue + """ + return input_queue_context.get(), output_queue_context.get() diff --git a/pyproject.toml b/pyproject.toml index bf5b860..519efb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,11 @@ build-backend = "poetry.core.masonry.api" [tool.pytest.ini_options] asyncio_mode = "auto" +qt_api = "pyqt5" +markers = [ + "qt: marks tests that require a running qt application", +] + [[tool.pydoc-markdown.loaders]] type = "python" diff --git a/tests/test_processed.py b/tests/test_processed.py new file mode 100644 index 0000000..cd62759 --- /dev/null +++ b/tests/test_processed.py @@ -0,0 +1,132 @@ +import asyncio + +import pytest +from koil.errors import ContextError + +from koil.helpers import unkoil, unkoil_gen, run_processed, iterate_processed +from .context import AsyncContextManager +from koil import Koil + + +async def sleep(ins): + await asyncio.sleep(0.001) + return ins + +async def sleep_and_raise(ins): + await asyncio.sleep(0.001) + raise Exception("This is a sleep and raise exception") + + +async def iterating(): + yield 1 + await asyncio.sleep(0.001) + yield 2 + await asyncio.sleep(0.001) + yield 3 + +async def iterate_and_raise(): + yield 1 + raise Exception("This is an iterate and raise exception") + +def test_sync_context(): + + with AsyncContextManager() as c: + print(c.aprint()) + + +async def test_async_context(): + async with AsyncContextManager() as c: + print(await c.aprint()) + + +def test_sync(): + + with Koil(): + assert unkoil(sleep, 1) == 1, "Koil realized its async and was okay with that" + + + +def process_func(arg: int, number: int): + return arg + number + + +def raising_process_func(arg: int, number: int): + raise Exception("This is a test exception") + + +def back_calling_func(arg: int, number: int): + return unkoil(sleep, arg + number) + + +def back_calling_raising_func(arg: int, number: int): + return unkoil(sleep_and_raise, arg + number) + + + + + + +async def test_spawn_process_func(): + async with Koil(): + assert await run_processed(process_func, 1, number=2) == 3, "Process should run and return 3" + + +async def test_spawn_process_exception_func(): + async with Koil(): + with pytest.raises(Exception, match="This is a test exception"): + assert await run_processed(raising_process_func, 1, number=2) == 3, "Process should run and return 3" + + +async def test_spawn_process_back_calling_func(): + async with Koil(): + assert await run_processed(back_calling_func, 1, number=2) == 3, "Process should run and return 3" + +async def test_spawn_process_back_raise_calling_func(): + async with Koil(): + with pytest.raises(Exception, match="This is a sleep and raise exception"): + assert await run_processed(back_calling_raising_func, 1, number=2) == 3, "Process should run and return 3" + + + + +def process_gen(arg: int, number: int): + yield arg + number + yield arg + number + + +def raising_process_gen(arg: int, number: int): + raise Exception("This is a test exception") + + +def back_calling_gen(arg: int, number: int): + for i in unkoil_gen(iterating): + yield arg + number + + +def back_calling_raising_gen(arg: int, number: int): + for i in unkoil_gen(iterate_and_raise): + yield arg + number + +async def test_spawn_process_gen(): + async with Koil(): + async for i in iterate_processed(process_gen, 1, number=2): + assert i == 3, "Process should run and yield 3" + + +async def test_spawn_process_exception_gen(): + async with Koil(): + with pytest.raises(Exception, match="This is a test exception"): + async for i in iterate_processed(raising_process_gen, 1, number=2): + assert i == 3, "Process should run and yield 3" + + +async def test_spawn_process_back_calling_gen(): + async with Koil(): + async for i in iterate_processed(back_calling_gen, 1, number=2): + assert i == 3, "Process should run and yield 3" + +async def test_spawn_process_back_raise_calling_gen(): + async with Koil(): + with pytest.raises(Exception, match="This is an iterate and raise exception"): + async for i in iterate_processed(back_calling_raising_gen, 1, number=2): + assert i == 3, "Process should run and yield 3" \ No newline at end of file diff --git a/tests/test_qtkoil.py b/tests/test_qtkoil.py index f2cb40e..6af732f 100644 --- a/tests/test_qtkoil.py +++ b/tests/test_qtkoil.py @@ -2,7 +2,7 @@ from PyQt5 import QtWidgets, QtCore from koil.qt import QtCoro, QtFuture, QtGenerator, QtGeneratorRunner, QtKoil, QtRunner import contextvars - +import pytest x = contextvars.ContextVar("x") @@ -190,6 +190,7 @@ async def call_coro(self): print("nana") +@pytest.mark.qt def test_koil_qt_no_interference(qtbot): """Tests if just adding koil interferes with normal qtpy widgets. @@ -205,7 +206,7 @@ def test_koil_qt_no_interference(qtbot): assert widget.greet_label.text() == "Hello!" - +@pytest.mark.qt def test_koil_qt_call_task(qtbot): """Tests if we can call a task from a koil widget.""" widget = KoiledInterferingWidget() @@ -215,7 +216,7 @@ def test_koil_qt_call_task(qtbot): with qtbot.waitSignal(widget.sleep_and_resolve_task.returned) as b: qtbot.mouseClick(widget.call_task_button, QtCore.Qt.LeftButton) - +@pytest.mark.qt def test_call_gen(qtbot): """Tests if we can call a task from a koil widget.""" widget = KoiledInterferingWidget() @@ -226,7 +227,7 @@ def test_call_gen(qtbot): qtbot.mouseClick(widget.call_gen_button, QtCore.Qt.LeftButton) - +@pytest.mark.qt def test_call_future(qtbot): """Tests if we can call a task from a koil widget.""" widget = KoiledInterferingFutureWidget() @@ -240,7 +241,7 @@ def test_call_future(qtbot): assert widget.task_was_run == True assert widget.coroutine_was_run == True - +@pytest.mark.qt def test_call_raise(qtbot): """Tests if we can call a task from a koil widget.""" widget = KoiledInterferingWidget() @@ -253,7 +254,7 @@ def test_call_raise(qtbot): assert isinstance(b.args[0], Exception) - +@pytest.mark.qt def test_context(qtbot): """Tests if we can call a task from a koil widget.""" widget = KoiledInterferingWidget() diff --git a/tests/test_koil.py b/tests/test_threaded.py similarity index 88% rename from tests/test_koil.py rename to tests/test_threaded.py index 3b76abb..8004279 100644 --- a/tests/test_koil.py +++ b/tests/test_threaded.py @@ -66,7 +66,7 @@ def test_double_context(): print(c.aprint()) -def test_ierating(): +def test_iterating(): with Koil(): @@ -76,11 +76,3 @@ def test_ierating(): assert next(x) == 3 -def test_ierating(): - - with Koil(): - - x = unkoil_gen(iterating) - assert next(x) == 1 - assert next(x) == 2 - assert next(x) == 3 From 5a5727fa24c3407fe5c78ccfb134676a100262cd Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 26 Jul 2023 12:34:38 +0200 Subject: [PATCH 03/30] support for cloudpickle --- poetry.lock | 572 +++++++++++++++++++++++++------------------------ pyproject.toml | 1 + 2 files changed, 295 insertions(+), 278 deletions(-) diff --git a/poetry.lock b/poetry.lock index 545c38b..770ecfc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. + [[package]] name = "attrs" version = "22.1.0" @@ -5,12 +7,16 @@ description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] [package.extras] dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] +tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "autoflake" @@ -19,6 +25,10 @@ description = "Removes unused imports and unused variables" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "autoflake-1.7.7-py3-none-any.whl", hash = "sha256:a9b43d08f8e455824e4f7b3f078399f59ba538ba53872f466c09e55c827773ef"}, + {file = "autoflake-1.7.7.tar.gz", hash = "sha256:c8e4fc41aa3eae0f5c94b939e3a3d50923d7a9306786a6cbf4866a077b8f6832"}, +] [package.dependencies] pyflakes = ">=1.1.0" @@ -31,6 +41,29 @@ description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, + {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, + {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, + {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, + {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, + {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, + {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, + {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, + {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, + {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, + {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, + {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, + {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, + {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, + {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, + {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, + {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, + {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, + {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, + {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, + {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, +] [package.dependencies] click = ">=8.0.0" @@ -54,11 +87,27 @@ description = "Composable command line interface toolkit" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +[[package]] +name = "cloudpickle" +version = "2.2.1" +description = "Extended pickling support for Python objects" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cloudpickle-2.2.1-py3-none-any.whl", hash = "sha256:61f594d1f4c295fa5cd9014ceb3a1fc4a70b0de1164b94fbc2d854ccba056f9f"}, + {file = "cloudpickle-2.2.1.tar.gz", hash = "sha256:d89684b8de9e34a2a43b3460fbca07d09d6e25ce858df4d5a44240403b6178f5"}, +] + [[package]] name = "colorama" version = "0.4.6" @@ -66,6 +115,10 @@ description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] name = "coverage" @@ -74,6 +127,58 @@ description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, +] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} @@ -88,6 +193,10 @@ description = "Backport of PEP 654 (exception groups)" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, + {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, +] [package.extras] test = ["pytest (>=6)"] @@ -99,6 +208,10 @@ description = "Read metadata from Python packages" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, + {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, +] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} @@ -116,6 +229,10 @@ description = "iniconfig: brain-dead simple config-ini parsing" category = "dev" optional = false python-versions = "*" +files = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] [[package]] name = "janus" @@ -124,6 +241,10 @@ description = "Mixed sync-async queue to interoperate between asyncio tasks and category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "janus-1.0.0-py3-none-any.whl", hash = "sha256:2596ea5482711c1ee3ef2df6c290aaf370a13c55a007826e8f7c32d696d1d00a"}, + {file = "janus-1.0.0.tar.gz", hash = "sha256:df976f2cdcfb034b147a2d51edfc34ff6bfb12d4e2643d3ad0e10de058cb1612"}, +] [package.dependencies] typing-extensions = ">=3.7.4.3" @@ -135,6 +256,10 @@ description = "Experimental type system extensions for programs checked with the category = "dev" optional = false python-versions = "*" +files = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] [[package]] name = "packaging" @@ -143,6 +268,10 @@ description = "Core utilities for Python packages" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" @@ -154,6 +283,10 @@ description = "Utility library for gitignore style pattern matching of file path category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, + {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, +] [[package]] name = "platformdirs" @@ -162,6 +295,10 @@ description = "A small Python package for determining appropriate platform-speci category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, + {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, +] [package.extras] docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] @@ -174,6 +311,10 @@ description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -189,6 +330,44 @@ description = "Data validation and settings management using python type hints" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, + {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, + {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, + {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, + {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, + {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, + {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, + {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, + {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, + {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, + {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, + {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, + {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, + {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, + {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, + {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, + {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, + {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, + {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, + {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, + {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, + {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, + {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, + {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, + {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, + {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, + {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, + {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, + {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, + {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, + {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, + {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, + {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, + {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, + {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, + {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, +] [package.dependencies] typing-extensions = ">=4.1.0" @@ -204,6 +383,10 @@ description = "passive checker of Python programs" category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, + {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, +] [[package]] name = "pyparsing" @@ -212,6 +395,10 @@ description = "pyparsing module - Classes and methods to define and execute pars category = "main" optional = false python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] [package.extras] diagrams = ["jinja2", "railroad-diagrams"] @@ -223,6 +410,13 @@ description = "Python bindings for the Qt cross platform application toolkit" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "PyQt5-5.15.7-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:1a793748c60d5aff3850b7abf84d47c1d41edb11231b7d7c16bef602c36be643"}, + {file = "PyQt5-5.15.7-cp37-abi3-manylinux1_x86_64.whl", hash = "sha256:e319c9d8639e0729235c1b09c99afdadad96fa3dbd8392ab561b5ab5946ee6ef"}, + {file = "PyQt5-5.15.7-cp37-abi3-win32.whl", hash = "sha256:08694f0a4c7d4f3d36b2311b1920e6283240ad3b7c09b515e08262e195dcdf37"}, + {file = "PyQt5-5.15.7-cp37-abi3-win_amd64.whl", hash = "sha256:232fe5b135a095cbd024cf341d928fc672c963f88e6a52b0c605be8177c2fdb5"}, + {file = "PyQt5-5.15.7.tar.gz", hash = "sha256:755121a52b3a08cb07275c10ebb96576d36e320e572591db16cfdbc558101594"}, +] [package.dependencies] PyQt5-Qt5 = ">=5.15.0" @@ -235,6 +429,12 @@ description = "The subset of a Qt installation needed by PyQt5." category = "dev" optional = false python-versions = "*" +files = [ + {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, + {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, +] [[package]] name = "PyQt5-sip" @@ -243,6 +443,29 @@ description = "The sip module support for PyQt5" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "PyQt5_sip-12.11.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f9e312ff8284d6dfebc5366f6f7d103f84eec23a4da0be0482403933e68660"}, + {file = "PyQt5_sip-12.11.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:4031547dfb679be309094bfa79254f5badc5ddbe66b9ad38e319d84a7d612443"}, + {file = "PyQt5_sip-12.11.0-cp310-cp310-win32.whl", hash = "sha256:ad21ca0ee8cae2a41b61fc04949dccfab6fe008749627d94e8c7078cb7a73af1"}, + {file = "PyQt5_sip-12.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:3126c84568ab341c12e46ded2230f62a9a78752a70fdab13713f89a71cd44f73"}, + {file = "PyQt5_sip-12.11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd733667098cac70e89279d9c239106d543fb480def62a44e6366ccb8f68510b"}, + {file = "PyQt5_sip-12.11.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec1d8ce50be76c5c1d1c86c6dc0ccacc2446172dde98b663a17871f532f9bd44"}, + {file = "PyQt5_sip-12.11.0-cp311-cp311-win32.whl", hash = "sha256:43dfe6dd409e713edeb67019b85419a7a0dc9923bfc451d6cf3778471c122532"}, + {file = "PyQt5_sip-12.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:afa4ffffc54e306669bf2b66ea37abbc56c5cdda4f3f474d20324e3634302b12"}, + {file = "PyQt5_sip-12.11.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0f77655c62ec91d47c2c99143f248624d44dd2d8a12d016e7c020508ad418aca"}, + {file = "PyQt5_sip-12.11.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ec5e9ef78852e1f96f86d7e15c9215878422b83dde36d44f1539a3062942f19c"}, + {file = "PyQt5_sip-12.11.0-cp37-cp37m-win32.whl", hash = "sha256:d12b81c3a08abf7657a2ebc7d3649852a1f327eb2146ebadf45930486d32e920"}, + {file = "PyQt5_sip-12.11.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b69a1911f768b489846335e31e49eb34795c6b5a038ca24d894d751e3b0b44da"}, + {file = "PyQt5_sip-12.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51e377789d59196213eddf458e6927f33ba9d217b614d17d20df16c9a8b2c41c"}, + {file = "PyQt5_sip-12.11.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4e5c1559311515291ea0ab0635529f14536954e3b973a7c7890ab7e4de1c2c23"}, + {file = "PyQt5_sip-12.11.0-cp38-cp38-win32.whl", hash = "sha256:9bca450c5306890cb002fe36bbca18f979dd9e5b810b766dce8e3ce5e66ba795"}, + {file = "PyQt5_sip-12.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:f6b72035da4e8fecbb0bc4a972e30a5674a9ad5608dbddaa517e983782dbf3bf"}, + {file = "PyQt5_sip-12.11.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9356260d4feb60dbac0ab66f8a791a0d2cda1bf98c9dec8e575904a045fbf7c5"}, + {file = "PyQt5_sip-12.11.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:205f3e1b3eea3597d8e878936c1a06e04bd23a59e8b179ee806465d72eea3071"}, + {file = "PyQt5_sip-12.11.0-cp39-cp39-win32.whl", hash = "sha256:686071be054e5be6ca5aaaef7960931d4ba917277e839e2e978c7cbe3f43bb6e"}, + {file = "PyQt5_sip-12.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:42320e7a94b1085ed85d49794ed4ccfe86f1cae80b44a894db908a8aba2bc60e"}, + {file = "PyQt5_sip-12.11.0.tar.gz", hash = "sha256:b4710fd85b57edef716cc55fae45bfd5bfac6fc7ba91036f1dcc3f331ca0eb39"}, +] [[package]] name = "pytest" @@ -251,6 +474,10 @@ description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, + {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, +] [package.dependencies] attrs = ">=19.2.0" @@ -272,6 +499,10 @@ description = "Pytest support for asyncio" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-asyncio-0.20.2.tar.gz", hash = "sha256:32a87a9836298a881c0ec637ebcc952cfe23a56436bdc0d09d1511941dd8a812"}, + {file = "pytest_asyncio-0.20.2-py3-none-any.whl", hash = "sha256:07e0abf9e6e6b95894a39f688a4a875d63c2128f76c02d03d16ccbc35bcc0f8a"}, +] [package.dependencies] pytest = ">=6.1.0" @@ -287,6 +518,10 @@ description = "Pytest plugin for measuring coverage." category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} @@ -302,6 +537,10 @@ description = "pytest support for PyQt and PySide applications" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-qt-4.2.0.tar.gz", hash = "sha256:00a17b586dd530b6d7a9399923a40489ca4a9a309719011175f55dc6b5dc8f41"}, + {file = "pytest_qt-4.2.0-py2.py3-none-any.whl", hash = "sha256:a7659960a1ab2af8fc944655a157ff45d714b80ed7a6af96a4b5bb99ecf40a22"}, +] [package.dependencies] pytest = ">=3.0.0" @@ -317,6 +556,10 @@ description = "Provides an abstraction layer on top of the various Qt bindings ( category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "QtPy-2.3.0-py3-none-any.whl", hash = "sha256:8d6d544fc20facd27360ea189592e6135c614785f0dec0b4f083289de6beb408"}, + {file = "QtPy-2.3.0.tar.gz", hash = "sha256:0603c9c83ccc035a4717a12908bf6bc6cb22509827ea2ec0e94c2da7c9ed57c5"}, +] [package.dependencies] packaging = "*" @@ -331,6 +574,10 @@ description = "A lil' TOML parser" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] [[package]] name = "typed-ast" @@ -339,280 +586,7 @@ description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false python-versions = ">=3.6" - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "uvloop" -version = "0.16.0" -description = "Fast implementation of asyncio event loop on top of libuv" -category = "main" -optional = true -python-versions = ">=3.7" - -[package.extras] -dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] - -[[package]] -name = "zipp" -version = "3.10.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[extras] -qtpy = [] -uvloop = ["uvloop"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.7" -content-hash = "eb35290053d398ee62d072f064beec6082a18c19f2e0093f956163028cca6860" - -[metadata.files] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -autoflake = [ - {file = "autoflake-1.7.7-py3-none-any.whl", hash = "sha256:a9b43d08f8e455824e4f7b3f078399f59ba538ba53872f466c09e55c827773ef"}, - {file = "autoflake-1.7.7.tar.gz", hash = "sha256:c8e4fc41aa3eae0f5c94b939e3a3d50923d7a9306786a6cbf4866a077b8f6832"}, -] -black = [ - {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, - {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, - {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, - {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, - {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, - {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, - {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, - {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, - {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, - {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, - {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, - {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, - {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, - {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, - {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, - {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, - {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, - {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, - {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, - {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, - {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -colorama = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -coverage = [ - {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, - {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, - {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, - {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, - {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, - {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, - {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, - {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, - {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, - {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, - {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, - {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, - {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, - {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, - {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, - {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, -] -exceptiongroup = [ - {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, - {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, -] -importlib-metadata = [ - {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, - {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -janus = [ - {file = "janus-1.0.0-py3-none-any.whl", hash = "sha256:2596ea5482711c1ee3ef2df6c290aaf370a13c55a007826e8f7c32d696d1d00a"}, - {file = "janus-1.0.0.tar.gz", hash = "sha256:df976f2cdcfb034b147a2d51edfc34ff6bfb12d4e2643d3ad0e10de058cb1612"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -pathspec = [ - {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, - {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, -] -platformdirs = [ - {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, - {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -pydantic = [ - {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, - {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, - {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, - {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, - {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, - {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, - {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, - {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, - {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, - {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, -] -pyflakes = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, -] -pyparsing = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] -PyQt5 = [ - {file = "PyQt5-5.15.7-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:1a793748c60d5aff3850b7abf84d47c1d41edb11231b7d7c16bef602c36be643"}, - {file = "PyQt5-5.15.7-cp37-abi3-manylinux1_x86_64.whl", hash = "sha256:e319c9d8639e0729235c1b09c99afdadad96fa3dbd8392ab561b5ab5946ee6ef"}, - {file = "PyQt5-5.15.7-cp37-abi3-win32.whl", hash = "sha256:08694f0a4c7d4f3d36b2311b1920e6283240ad3b7c09b515e08262e195dcdf37"}, - {file = "PyQt5-5.15.7-cp37-abi3-win_amd64.whl", hash = "sha256:232fe5b135a095cbd024cf341d928fc672c963f88e6a52b0c605be8177c2fdb5"}, - {file = "PyQt5-5.15.7.tar.gz", hash = "sha256:755121a52b3a08cb07275c10ebb96576d36e320e572591db16cfdbc558101594"}, -] -PyQt5-Qt5 = [ - {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, -] -PyQt5-sip = [ - {file = "PyQt5_sip-12.11.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f9e312ff8284d6dfebc5366f6f7d103f84eec23a4da0be0482403933e68660"}, - {file = "PyQt5_sip-12.11.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:4031547dfb679be309094bfa79254f5badc5ddbe66b9ad38e319d84a7d612443"}, - {file = "PyQt5_sip-12.11.0-cp310-cp310-win32.whl", hash = "sha256:ad21ca0ee8cae2a41b61fc04949dccfab6fe008749627d94e8c7078cb7a73af1"}, - {file = "PyQt5_sip-12.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:3126c84568ab341c12e46ded2230f62a9a78752a70fdab13713f89a71cd44f73"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0f77655c62ec91d47c2c99143f248624d44dd2d8a12d016e7c020508ad418aca"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ec5e9ef78852e1f96f86d7e15c9215878422b83dde36d44f1539a3062942f19c"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-win32.whl", hash = "sha256:d12b81c3a08abf7657a2ebc7d3649852a1f327eb2146ebadf45930486d32e920"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b69a1911f768b489846335e31e49eb34795c6b5a038ca24d894d751e3b0b44da"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51e377789d59196213eddf458e6927f33ba9d217b614d17d20df16c9a8b2c41c"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4e5c1559311515291ea0ab0635529f14536954e3b973a7c7890ab7e4de1c2c23"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-win32.whl", hash = "sha256:9bca450c5306890cb002fe36bbca18f979dd9e5b810b766dce8e3ce5e66ba795"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:f6b72035da4e8fecbb0bc4a972e30a5674a9ad5608dbddaa517e983782dbf3bf"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9356260d4feb60dbac0ab66f8a791a0d2cda1bf98c9dec8e575904a045fbf7c5"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:205f3e1b3eea3597d8e878936c1a06e04bd23a59e8b179ee806465d72eea3071"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-win32.whl", hash = "sha256:686071be054e5be6ca5aaaef7960931d4ba917277e839e2e978c7cbe3f43bb6e"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:42320e7a94b1085ed85d49794ed4ccfe86f1cae80b44a894db908a8aba2bc60e"}, - {file = "PyQt5_sip-12.11.0.tar.gz", hash = "sha256:b4710fd85b57edef716cc55fae45bfd5bfac6fc7ba91036f1dcc3f331ca0eb39"}, -] -pytest = [ - {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, - {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, -] -pytest-asyncio = [ - {file = "pytest-asyncio-0.20.2.tar.gz", hash = "sha256:32a87a9836298a881c0ec637ebcc952cfe23a56436bdc0d09d1511941dd8a812"}, - {file = "pytest_asyncio-0.20.2-py3-none-any.whl", hash = "sha256:07e0abf9e6e6b95894a39f688a4a875d63c2128f76c02d03d16ccbc35bcc0f8a"}, -] -pytest-cov = [ - {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, - {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, -] -pytest-qt = [ - {file = "pytest-qt-4.2.0.tar.gz", hash = "sha256:00a17b586dd530b6d7a9399923a40489ca4a9a309719011175f55dc6b5dc8f41"}, - {file = "pytest_qt-4.2.0-py2.py3-none-any.whl", hash = "sha256:a7659960a1ab2af8fc944655a157ff45d714b80ed7a6af96a4b5bb99ecf40a22"}, -] -QtPy = [ - {file = "QtPy-2.3.0-py3-none-any.whl", hash = "sha256:8d6d544fc20facd27360ea189592e6135c614785f0dec0b4f083289de6beb408"}, - {file = "QtPy-2.3.0.tar.gz", hash = "sha256:0603c9c83ccc035a4717a12908bf6bc6cb22509827ea2ec0e94c2da7c9ed57c5"}, -] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] -typed-ast = [ +files = [ {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, @@ -638,11 +612,27 @@ typed-ast = [ {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, ] -typing-extensions = [ + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] -uvloop = [ + +[[package]] +name = "uvloop" +version = "0.16.0" +description = "Fast implementation of asyncio event loop on top of libuv" +category = "main" +optional = true +python-versions = ">=3.7" +files = [ {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"}, {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30ba9dcbd0965f5c812b7c2112a1ddf60cf904c1c160f398e7eed3a6b82dcd9c"}, {file = "uvloop-0.16.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bd53f7f5db562f37cd64a3af5012df8cac2c464c97e732ed556800129505bd64"}, @@ -660,7 +650,33 @@ uvloop = [ {file = "uvloop-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861"}, {file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"}, ] -zipp = [ + +[package.extras] +dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] +test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] + +[[package]] +name = "zipp" +version = "3.10.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"}, {file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"}, ] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[extras] +qtpy = [] +uvloop = ["uvloop"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.7" +content-hash = "eea55c94c1b4742f4b05efa23397496fa8130746bf6b8497efb0fe2ab76412b0" diff --git a/pyproject.toml b/pyproject.toml index 519efb2..6a42d43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ python = "^3.7" qtpy = { version = ">1", optional = true } uvloop = { version = "^0.16.0", optional = true } janus = "^1.0.0" +cloudpickle = "^2.2.1" [tool.poetry.extras] qtpy = ["qt"] From c3cc973ca21a717ceed4009ce128cc132552a771 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 26 Jul 2023 12:53:43 +0200 Subject: [PATCH 04/30] up up --- koil/process.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/koil/process.py b/koil/process.py index 678aeec..4e914aa 100644 --- a/koil/process.py +++ b/koil/process.py @@ -4,6 +4,7 @@ from .errors import ProcessCancelledError, KoilError from concurrent.futures import ThreadPoolExecutor import asyncio +import cvpickle try: @@ -24,7 +25,6 @@ def is_in_process(): return in_process_context.get() - def unkoil_process_gen(iterator, args, kwargs): input_queue = input_queue_context.get() out_queue = output_queue_context.get() @@ -76,9 +76,7 @@ def get_from_queue(queue): return task, func_args_kwargs_return_exception -def worker(context, input_queue, output_queue): - for ctx, value in context.items(): - ctx.set(value) +def worker(input_queue, output_queue): output_queue_context.set(input_queue) input_queue_context.set(output_queue) @@ -138,9 +136,8 @@ def __init__(self, max_workers=1): self.output_queue = multiprocessing.Queue() # Start the worker process - context = contextvars.copy_context() self.worker_process = multiprocessing.Process( - target=worker, args=(context, self.input_queue, self.output_queue) + target=worker, args=(self.input_queue, self.output_queue) ) self.started = False From 0c713aa0e4f06ea41e656a1be3cb020e313fd620 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 26 Jul 2023 12:54:26 +0200 Subject: [PATCH 05/30] up up --- koil/process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/koil/process.py b/koil/process.py index 4e914aa..764d7ea 100644 --- a/koil/process.py +++ b/koil/process.py @@ -39,7 +39,7 @@ def unkoil_process_gen(iterator, args, kwargs): elif answer == DONE: break elif answer == CANCEL: - raise ProcessCancelledError("Cancelled during loop back") + raise ProcessCancelledError("Cancelled during loop back of generator") else: raise KoilError(f"Unexpected answer: {answer}") From e163f7f04dc99a747102931edeb01ed5c213192d Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 26 Jul 2023 13:49:46 +0200 Subject: [PATCH 06/30] trying contextvar approach --- koil/helpers.py | 8 ++--- koil/process.py | 63 ++++++++++++++++++++++++++++------- tests/test_context_complex.py | 1 - tests/test_processed.py | 25 ++++++++++++-- 4 files changed, 77 insertions(+), 20 deletions(-) diff --git a/koil/helpers.py b/koil/helpers.py index a9f777b..742c7b1 100644 --- a/koil/helpers.py +++ b/koil/helpers.py @@ -330,13 +330,13 @@ def wrapper( raise e -async def run_processed(func, *args, **kwargs): - async with KoiledProcess() as p: +async def run_processed(func, *args, _omit_vars=None, **kwargs, ): + async with KoiledProcess(omit_vars=_omit_vars) as p: return await p.call(func, *args, **kwargs) -async def iterate_processed(func, *args, **kwargs): - async with KoiledProcess() as p: +async def iterate_processed(func, *args, _omit_vars=None, **kwargs): + async with KoiledProcess(omit_vars=_omit_vars) as p: async for i in p.iter(func, *args, **kwargs): yield i diff --git a/koil/process.py b/koil/process.py index 764d7ea..813b90a 100644 --- a/koil/process.py +++ b/koil/process.py @@ -4,7 +4,6 @@ from .errors import ProcessCancelledError, KoilError from concurrent.futures import ThreadPoolExecutor import asyncio -import cvpickle try: @@ -25,6 +24,38 @@ def is_in_process(): return in_process_context.get() + +def serialize_context(sendable_suffixes=None, omit_vars=None): + copy = contextvars.copy_context() + + send_context = {} + + for ctx, value in copy.items(): + if ctx.name in send_context: + raise KoilError(f"Context variable {ctx.name} is used multiple times. This will lead to unexpected behaviour. Please rename the variable.") + + if omit_vars and ctx.name in omit_vars: + continue + if sendable_suffixes is None: + send_context[ctx.name] = value + else: + for i in sendable_suffixes or []: + if ctx.name.endswith(i): + send_context[ctx.name] = value + return send_context + + +def deserialize_context(context): + x = contextvars.copy_context() + for ctx, value in x.items(): + if ctx.name in context: + ctx.set(context[ctx.name]) + + return x + + + + def unkoil_process_gen(iterator, args, kwargs): input_queue = input_queue_context.get() out_queue = output_queue_context.get() @@ -76,6 +107,10 @@ def get_from_queue(queue): return task, func_args_kwargs_return_exception +def gen_runner(queue, gen, *args, **kwargs): + for i in gen(*args, **kwargs): + send_to_queue(queue, YIELD, i) + def worker(input_queue, output_queue): output_queue_context.set(input_queue) @@ -85,19 +120,19 @@ def worker(input_queue, output_queue): # Wait for a task task, func_args_kwargs_return_exception = get_from_queue(input_queue) if task == CALL: - func, args, kwargs = func_args_kwargs_return_exception + func, context, args, kwargs = func_args_kwargs_return_exception try: - result = func(*args, **kwargs) + ctx = deserialize_context(context) + result = ctx.run(func, *args, **kwargs) send_to_queue(output_queue, RETURN, result) except Exception as e: send_to_queue(output_queue, EXCEPTION, e) if task == ITER: - gen, args, kwargs = func_args_kwargs_return_exception + gen, context, args, kwargs = func_args_kwargs_return_exception try: - for i in gen(*args, **kwargs): - send_to_queue(output_queue, YIELD, i) - + ctx = deserialize_context(context) + ctx.run(gen_runner, output_queue, gen, *args, **kwargs) send_to_queue(output_queue, DONE, None) except Exception as e: send_to_queue(output_queue, EXCEPTION, e) @@ -112,7 +147,6 @@ def worker(input_queue, output_queue): break if task == EXCEPTION: - print("Unsupported path TERMINAL SHIT") raise func_args_kwargs_return_exception if task == QUIT: @@ -122,7 +156,7 @@ def worker(input_queue, output_queue): class KoiledProcess: """A class that allows to call functions in a separate process.""" - def __init__(self, max_workers=1): + def __init__(self, max_workers=1, omit_vars=None): """Create a new KoiledProcess. Args: @@ -139,6 +173,8 @@ def __init__(self, max_workers=1): self.worker_process = multiprocessing.Process( target=worker, args=(self.input_queue, self.output_queue) ) + self.omit_vars = omit_vars + self.started = False async def __aenter__(self): @@ -151,7 +187,6 @@ async def __aexit__(self, *args, **kwargs): self.quit() async def get_output(self): - print("GETTING OUTPUT") return await asyncio.get_event_loop().run_in_executor( self.executor, get_from_queue, self.output_queue ) @@ -159,8 +194,10 @@ async def get_output(self): async def call(self, func, *args, **kwargs): assert self.started, "You need to start the KoilProcess first" # Send the function and arguments to the worker process + context = serialize_context(omit_vars=self.omit_vars) + try: - send_to_queue(self.input_queue, CALL, (func, args, kwargs)) + send_to_queue(self.input_queue, CALL, (func, context, args, kwargs)) # Wait for the result and then execute the callback while True: @@ -204,8 +241,10 @@ async def call(self, func, *args, **kwargs): async def iter(self, func, *args, **kwargs): assert self.started, "You need to start the KoilProcess first" # Send the function and arguments to the worker process + + context = serialize_context(omit_vars=self.omit_vars) try: - send_to_queue(self.input_queue, ITER, (func, args, kwargs)) + send_to_queue(self.input_queue, ITER, (func, context, args, kwargs)) # Wait for the result and then execute the callback while True: diff --git a/tests/test_context_complex.py b/tests/test_context_complex.py index 3fbb0c9..3d7e950 100644 --- a/tests/test_context_complex.py +++ b/tests/test_context_complex.py @@ -83,7 +83,6 @@ def test_x_sync(): l = unkoil_gen(x.g) l.send(None) l.send(None) - print("Here") try: l.send(None) except StopIteration: diff --git a/tests/test_processed.py b/tests/test_processed.py index cd62759..07779dd 100644 --- a/tests/test_processed.py +++ b/tests/test_processed.py @@ -6,8 +6,11 @@ from koil.helpers import unkoil, unkoil_gen, run_processed, iterate_processed from .context import AsyncContextManager from koil import Koil +import contextvars +t = contextvars.ContextVar("t", default=0) + async def sleep(ins): await asyncio.sleep(0.001) return ins @@ -31,12 +34,12 @@ async def iterate_and_raise(): def test_sync_context(): with AsyncContextManager() as c: - print(c.aprint()) + c.aprint() async def test_async_context(): async with AsyncContextManager() as c: - print(await c.aprint()) + await c.aprint() def test_sync(): @@ -63,6 +66,9 @@ def back_calling_raising_func(arg: int, number: int): +def context_vars(): + return t.get() + @@ -89,6 +95,7 @@ async def test_spawn_process_back_raise_calling_func(): + def process_gen(arg: int, number: int): yield arg + number yield arg + number @@ -129,4 +136,16 @@ async def test_spawn_process_back_raise_calling_gen(): async with Koil(): with pytest.raises(Exception, match="This is an iterate and raise exception"): async for i in iterate_processed(back_calling_raising_gen, 1, number=2): - assert i == 3, "Process should run and yield 3" \ No newline at end of file + assert i == 3, "Process should run and yield 3" + + + + + + +async def test_context_var(): + async with Koil(): + t.set(1) + assert await run_processed(context_vars) == 1, "Process should run and return 1" + t.set(5) + assert await run_processed(context_vars) == 5, "Process should run and return 1" \ No newline at end of file From e132bd6114d17fbce5c7fde495930d14c5e9acec Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 26 Jul 2023 13:58:41 +0200 Subject: [PATCH 07/30] silent errors --- koil/process.py | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/koil/process.py b/koil/process.py index 813b90a..4929dd1 100644 --- a/koil/process.py +++ b/koil/process.py @@ -25,10 +25,18 @@ def is_in_process(): return in_process_context.get() -def serialize_context(sendable_suffixes=None, omit_vars=None): +def matches_suffixes(name, suffixes): + for i in suffixes: + if name.endswith(i): + return True + return False + + +def serialize_context(omit_vars=None, omit_suffixes=None, silent_errors=True): copy = contextvars.copy_context() send_context = {} + omit_suffixes = omit_suffixes or ["_unpickkable"] for ctx, value in copy.items(): if ctx.name in send_context: @@ -36,12 +44,18 @@ def serialize_context(sendable_suffixes=None, omit_vars=None): if omit_vars and ctx.name in omit_vars: continue - if sendable_suffixes is None: - send_context[ctx.name] = value - else: - for i in sendable_suffixes or []: - if ctx.name.endswith(i): - send_context[ctx.name] = value + + if matches_suffixes(ctx.name, omit_suffixes): + continue + try: + send_context[ctx.name] = cloudpickle.dumps(value) + except Exception as e: + if silent_errors: + continue + else: + raise KoilError(f"Could not serialize context variable {ctx.name}") from e + + return send_context @@ -49,7 +63,7 @@ def deserialize_context(context): x = contextvars.copy_context() for ctx, value in x.items(): if ctx.name in context: - ctx.set(context[ctx.name]) + ctx.set(cloudpickle.loads(context[ctx.name])) return x @@ -156,7 +170,7 @@ def worker(input_queue, output_queue): class KoiledProcess: """A class that allows to call functions in a separate process.""" - def __init__(self, max_workers=1, omit_vars=None): + def __init__(self, max_workers=1, omit_vars=None, silent_errors=True): """Create a new KoiledProcess. Args: @@ -174,6 +188,7 @@ def __init__(self, max_workers=1, omit_vars=None): target=worker, args=(self.input_queue, self.output_queue) ) self.omit_vars = omit_vars + self.silent_errors = silent_errors self.started = False @@ -190,12 +205,14 @@ async def get_output(self): return await asyncio.get_event_loop().run_in_executor( self.executor, get_from_queue, self.output_queue ) + + def serialize_context(self): + return serialize_context(omit_vars=self.omit_vars, silent_errors=self.silent_errors) async def call(self, func, *args, **kwargs): assert self.started, "You need to start the KoilProcess first" # Send the function and arguments to the worker process - context = serialize_context(omit_vars=self.omit_vars) - + context = self.serialize_context() try: send_to_queue(self.input_queue, CALL, (func, context, args, kwargs)) @@ -242,7 +259,7 @@ async def iter(self, func, *args, **kwargs): assert self.started, "You need to start the KoilProcess first" # Send the function and arguments to the worker process - context = serialize_context(omit_vars=self.omit_vars) + context = self.serialize_context() try: send_to_queue(self.input_queue, ITER, (func, context, args, kwargs)) From c951a6b22c23bbc415cd6a678e2012cd7a09b779 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 26 Jul 2023 15:36:35 +0200 Subject: [PATCH 08/30] update strategies --- .github/workflows/docker-master.yaml | 2 +- .github/workflows/release.yaml | 2 +- .vscode/settings.json | 4 ++++ koil/composition/base.py | 4 ---- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker-master.yaml b/.github/workflows/docker-master.yaml index 22e31c2..1164ab5 100644 --- a/.github/workflows/docker-master.yaml +++ b/.github/workflows/docker-master.yaml @@ -33,7 +33,7 @@ jobs: fail-fast: false matrix: python-version: [3.7, 3.8, 3.9] - poetry-version: [1.2] + poetry-version: [1.4] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 87983e6..7d012c9 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -17,7 +17,7 @@ jobs: - name: Run image uses: abatilo/actions-poetry@v2.0.0 with: - poetry-version: "1.1.4" + poetry-version: "1.4" - name: Run Poetry Install run: poetry install - name: Publish diff --git a/.vscode/settings.json b/.vscode/settings.json index f012532..7fb39c7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -35,4 +35,8 @@ ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + }, + "python.formatting.provider": "none", } \ No newline at end of file diff --git a/koil/composition/base.py b/koil/composition/base.py index a7f96fa..9934218 100644 --- a/koil/composition/base.py +++ b/koil/composition/base.py @@ -82,8 +82,6 @@ class Config: copy_on_model_validation = "none" - - class Composition(KoiledModel): async def __aenter__(self: T) -> T: for key, value in self: @@ -106,5 +104,3 @@ def _repr_html_(self): + "\n".join(["{}".format(key) for key, value in self]) + "" ) - - From 8986666792444165ed5a6695a11c105039e31f7c Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 26 Jul 2023 15:39:03 +0200 Subject: [PATCH 09/30] up --- .github/workflows/docker-master.yaml | 2 +- tests/test_processed.py | 46 +++++++++++++++------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/.github/workflows/docker-master.yaml b/.github/workflows/docker-master.yaml index 1164ab5..49e96ba 100644 --- a/.github/workflows/docker-master.yaml +++ b/.github/workflows/docker-master.yaml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: python-version: [3.7, 3.8, 3.9] - poetry-version: [1.2] + poetry-version: [1.4] os: [macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: diff --git a/tests/test_processed.py b/tests/test_processed.py index 07779dd..699f6a9 100644 --- a/tests/test_processed.py +++ b/tests/test_processed.py @@ -11,10 +11,12 @@ t = contextvars.ContextVar("t", default=0) + async def sleep(ins): await asyncio.sleep(0.001) return ins + async def sleep_and_raise(ins): await asyncio.sleep(0.001) raise Exception("This is a sleep and raise exception") @@ -27,12 +29,13 @@ async def iterating(): await asyncio.sleep(0.001) yield 3 + async def iterate_and_raise(): yield 1 raise Exception("This is an iterate and raise exception") -def test_sync_context(): +def test_sync_context(): with AsyncContextManager() as c: c.aprint() @@ -43,19 +46,17 @@ async def test_async_context(): def test_sync(): - with Koil(): assert unkoil(sleep, 1) == 1, "Koil realized its async and was okay with that" - def process_func(arg: int, number: int): return arg + number def raising_process_func(arg: int, number: int): raise Exception("This is a test exception") - + def back_calling_func(arg: int, number: int): return unkoil(sleep, arg + number) @@ -65,35 +66,38 @@ def back_calling_raising_func(arg: int, number: int): return unkoil(sleep_and_raise, arg + number) - def context_vars(): - return t.get() - - + return t.get() async def test_spawn_process_func(): async with Koil(): - assert await run_processed(process_func, 1, number=2) == 3, "Process should run and return 3" + assert ( + await run_processed(process_func, 1, number=2) == 3 + ), "Process should run and return 3" async def test_spawn_process_exception_func(): async with Koil(): with pytest.raises(Exception, match="This is a test exception"): - assert await run_processed(raising_process_func, 1, number=2) == 3, "Process should run and return 3" + assert ( + await run_processed(raising_process_func, 1, number=2) == 3 + ), "Process should run and return 3" async def test_spawn_process_back_calling_func(): async with Koil(): - assert await run_processed(back_calling_func, 1, number=2) == 3, "Process should run and return 3" + assert ( + await run_processed(back_calling_func, 1, number=2) == 3 + ), "Process should run and return 3" + async def test_spawn_process_back_raise_calling_func(): async with Koil(): with pytest.raises(Exception, match="This is a sleep and raise exception"): - assert await run_processed(back_calling_raising_func, 1, number=2) == 3, "Process should run and return 3" - - - + assert ( + await run_processed(back_calling_raising_func, 1, number=2) == 3 + ), "Process should run and return 3" def process_gen(arg: int, number: int): @@ -103,7 +107,7 @@ def process_gen(arg: int, number: int): def raising_process_gen(arg: int, number: int): raise Exception("This is a test exception") - + def back_calling_gen(arg: int, number: int): for i in unkoil_gen(iterating): @@ -114,6 +118,7 @@ def back_calling_raising_gen(arg: int, number: int): for i in unkoil_gen(iterate_and_raise): yield arg + number + async def test_spawn_process_gen(): async with Koil(): async for i in iterate_processed(process_gen, 1, number=2): @@ -130,7 +135,8 @@ async def test_spawn_process_exception_gen(): async def test_spawn_process_back_calling_gen(): async with Koil(): async for i in iterate_processed(back_calling_gen, 1, number=2): - assert i == 3, "Process should run and yield 3" + assert i == 3, "Process should run and yield 3" + async def test_spawn_process_back_raise_calling_gen(): async with Koil(): @@ -139,13 +145,9 @@ async def test_spawn_process_back_raise_calling_gen(): assert i == 3, "Process should run and yield 3" - - - - async def test_context_var(): async with Koil(): t.set(1) assert await run_processed(context_vars) == 1, "Process should run and return 1" t.set(5) - assert await run_processed(context_vars) == 5, "Process should run and return 1" \ No newline at end of file + assert await run_processed(context_vars) == 5, "Process should run and return 1" From 03a43dd606b3497079eeffc689a4af2987553108 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 26 Jul 2023 15:39:21 +0200 Subject: [PATCH 10/30] up up --- .github/workflows/docker-master.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-master.yaml b/.github/workflows/docker-master.yaml index 49e96ba..9d49da8 100644 --- a/.github/workflows/docker-master.yaml +++ b/.github/workflows/docker-master.yaml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9] + python-version: [3.7, 3.8, 3.9, "3.10", "3.11"] poetry-version: [1.4] os: [macos-latest, windows-latest] runs-on: ${{ matrix.os }} From 64d288cfeea94dc01e1cbcc7f13453b17c35df35 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 26 Jul 2023 15:44:12 +0200 Subject: [PATCH 11/30] try again --- poetry.lock | 525 ++++++++++++++++++++++++------------------------- pyproject.toml | 2 +- 2 files changed, 256 insertions(+), 271 deletions(-) diff --git a/poetry.lock b/poetry.lock index 770ecfc..27ab8e6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,68 +1,41 @@ # This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. -[[package]] -name = "attrs" -version = "22.1.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=3.5" -files = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] - -[package.extras] -dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] -docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] -tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] - [[package]] name = "autoflake" -version = "1.7.7" +version = "1.7.8" description = "Removes unused imports and unused variables" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "autoflake-1.7.7-py3-none-any.whl", hash = "sha256:a9b43d08f8e455824e4f7b3f078399f59ba538ba53872f466c09e55c827773ef"}, - {file = "autoflake-1.7.7.tar.gz", hash = "sha256:c8e4fc41aa3eae0f5c94b939e3a3d50923d7a9306786a6cbf4866a077b8f6832"}, + {file = "autoflake-1.7.8-py3-none-any.whl", hash = "sha256:46373ef69b6714f5064c923bb28bd797c4f8a9497f557d87fc36665c6d956b39"}, + {file = "autoflake-1.7.8.tar.gz", hash = "sha256:e7e46372dee46fa1c97acf310d99d922b63d369718a270809d7c278d34a194cf"}, ] [package.dependencies] -pyflakes = ">=1.1.0" +pyflakes = ">=1.1.0,<3" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} [[package]] name = "black" -version = "22.10.0" +version = "22.12.0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, - {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, - {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, - {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, - {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, - {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, - {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, - {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, - {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, - {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, - {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, - {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, - {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, - {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, - {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, - {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, - {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, - {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, - {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, - {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, - {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, + {file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"}, + {file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"}, + {file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"}, + {file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"}, + {file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"}, + {file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"}, + {file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"}, + {file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"}, + {file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"}, + {file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"}, + {file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"}, + {file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"}, ] [package.dependencies] @@ -82,14 +55,14 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "click" -version = "8.1.3" +version = "8.1.6" description = "Composable command line interface toolkit" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, ] [package.dependencies] @@ -122,62 +95,72 @@ files = [ [[package]] name = "coverage" -version = "6.5.0" +version = "7.2.7" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, - {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, - {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, - {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, - {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, - {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, - {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, - {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, - {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, - {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, - {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, - {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, - {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, - {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, - {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, - {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, ] [package.dependencies] @@ -188,14 +171,14 @@ toml = ["tomli"] [[package]] name = "exceptiongroup" -version = "1.0.4" +version = "1.1.2" description = "Backport of PEP 654 (exception groups)" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, - {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, + {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, + {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, ] [package.extras] @@ -203,14 +186,14 @@ test = ["pytest (>=6)"] [[package]] name = "importlib-metadata" -version = "5.0.0" +version = "6.7.0" description = "Read metadata from Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, - {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, ] [package.dependencies] @@ -218,20 +201,20 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] @@ -251,69 +234,69 @@ typing-extensions = ">=3.7.4.3" [[package]] name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.5" files = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] name = "packaging" -version = "21.3" +version = "23.1" description = "Core utilities for Python packages" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - [[package]] name = "pathspec" -version = "0.10.2" +version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, - {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, + {file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, + {file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, ] [[package]] name = "platformdirs" -version = "2.5.4" +version = "3.9.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, - {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, + {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, + {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.6.3", markers = "python_version < \"3.8\""} + [package.extras] -docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] -test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.2.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, ] [package.dependencies] @@ -325,52 +308,52 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pydantic" -version = "1.10.2" +version = "1.10.12" description = "Data validation and settings management using python type hints" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, - {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, - {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, - {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, - {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, - {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, - {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, - {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, - {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, - {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, + {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, + {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, + {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, + {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, + {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, + {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, + {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, + {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, + {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, + {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, + {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, + {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, + {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, + {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, + {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, + {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, + {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, + {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, + {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, + {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, + {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, + {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, ] [package.dependencies] -typing-extensions = ">=4.1.0" +typing-extensions = ">=4.2.0" [package.extras] dotenv = ["python-dotenv (>=0.10.4)"] @@ -389,41 +372,26 @@ files = [ ] [[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" -optional = false -python-versions = ">=3.6.8" -files = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "PyQt5" -version = "5.15.7" +name = "pyqt5" +version = "5.15.9" description = "Python bindings for the Qt cross platform application toolkit" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "PyQt5-5.15.7-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:1a793748c60d5aff3850b7abf84d47c1d41edb11231b7d7c16bef602c36be643"}, - {file = "PyQt5-5.15.7-cp37-abi3-manylinux1_x86_64.whl", hash = "sha256:e319c9d8639e0729235c1b09c99afdadad96fa3dbd8392ab561b5ab5946ee6ef"}, - {file = "PyQt5-5.15.7-cp37-abi3-win32.whl", hash = "sha256:08694f0a4c7d4f3d36b2311b1920e6283240ad3b7c09b515e08262e195dcdf37"}, - {file = "PyQt5-5.15.7-cp37-abi3-win_amd64.whl", hash = "sha256:232fe5b135a095cbd024cf341d928fc672c963f88e6a52b0c605be8177c2fdb5"}, - {file = "PyQt5-5.15.7.tar.gz", hash = "sha256:755121a52b3a08cb07275c10ebb96576d36e320e572591db16cfdbc558101594"}, + {file = "PyQt5-5.15.9-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:883ba5c8a348be78c8be6a3d3ba014c798e679503bce00d76c666c2dc6afe828"}, + {file = "PyQt5-5.15.9-cp37-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:dd5ce10e79fbf1df29507d2daf99270f2057cdd25e4de6fbf2052b46c652e3a5"}, + {file = "PyQt5-5.15.9-cp37-abi3-win32.whl", hash = "sha256:e45c5cc15d4fd26ab5cb0e5cdba60691a3e9086411f8e3662db07a5a4222a696"}, + {file = "PyQt5-5.15.9-cp37-abi3-win_amd64.whl", hash = "sha256:e030d795df4cbbfcf4f38b18e2e119bcc9e177ef658a5094b87bb16cac0ce4c5"}, + {file = "PyQt5-5.15.9.tar.gz", hash = "sha256:dc41e8401a90dc3e2b692b411bd5492ab559ae27a27424eed4bd3915564ec4c0"}, ] [package.dependencies] -PyQt5-Qt5 = ">=5.15.0" +PyQt5-Qt5 = ">=5.15.2" PyQt5-sip = ">=12.11,<13" [[package]] -name = "PyQt5-Qt5" +name = "pyqt5-qt5" version = "5.15.2" description = "The subset of a Qt installation needed by PyQt5." category = "dev" @@ -437,50 +405,49 @@ files = [ ] [[package]] -name = "PyQt5-sip" -version = "12.11.0" +name = "pyqt5-sip" +version = "12.12.2" description = "The sip module support for PyQt5" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "PyQt5_sip-12.11.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f9e312ff8284d6dfebc5366f6f7d103f84eec23a4da0be0482403933e68660"}, - {file = "PyQt5_sip-12.11.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:4031547dfb679be309094bfa79254f5badc5ddbe66b9ad38e319d84a7d612443"}, - {file = "PyQt5_sip-12.11.0-cp310-cp310-win32.whl", hash = "sha256:ad21ca0ee8cae2a41b61fc04949dccfab6fe008749627d94e8c7078cb7a73af1"}, - {file = "PyQt5_sip-12.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:3126c84568ab341c12e46ded2230f62a9a78752a70fdab13713f89a71cd44f73"}, - {file = "PyQt5_sip-12.11.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd733667098cac70e89279d9c239106d543fb480def62a44e6366ccb8f68510b"}, - {file = "PyQt5_sip-12.11.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ec1d8ce50be76c5c1d1c86c6dc0ccacc2446172dde98b663a17871f532f9bd44"}, - {file = "PyQt5_sip-12.11.0-cp311-cp311-win32.whl", hash = "sha256:43dfe6dd409e713edeb67019b85419a7a0dc9923bfc451d6cf3778471c122532"}, - {file = "PyQt5_sip-12.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:afa4ffffc54e306669bf2b66ea37abbc56c5cdda4f3f474d20324e3634302b12"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0f77655c62ec91d47c2c99143f248624d44dd2d8a12d016e7c020508ad418aca"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ec5e9ef78852e1f96f86d7e15c9215878422b83dde36d44f1539a3062942f19c"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-win32.whl", hash = "sha256:d12b81c3a08abf7657a2ebc7d3649852a1f327eb2146ebadf45930486d32e920"}, - {file = "PyQt5_sip-12.11.0-cp37-cp37m-win_amd64.whl", hash = "sha256:b69a1911f768b489846335e31e49eb34795c6b5a038ca24d894d751e3b0b44da"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:51e377789d59196213eddf458e6927f33ba9d217b614d17d20df16c9a8b2c41c"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4e5c1559311515291ea0ab0635529f14536954e3b973a7c7890ab7e4de1c2c23"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-win32.whl", hash = "sha256:9bca450c5306890cb002fe36bbca18f979dd9e5b810b766dce8e3ce5e66ba795"}, - {file = "PyQt5_sip-12.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:f6b72035da4e8fecbb0bc4a972e30a5674a9ad5608dbddaa517e983782dbf3bf"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9356260d4feb60dbac0ab66f8a791a0d2cda1bf98c9dec8e575904a045fbf7c5"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:205f3e1b3eea3597d8e878936c1a06e04bd23a59e8b179ee806465d72eea3071"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-win32.whl", hash = "sha256:686071be054e5be6ca5aaaef7960931d4ba917277e839e2e978c7cbe3f43bb6e"}, - {file = "PyQt5_sip-12.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:42320e7a94b1085ed85d49794ed4ccfe86f1cae80b44a894db908a8aba2bc60e"}, - {file = "PyQt5_sip-12.11.0.tar.gz", hash = "sha256:b4710fd85b57edef716cc55fae45bfd5bfac6fc7ba91036f1dcc3f331ca0eb39"}, + {file = "PyQt5_sip-12.12.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1cc49c8498c34649325d53bcd243c854391f828d9bab4f2f3afd3ee3451cab72"}, + {file = "PyQt5_sip-12.12.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c8f6e7a697d0ddf754798988fae7b2a0da04f6a59fb13ae863e5d1da4b280c4f"}, + {file = "PyQt5_sip-12.12.2-cp310-cp310-win32.whl", hash = "sha256:7e572c8104e75db2c69609d195daf44c7b965dcb1c5b48e30fc376868909be56"}, + {file = "PyQt5_sip-12.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:6a65697aa0fdb66e20d7b1ef8adfacc1caf1e61655530920172bf3a2fb1148cd"}, + {file = "PyQt5_sip-12.12.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:761e018dbbc46daccdb01f8f0dcc0d055c76834d839f0343cbec4b0ecbbde512"}, + {file = "PyQt5_sip-12.12.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9d2b127ba5155bff452944b8a96ba06d7ec2161f48a2f9cc190425bfca94ab6b"}, + {file = "PyQt5_sip-12.12.2-cp311-cp311-win32.whl", hash = "sha256:26e75bc4ffd8e6b19ae96fe93dc135eb5aea03e4570724d4b3c40dbf36f3a2e6"}, + {file = "PyQt5_sip-12.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:d9548f353f17407d00f67d08c737de9f5c067352c3bdac8571492c614c2893eb"}, + {file = "PyQt5_sip-12.12.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e640b7636d86271ba8969b260e1655068b44750f20801ebc80f49a1aa737bf9"}, + {file = "PyQt5_sip-12.12.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e46d957fbeecaa1437f2dd715407b1e59e0918cc29382c7ea79784c5f3cbe0d2"}, + {file = "PyQt5_sip-12.12.2-cp37-cp37m-win32.whl", hash = "sha256:cb4523097f1ccabb95b3197a58278a40fc944b33791d3406bfa397e12303b6c6"}, + {file = "PyQt5_sip-12.12.2-cp37-cp37m-win_amd64.whl", hash = "sha256:ed04bd0065d870912c1b0a4b34b8a78698c76d77f15474c3e841b0b6dd2f429f"}, + {file = "PyQt5_sip-12.12.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:71795c177010e52109812b03ec919020461ec42a7d9d241a45fe6d708529b5a6"}, + {file = "PyQt5_sip-12.12.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:de06b6bd8241a189f729b8c093ce5dcf5928489eb7748bda28e28324e57544b0"}, + {file = "PyQt5_sip-12.12.2-cp38-cp38-win32.whl", hash = "sha256:7050ad8f94370eb7e4caa022b7e6d8b2de615e0714b557ca2098c82c0132074a"}, + {file = "PyQt5_sip-12.12.2-cp38-cp38-win_amd64.whl", hash = "sha256:67eed70427d3291e5c52c349fb4619c57c9a8810ab8d78a142c00edcbfd20d3b"}, + {file = "PyQt5_sip-12.12.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cf74db9a1542f66793ccc00e403c8c2c36c67c0cff0fb01d23fe71cc1c56c788"}, + {file = "PyQt5_sip-12.12.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:23e983119f760dc6c1a1e6cb21fd4c268d14c4ee497de6da9ce2b9d46f9779b2"}, + {file = "PyQt5_sip-12.12.2-cp39-cp39-win32.whl", hash = "sha256:a88ce85176639723f04cf5ce59157ecf3a9faca5d5dd1fe82d5ef46a3bd1d102"}, + {file = "PyQt5_sip-12.12.2-cp39-cp39-win_amd64.whl", hash = "sha256:7f13e71f5171f30d8b4176c081f0203a43e1704746b4cdaa837477945177b2a0"}, + {file = "PyQt5_sip-12.12.2.tar.gz", hash = "sha256:10d9bfa9f59f0fd1cad81be187479316ffc95684f573efea94512cb4257d2b17"}, ] [[package]] name = "pytest" -version = "7.2.0" +version = "7.4.0" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, - {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, ] [package.dependencies] -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -490,18 +457,18 @@ pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" -version = "0.20.2" +version = "0.20.3" description = "Pytest support for asyncio" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-asyncio-0.20.2.tar.gz", hash = "sha256:32a87a9836298a881c0ec637ebcc952cfe23a56436bdc0d09d1511941dd8a812"}, - {file = "pytest_asyncio-0.20.2-py3-none-any.whl", hash = "sha256:07e0abf9e6e6b95894a39f688a4a875d63c2128f76c02d03d16ccbc35bcc0f8a"}, + {file = "pytest-asyncio-0.20.3.tar.gz", hash = "sha256:83cbf01169ce3e8eb71c6c278ccb0574d1a7a3bb8eaaf5e50e0ad342afb33b36"}, + {file = "pytest_asyncio-0.20.3-py3-none-any.whl", hash = "sha256:f129998b209d04fcc65c96fc85c11e5316738358909a8399e93be553d7656442"}, ] [package.dependencies] @@ -509,18 +476,19 @@ pytest = ">=6.1.0" typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} [package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] [[package]] name = "pytest-cov" -version = "4.0.0" +version = "4.1.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, - {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, ] [package.dependencies] @@ -550,15 +518,15 @@ dev = ["pre-commit", "tox"] doc = ["sphinx", "sphinx-rtd-theme"] [[package]] -name = "QtPy" -version = "2.3.0" +name = "qtpy" +version = "2.3.1" description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "QtPy-2.3.0-py3-none-any.whl", hash = "sha256:8d6d544fc20facd27360ea189592e6135c614785f0dec0b4f083289de6beb408"}, - {file = "QtPy-2.3.0.tar.gz", hash = "sha256:0603c9c83ccc035a4717a12908bf6bc6cb22509827ea2ec0e94c2da7c9ed57c5"}, + {file = "QtPy-2.3.1-py3-none-any.whl", hash = "sha256:5193d20e0b16e4d9d3bc2c642d04d9f4e2c892590bd1b9c92bfe38a95d5a2e12"}, + {file = "QtPy-2.3.1.tar.gz", hash = "sha256:a8c74982d6d172ce124d80cafd39653df78989683f760f2281ba91a6e7b9de8b"}, ] [package.dependencies] @@ -581,48 +549,65 @@ files = [ [[package]] name = "typed-ast" -version = "1.5.4" +version = "1.5.5" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false python-versions = ">=3.6" files = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, + {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, + {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, + {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, + {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, + {file = "typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5"}, + {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c"}, + {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa"}, + {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f"}, + {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d"}, + {file = "typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5"}, + {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, + {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, + {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, + {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, + {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, ] [[package]] name = "typing-extensions" -version = "4.4.0" +version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] [[package]] @@ -658,19 +643,19 @@ test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOp [[package]] name = "zipp" -version = "3.10.0" +version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"}, - {file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"}, + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [extras] qtpy = [] @@ -679,4 +664,4 @@ uvloop = ["uvloop"] [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "eea55c94c1b4742f4b05efa23397496fa8130746bf6b8497efb0fe2ab76412b0" +content-hash = "52114623c972aa9f0fa4cffc608db468caec8a5fb9e1920347aeb8f45ca9e6cc" diff --git a/pyproject.toml b/pyproject.toml index 6a42d43..699d747 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,10 +25,10 @@ black = "^22.10.0" pytest-cov = "^4.0.0" pytest-qt = "^4.2.0" QtPy = "^2.3.0" -PyQt5 = "^5.15.7" pytest-asyncio = "^0.20.2" pydantic = "^1.10.2" autoflake = "^1.7.7" +pyqt5 = "5.15.9" [build-system] requires = ["poetry-core>=1.0.0"] From 2c771e03bbfa942f329aa34cf846ac3e361ee257 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 26 Jul 2023 15:53:37 +0200 Subject: [PATCH 12/30] fix fucking pyqt5 --- .github/workflows/docker-master.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker-master.yaml b/.github/workflows/docker-master.yaml index 9d49da8..b46f2ac 100644 --- a/.github/workflows/docker-master.yaml +++ b/.github/workflows/docker-master.yaml @@ -24,6 +24,8 @@ jobs: poetry-version: ${{ matrix.poetry-version }} - name: Run Poetry Install run: poetry install + - name: Install Qt because Qt sucks with Poetry + run: poetry run pip install pyqt5 - name: Run Tests run: poetry run pytest --cov --cov-report=xml . - name: Upload coverage to Codecov From 559be314f21add912d6a530f9e7666c756f3ee3b Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 26 Jul 2023 15:58:20 +0200 Subject: [PATCH 13/30] dd --- poetry.lock | 66 +------------------------------------------------- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 66 deletions(-) diff --git a/poetry.lock b/poetry.lock index 27ab8e6..39f935e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -371,70 +371,6 @@ files = [ {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, ] -[[package]] -name = "pyqt5" -version = "5.15.9" -description = "Python bindings for the Qt cross platform application toolkit" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "PyQt5-5.15.9-cp37-abi3-macosx_10_13_x86_64.whl", hash = "sha256:883ba5c8a348be78c8be6a3d3ba014c798e679503bce00d76c666c2dc6afe828"}, - {file = "PyQt5-5.15.9-cp37-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:dd5ce10e79fbf1df29507d2daf99270f2057cdd25e4de6fbf2052b46c652e3a5"}, - {file = "PyQt5-5.15.9-cp37-abi3-win32.whl", hash = "sha256:e45c5cc15d4fd26ab5cb0e5cdba60691a3e9086411f8e3662db07a5a4222a696"}, - {file = "PyQt5-5.15.9-cp37-abi3-win_amd64.whl", hash = "sha256:e030d795df4cbbfcf4f38b18e2e119bcc9e177ef658a5094b87bb16cac0ce4c5"}, - {file = "PyQt5-5.15.9.tar.gz", hash = "sha256:dc41e8401a90dc3e2b692b411bd5492ab559ae27a27424eed4bd3915564ec4c0"}, -] - -[package.dependencies] -PyQt5-Qt5 = ">=5.15.2" -PyQt5-sip = ">=12.11,<13" - -[[package]] -name = "pyqt5-qt5" -version = "5.15.2" -description = "The subset of a Qt installation needed by PyQt5." -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "PyQt5_Qt5-5.15.2-py3-none-macosx_10_13_intel.whl", hash = "sha256:76980cd3d7ae87e3c7a33bfebfaee84448fd650bad6840471d6cae199b56e154"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:1988f364ec8caf87a6ee5d5a3a5210d57539988bf8e84714c7d60972692e2f4a"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win32.whl", hash = "sha256:9cc7a768b1921f4b982ebc00a318ccb38578e44e45316c7a4a850e953e1dd327"}, - {file = "PyQt5_Qt5-5.15.2-py3-none-win_amd64.whl", hash = "sha256:750b78e4dba6bdf1607febedc08738e318ea09e9b10aea9ff0d73073f11f6962"}, -] - -[[package]] -name = "pyqt5-sip" -version = "12.12.2" -description = "The sip module support for PyQt5" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "PyQt5_sip-12.12.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1cc49c8498c34649325d53bcd243c854391f828d9bab4f2f3afd3ee3451cab72"}, - {file = "PyQt5_sip-12.12.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c8f6e7a697d0ddf754798988fae7b2a0da04f6a59fb13ae863e5d1da4b280c4f"}, - {file = "PyQt5_sip-12.12.2-cp310-cp310-win32.whl", hash = "sha256:7e572c8104e75db2c69609d195daf44c7b965dcb1c5b48e30fc376868909be56"}, - {file = "PyQt5_sip-12.12.2-cp310-cp310-win_amd64.whl", hash = "sha256:6a65697aa0fdb66e20d7b1ef8adfacc1caf1e61655530920172bf3a2fb1148cd"}, - {file = "PyQt5_sip-12.12.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:761e018dbbc46daccdb01f8f0dcc0d055c76834d839f0343cbec4b0ecbbde512"}, - {file = "PyQt5_sip-12.12.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9d2b127ba5155bff452944b8a96ba06d7ec2161f48a2f9cc190425bfca94ab6b"}, - {file = "PyQt5_sip-12.12.2-cp311-cp311-win32.whl", hash = "sha256:26e75bc4ffd8e6b19ae96fe93dc135eb5aea03e4570724d4b3c40dbf36f3a2e6"}, - {file = "PyQt5_sip-12.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:d9548f353f17407d00f67d08c737de9f5c067352c3bdac8571492c614c2893eb"}, - {file = "PyQt5_sip-12.12.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e640b7636d86271ba8969b260e1655068b44750f20801ebc80f49a1aa737bf9"}, - {file = "PyQt5_sip-12.12.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e46d957fbeecaa1437f2dd715407b1e59e0918cc29382c7ea79784c5f3cbe0d2"}, - {file = "PyQt5_sip-12.12.2-cp37-cp37m-win32.whl", hash = "sha256:cb4523097f1ccabb95b3197a58278a40fc944b33791d3406bfa397e12303b6c6"}, - {file = "PyQt5_sip-12.12.2-cp37-cp37m-win_amd64.whl", hash = "sha256:ed04bd0065d870912c1b0a4b34b8a78698c76d77f15474c3e841b0b6dd2f429f"}, - {file = "PyQt5_sip-12.12.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:71795c177010e52109812b03ec919020461ec42a7d9d241a45fe6d708529b5a6"}, - {file = "PyQt5_sip-12.12.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:de06b6bd8241a189f729b8c093ce5dcf5928489eb7748bda28e28324e57544b0"}, - {file = "PyQt5_sip-12.12.2-cp38-cp38-win32.whl", hash = "sha256:7050ad8f94370eb7e4caa022b7e6d8b2de615e0714b557ca2098c82c0132074a"}, - {file = "PyQt5_sip-12.12.2-cp38-cp38-win_amd64.whl", hash = "sha256:67eed70427d3291e5c52c349fb4619c57c9a8810ab8d78a142c00edcbfd20d3b"}, - {file = "PyQt5_sip-12.12.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cf74db9a1542f66793ccc00e403c8c2c36c67c0cff0fb01d23fe71cc1c56c788"}, - {file = "PyQt5_sip-12.12.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:23e983119f760dc6c1a1e6cb21fd4c268d14c4ee497de6da9ce2b9d46f9779b2"}, - {file = "PyQt5_sip-12.12.2-cp39-cp39-win32.whl", hash = "sha256:a88ce85176639723f04cf5ce59157ecf3a9faca5d5dd1fe82d5ef46a3bd1d102"}, - {file = "PyQt5_sip-12.12.2-cp39-cp39-win_amd64.whl", hash = "sha256:7f13e71f5171f30d8b4176c081f0203a43e1704746b4cdaa837477945177b2a0"}, - {file = "PyQt5_sip-12.12.2.tar.gz", hash = "sha256:10d9bfa9f59f0fd1cad81be187479316ffc95684f573efea94512cb4257d2b17"}, -] - [[package]] name = "pytest" version = "7.4.0" @@ -664,4 +600,4 @@ uvloop = ["uvloop"] [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "52114623c972aa9f0fa4cffc608db468caec8a5fb9e1920347aeb8f45ca9e6cc" +content-hash = "62b60f37e9997396390c9774d70037444cad9035c1df7c1607dbdad1b70fae93" diff --git a/pyproject.toml b/pyproject.toml index 699d747..cf1106f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,6 @@ QtPy = "^2.3.0" pytest-asyncio = "^0.20.2" pydantic = "^1.10.2" autoflake = "^1.7.7" -pyqt5 = "5.15.9" [build-system] requires = ["poetry-core>=1.0.0"] From a610a18d1355f26f005a88172d195c8d603ae567 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Thu, 27 Jul 2023 08:59:56 +0200 Subject: [PATCH 14/30] update --- .github/workflows/docker-master.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker-master.yaml b/.github/workflows/docker-master.yaml index b46f2ac..6e710bb 100644 --- a/.github/workflows/docker-master.yaml +++ b/.github/workflows/docker-master.yaml @@ -54,6 +54,8 @@ jobs: poetry-version: ${{ matrix.poetry-version }} - name: Run Poetry Install run: poetry install + - name: Install Qt because Qt sucks with Poetry + run: poetry run pip install pyqt5 - name: Run Tests env: QT_DEBUG_PLUGINS: 1 From 6e95532bbd245f046b1633d7f05eaf84013367d2 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Thu, 27 Jul 2023 09:04:50 +0200 Subject: [PATCH 15/30] again --- koil/helpers.py | 22 +++++++++++++--------- tests/test_processed.py | 8 ++++++-- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/koil/helpers.py b/koil/helpers.py index 742c7b1..71dbfe0 100644 --- a/koil/helpers.py +++ b/koil/helpers.py @@ -17,11 +17,7 @@ import contextvars import time import logging -from .process import ( - unkoil_process_gen, - unkoil_process_func, - is_in_process -) +from .process import unkoil_process_gen, unkoil_process_func, is_in_process def unkoil_gen(iterator, *args, **kwargs): @@ -330,13 +326,21 @@ def wrapper( raise e -async def run_processed(func, *args, _omit_vars=None, **kwargs, ): - async with KoiledProcess(omit_vars=_omit_vars) as p: +async def run_processed( + func, + *args, + _omit_vars=None, + _silent_errrors=True, + **kwargs, +): + async with KoiledProcess(omit_vars=_omit_vars, silent_errors=_silent_errrors) as p: return await p.call(func, *args, **kwargs) -async def iterate_processed(func, *args, _omit_vars=None, **kwargs): - async with KoiledProcess(omit_vars=_omit_vars) as p: +async def iterate_processed( + func, *args, _omit_vars=None, _silent_errrors=True, **kwargs +): + async with KoiledProcess(omit_vars=_omit_vars, silent_errors=_silent_errrors) as p: async for i in p.iter(func, *args, **kwargs): yield i diff --git a/tests/test_processed.py b/tests/test_processed.py index 699f6a9..35847dd 100644 --- a/tests/test_processed.py +++ b/tests/test_processed.py @@ -148,6 +148,10 @@ async def test_spawn_process_back_raise_calling_gen(): async def test_context_var(): async with Koil(): t.set(1) - assert await run_processed(context_vars) == 1, "Process should run and return 1" + assert ( + await run_processed(context_vars, _silent_errrors=False) == 1 + ), "Process should run and return 1" t.set(5) - assert await run_processed(context_vars) == 5, "Process should run and return 1" + assert ( + await run_processed(context_vars, _silent_errrors=False) == 5 + ), "Process should run and return 1" From 1880b1b73f33c6efe8a8b9051f11ed0bcba2ec77 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Thu, 27 Jul 2023 09:15:55 +0200 Subject: [PATCH 16/30] again --- koil/process.py | 48 +++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/koil/process.py b/koil/process.py index 4929dd1..982d402 100644 --- a/koil/process.py +++ b/koil/process.py @@ -1,7 +1,7 @@ from .vars import output_queue_context, input_queue_context, in_process_context import contextvars import multiprocessing -from .errors import ProcessCancelledError, KoilError +from .errors import ProcessCancelledError, KoilError from concurrent.futures import ThreadPoolExecutor import asyncio @@ -21,6 +21,7 @@ QUIT = 7 CALL = 8 + def is_in_process(): return in_process_context.get() @@ -33,18 +34,20 @@ def matches_suffixes(name, suffixes): def serialize_context(omit_vars=None, omit_suffixes=None, silent_errors=True): - copy = contextvars.copy_context() + copy = contextvars.copy_context() send_context = {} omit_suffixes = omit_suffixes or ["_unpickkable"] for ctx, value in copy.items(): if ctx.name in send_context: - raise KoilError(f"Context variable {ctx.name} is used multiple times. This will lead to unexpected behaviour. Please rename the variable.") + raise KoilError( + f"Context variable {ctx.name} is used multiple times. This will lead to unexpected behaviour. Please rename the variable." + ) if omit_vars and ctx.name in omit_vars: continue - + if matches_suffixes(ctx.name, omit_suffixes): continue try: @@ -53,22 +56,20 @@ def serialize_context(omit_vars=None, omit_suffixes=None, silent_errors=True): if silent_errors: continue else: - raise KoilError(f"Could not serialize context variable {ctx.name}") from e - + raise KoilError( + f"Could not serialize context variable {ctx.name}" + ) from e return send_context def deserialize_context(context): x = contextvars.copy_context() + for ctx, value in x.items(): if ctx.name in context: ctx.set(cloudpickle.loads(context[ctx.name])) - return x - - - def unkoil_process_gen(iterator, args, kwargs): input_queue = input_queue_context.get() @@ -87,7 +88,7 @@ def unkoil_process_gen(iterator, args, kwargs): raise ProcessCancelledError("Cancelled during loop back of generator") else: raise KoilError(f"Unexpected answer: {answer}") - + def unkoil_process_func(coro, args, kwargs): input_queue = input_queue_context.get() @@ -106,7 +107,6 @@ def unkoil_process_func(coro, args, kwargs): raise KoilError(f"Unexpected answer: {answer}") - def send_to_queue(queue, task, func_args_kwargs_return_exception): cloudpickle_args = cloudpickle.dumps(func_args_kwargs_return_exception) queue.put((task, cloudpickle_args)) @@ -125,8 +125,8 @@ def gen_runner(queue, gen, *args, **kwargs): for i in gen(*args, **kwargs): send_to_queue(queue, YIELD, i) -def worker(input_queue, output_queue): +def worker(input_queue, output_queue): output_queue_context.set(input_queue) input_queue_context.set(output_queue) in_process_context.set(True) @@ -136,8 +136,8 @@ def worker(input_queue, output_queue): if task == CALL: func, context, args, kwargs = func_args_kwargs_return_exception try: - ctx = deserialize_context(context) - result = ctx.run(func, *args, **kwargs) + deserialize_context(context) + result = func(*args, **kwargs) send_to_queue(output_queue, RETURN, result) except Exception as e: send_to_queue(output_queue, EXCEPTION, e) @@ -145,8 +145,8 @@ def worker(input_queue, output_queue): if task == ITER: gen, context, args, kwargs = func_args_kwargs_return_exception try: - ctx = deserialize_context(context) - ctx.run(gen_runner, output_queue, gen, *args, **kwargs) + deserialize_context(context) + gen_runner(output_queue, gen, *args, **kwargs) send_to_queue(output_queue, DONE, None) except Exception as e: send_to_queue(output_queue, EXCEPTION, e) @@ -157,11 +157,11 @@ def worker(input_queue, output_queue): raise ProcessCancelledError("from worker") from exception except Exception as e: send_to_queue(output_queue, EXCEPTION, e) - + break if task == EXCEPTION: - raise func_args_kwargs_return_exception + raise func_args_kwargs_return_exception if task == QUIT: break @@ -205,9 +205,11 @@ async def get_output(self): return await asyncio.get_event_loop().run_in_executor( self.executor, get_from_queue, self.output_queue ) - + def serialize_context(self): - return serialize_context(omit_vars=self.omit_vars, silent_errors=self.silent_errors) + return serialize_context( + omit_vars=self.omit_vars, silent_errors=self.silent_errors + ) async def call(self, func, *args, **kwargs): assert self.started, "You need to start the KoilProcess first" @@ -240,12 +242,10 @@ async def call(self, func, *args, **kwargs): async for result in func(*args, **kwargs): send_to_queue(self.input_queue, YIELD, result) - send_to_queue(self.input_queue, DONE, None) except Exception as e: send_to_queue(self.input_queue, EXCEPTION, e) - except asyncio.CancelledError as e: send_to_queue(self.input_queue, CANCEL, e) task, args = await self.get_output() @@ -290,12 +290,10 @@ async def iter(self, func, *args, **kwargs): async for result in func(*args, **kwargs): send_to_queue(self.input_queue, YIELD, result) - send_to_queue(self.input_queue, DONE, None) except Exception as e: send_to_queue(self.input_queue, EXCEPTION, e) - except asyncio.CancelledError as e: send_to_queue(self.input_queue, CANCEL, e) task, args = await self.get_output() From 380b4cc5f4953a762bc1b91cdbef2ef9b2f4a1c9 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Thu, 27 Jul 2023 09:21:04 +0200 Subject: [PATCH 17/30] with debug --- .github/workflows/docker-master.yaml | 4 ++-- koil/process.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-master.yaml b/.github/workflows/docker-master.yaml index 6e710bb..84c77dc 100644 --- a/.github/workflows/docker-master.yaml +++ b/.github/workflows/docker-master.yaml @@ -27,7 +27,7 @@ jobs: - name: Install Qt because Qt sucks with Poetry run: poetry run pip install pyqt5 - name: Run Tests - run: poetry run pytest --cov --cov-report=xml . + run: poetry run pytest --cov --cov-report=xml -s . - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 linux: @@ -60,6 +60,6 @@ jobs: env: QT_DEBUG_PLUGINS: 1 DISPLAY: ":99.0" - run: xvfb-run `which poetry` run pytest --cov --cov-report=xml . + run: xvfb-run `which poetry` run pytest --cov --cov-report=xml -s . - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 diff --git a/koil/process.py b/koil/process.py index 982d402..121cee2 100644 --- a/koil/process.py +++ b/koil/process.py @@ -65,8 +65,9 @@ def serialize_context(omit_vars=None, omit_suffixes=None, silent_errors=True): def deserialize_context(context): x = contextvars.copy_context() - + print(context) for ctx, value in x.items(): + print(ctx.name) if ctx.name in context: ctx.set(cloudpickle.loads(context[ctx.name])) From 8663f24a3c2a808f122a61aed9bc62a152bae236 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Thu, 27 Jul 2023 09:29:19 +0200 Subject: [PATCH 18/30] please --- tests/test_processed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_processed.py b/tests/test_processed.py index 35847dd..022587c 100644 --- a/tests/test_processed.py +++ b/tests/test_processed.py @@ -150,7 +150,7 @@ async def test_context_var(): t.set(1) assert ( await run_processed(context_vars, _silent_errrors=False) == 1 - ), "Process should run and return 1" + ), "Process should run and return correct context" t.set(5) assert ( await run_processed(context_vars, _silent_errrors=False) == 5 From 2a903b5ad90e0ae522548620e2f2dff0c810a6c7 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Thu, 27 Jul 2023 10:04:45 +0200 Subject: [PATCH 19/30] testing again --- koil/process.py | 5 ++++- tests/context.py | 4 ++++ tests/test_processed.py | 5 +---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/koil/process.py b/koil/process.py index 121cee2..53bc3e1 100644 --- a/koil/process.py +++ b/koil/process.py @@ -1,8 +1,9 @@ from .vars import output_queue_context, input_queue_context, in_process_context import contextvars import multiprocessing + from .errors import ProcessCancelledError, KoilError -from concurrent.futures import ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor import asyncio @@ -191,6 +192,8 @@ def __init__(self, max_workers=1, omit_vars=None, silent_errors=True): self.omit_vars = omit_vars self.silent_errors = silent_errors + self.loop = asyncio.get_event_loop() + self.loop.run_in_executor self.started = False async def __aenter__(self): diff --git a/tests/context.py b/tests/context.py index b8b2526..d49121a 100644 --- a/tests/context.py +++ b/tests/context.py @@ -1,5 +1,9 @@ import asyncio from koil.decorators import koilable, unkoilable +import contextvars + + +t = contextvars.ContextVar("t", default=0) @koilable() diff --git a/tests/test_processed.py b/tests/test_processed.py index 022587c..5754447 100644 --- a/tests/test_processed.py +++ b/tests/test_processed.py @@ -6,10 +6,7 @@ from koil.helpers import unkoil, unkoil_gen, run_processed, iterate_processed from .context import AsyncContextManager from koil import Koil -import contextvars - - -t = contextvars.ContextVar("t", default=0) +from .context import t async def sleep(ins): From f18f0918322dcea6ceca76b0714dab1406c5d39f Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 2 Aug 2023 12:20:10 +0200 Subject: [PATCH 20/30] added typehints --- koil/context.py | 28 ---------------- koil/helpers.py | 60 ++++++++++++++++++++++------------ koil/process.py | 61 +++-------------------------------- koil/vars.py | 19 ++++++++--- poetry.lock | 50 +++++++++++++++++++++++++++- pyproject.toml | 10 ++++-- tests/test_context_complex.py | 7 ++-- tests/test_multilevel.py | 58 +++++++++++++++++++++++++++++++++ tests/test_processed.py | 12 ------- 9 files changed, 176 insertions(+), 129 deletions(-) delete mode 100644 koil/context.py create mode 100644 tests/test_multilevel.py diff --git a/koil/context.py b/koil/context.py deleted file mode 100644 index b6ca5a1..0000000 --- a/koil/context.py +++ /dev/null @@ -1,28 +0,0 @@ -from contextvars import ContextVar - - -class Context: - def __init__(self, value): - self.__value: ContextVar = value - - def set(self, value): - return self.__value.set(value) - - def reset(self, token): - self.__value.reset(token) - return - - @classmethod - def __get_validators__(cls): - # one or more validators may be yielded which will be called in the - # order to validate the input, deach validator will receive as an input - # the value returned from the previous validator - # ss - yield cls.validate - - @classmethod - def validate(cls, v): - if isinstance(v, ContextVar): - return cls(v) - - raise TypeError("Needs to be either a instance of ContextVar or a string") diff --git a/koil/helpers.py b/koil/helpers.py index 71dbfe0..9088405 100644 --- a/koil/helpers.py +++ b/koil/helpers.py @@ -18,9 +18,24 @@ import time import logging from .process import unkoil_process_gen, unkoil_process_func, is_in_process +from typing import Callable, TypeVar +try: + from typing import ParamSpec +except ImportError: + from typing_extensions import ParamSpec -def unkoil_gen(iterator, *args, **kwargs): + +from typing import Coroutine, Any, Union, Optional, Awaitable, AsyncIterator, Iterator + +P = ParamSpec("P") +T = TypeVar("T") +R = TypeVar("R") + + +def unkoil_gen( + iterator: Callable[P, AsyncIterator[R]], *args: P.args, **kwargs: P.kwargs +) -> Iterator[R]: if is_in_process(): for i in unkoil_process_gen(iterator, args, kwargs): yield i @@ -80,7 +95,14 @@ async def next_on_ait(inside_args): next_args = yield obj -def unkoil(coro, *args, **kwargs): +def unkoil( + coro: Union[ + Callable[P, Coroutine[Any, Any, R]], + Callable[P, Awaitable[R]], + ], + *args: P.args, + **kwargs: P.kwargs, +) -> R: if is_in_process(): return unkoil_process_func(coro, args, kwargs) @@ -136,14 +158,14 @@ async def passed_with_context(): async def run_spawned( - sync_func, - *sync_args, + sync_func: Callable[P, R], + *sync_args: P.args, executor=None, pass_context=False, pass_loop=True, cancel_timeout=None, - **sync_kwargs, -): + **sync_kwargs: P.kwargs, +) -> R: """ Spawn a thread with a given sync function and arguments """ @@ -212,14 +234,14 @@ def wrapper(sync_args, sync_kwargs, loop, cancel_event, context): async def iterate_spawned( - sync_gen, - *sync_args, + sync_gen: Callable[P, Iterator[R]], + *sync_args: P.args, executor=None, pass_context=False, pass_loop=True, cancel_timeout=None, - **sync_kwargs, -): + **sync_kwargs: P.kwargs, +) -> R: """ Spawn a thread with a given sync function and arguments """ @@ -327,20 +349,18 @@ def wrapper( async def run_processed( - func, - *args, - _omit_vars=None, - _silent_errrors=True, - **kwargs, -): - async with KoiledProcess(omit_vars=_omit_vars, silent_errors=_silent_errrors) as p: + func: Callable[P, R], + *args: P.args, + **kwargs: P.kwargs, +) -> R: + async with KoiledProcess() as p: return await p.call(func, *args, **kwargs) async def iterate_processed( - func, *args, _omit_vars=None, _silent_errrors=True, **kwargs -): - async with KoiledProcess(omit_vars=_omit_vars, silent_errors=_silent_errrors) as p: + func: Callable[P, Iterator[R]], *args: P.args, **kwargs: P.kwargs +) -> AsyncIterator[R]: + async with KoiledProcess() as p: async for i in p.iter(func, *args, **kwargs): yield i diff --git a/koil/process.py b/koil/process.py index 53bc3e1..0640a96 100644 --- a/koil/process.py +++ b/koil/process.py @@ -34,45 +34,6 @@ def matches_suffixes(name, suffixes): return False -def serialize_context(omit_vars=None, omit_suffixes=None, silent_errors=True): - copy = contextvars.copy_context() - - send_context = {} - omit_suffixes = omit_suffixes or ["_unpickkable"] - - for ctx, value in copy.items(): - if ctx.name in send_context: - raise KoilError( - f"Context variable {ctx.name} is used multiple times. This will lead to unexpected behaviour. Please rename the variable." - ) - - if omit_vars and ctx.name in omit_vars: - continue - - if matches_suffixes(ctx.name, omit_suffixes): - continue - try: - send_context[ctx.name] = cloudpickle.dumps(value) - except Exception as e: - if silent_errors: - continue - else: - raise KoilError( - f"Could not serialize context variable {ctx.name}" - ) from e - - return send_context - - -def deserialize_context(context): - x = contextvars.copy_context() - print(context) - for ctx, value in x.items(): - print(ctx.name) - if ctx.name in context: - ctx.set(cloudpickle.loads(context[ctx.name])) - - def unkoil_process_gen(iterator, args, kwargs): input_queue = input_queue_context.get() out_queue = output_queue_context.get() @@ -136,18 +97,16 @@ def worker(input_queue, output_queue): # Wait for a task task, func_args_kwargs_return_exception = get_from_queue(input_queue) if task == CALL: - func, context, args, kwargs = func_args_kwargs_return_exception + func, args, kwargs = func_args_kwargs_return_exception try: - deserialize_context(context) result = func(*args, **kwargs) send_to_queue(output_queue, RETURN, result) except Exception as e: send_to_queue(output_queue, EXCEPTION, e) if task == ITER: - gen, context, args, kwargs = func_args_kwargs_return_exception + gen, args, kwargs = func_args_kwargs_return_exception try: - deserialize_context(context) gen_runner(output_queue, gen, *args, **kwargs) send_to_queue(output_queue, DONE, None) except Exception as e: @@ -172,7 +131,7 @@ def worker(input_queue, output_queue): class KoiledProcess: """A class that allows to call functions in a separate process.""" - def __init__(self, max_workers=1, omit_vars=None, silent_errors=True): + def __init__(self, max_workers=1): """Create a new KoiledProcess. Args: @@ -189,8 +148,6 @@ def __init__(self, max_workers=1, omit_vars=None, silent_errors=True): self.worker_process = multiprocessing.Process( target=worker, args=(self.input_queue, self.output_queue) ) - self.omit_vars = omit_vars - self.silent_errors = silent_errors self.loop = asyncio.get_event_loop() self.loop.run_in_executor @@ -210,17 +167,11 @@ async def get_output(self): self.executor, get_from_queue, self.output_queue ) - def serialize_context(self): - return serialize_context( - omit_vars=self.omit_vars, silent_errors=self.silent_errors - ) - async def call(self, func, *args, **kwargs): assert self.started, "You need to start the KoilProcess first" # Send the function and arguments to the worker process - context = self.serialize_context() try: - send_to_queue(self.input_queue, CALL, (func, context, args, kwargs)) + send_to_queue(self.input_queue, CALL, (func, args, kwargs)) # Wait for the result and then execute the callback while True: @@ -262,10 +213,8 @@ async def call(self, func, *args, **kwargs): async def iter(self, func, *args, **kwargs): assert self.started, "You need to start the KoilProcess first" # Send the function and arguments to the worker process - - context = self.serialize_context() try: - send_to_queue(self.input_queue, ITER, (func, context, args, kwargs)) + send_to_queue(self.input_queue, ITER, (func, args, kwargs)) # Wait for the result and then execute the callback while True: diff --git a/koil/vars.py b/koil/vars.py index e4f8c35..4a24c82 100644 --- a/koil/vars.py +++ b/koil/vars.py @@ -1,15 +1,25 @@ import contextvars - +import multiprocessing from koil.errors import ThreadCancelledError current_loop = contextvars.ContextVar("current_loop", default=None) + + +# Will only be set in the worker tread current_cancel_event = contextvars.ContextVar("current_cancel_event", default=None) -output_queue_context = contextvars.ContextVar("out_queue_context") -input_queue_context = contextvars.ContextVar("input_queue_context") -in_process_context = contextvars.ContextVar("in_process_context", default=False) +# Will only be set in the worker process +output_queue_context: contextvars.ContextVar[ + multiprocessing.Queue +] = contextvars.ContextVar("out_queue_context") +input_queue_context: contextvars.ContextVar[ + multiprocessing.Queue +] = contextvars.ContextVar("input_queue_context") +in_process_context: contextvars.ContextVar[bool] = contextvars.ContextVar( + "in_process_context", default=False +) def check_cancelled(): @@ -18,7 +28,6 @@ def check_cancelled(): raise ThreadCancelledError("Task was cancelled") - def get_process_queues(): """Get the process queues. diff --git a/poetry.lock b/poetry.lock index 39f935e..45f08b2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -232,6 +232,54 @@ files = [ [package.dependencies] typing-extensions = ">=3.7.4.3" +[[package]] +name = "mypy" +version = "1.4.1" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, + {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, + {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, + {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, + {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, + {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, + {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, + {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, + {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, + {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, + {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, + {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, + {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, + {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, + {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, + {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, + {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, + {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, + {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + [[package]] name = "mypy-extensions" version = "1.0.0" @@ -600,4 +648,4 @@ uvloop = ["uvloop"] [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "62b60f37e9997396390c9774d70037444cad9035c1df7c1607dbdad1b70fae93" +content-hash = "4220753ad74540b5a89569254baf0a22e2ebf8de3e78de268522d95d048c387f" diff --git a/pyproject.toml b/pyproject.toml index cf1106f..b641b88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ QtPy = "^2.3.0" pytest-asyncio = "^0.20.2" pydantic = "^1.10.2" autoflake = "^1.7.7" +mypy = "^1.4.1" [build-system] requires = ["poetry-core>=1.0.0"] @@ -36,9 +37,7 @@ build-backend = "poetry.core.masonry.api" [tool.pytest.ini_options] asyncio_mode = "auto" qt_api = "pyqt5" -markers = [ - "qt: marks tests that require a running qt application", -] +markers = ["qt: marks tests that require a running qt application"] [[tool.pydoc-markdown.loaders]] @@ -49,3 +48,8 @@ search_path = ["koil"] [tool.pydoc-markdown.renderer] type = "docusaurus" docs_base_path = "website/docs" + + +[tool.mypy] +ignore_missing_imports = true +strict = true diff --git a/tests/test_context_complex.py b/tests/test_context_complex.py index 3d7e950..fbe8639 100644 --- a/tests/test_context_complex.py +++ b/tests/test_context_complex.py @@ -32,7 +32,7 @@ class X(object): def __init__(self, x): self.x = x - def sleep_and_call(self, nana): + def sleep_and_call(self, nana) -> str: time.sleep(0.04) y = self.a(nana, as_task=True).run() check_cancelled() @@ -51,7 +51,8 @@ async def a(self, a): @unkoilable async def t(self): - return "x" + await run_spawned(self.sleep_and_call, "haha", cancel_timeout=3) + f = await run_spawned(self.sleep_and_call, "haha", cancel_timeout=3) + return "x" + f async def g(self): async for i in iterate_spawned(self.sleep_and_yield, "haha", cancel_timeout=3): @@ -65,7 +66,6 @@ async def __aexit__(self, *args, **kwargs): async def test_async(): - async with Koil(): async with X(1) as x: x = asyncio.create_task(x.t()) @@ -78,7 +78,6 @@ async def test_async(): def test_x_sync(): - with X(1) as x: l = unkoil_gen(x.g) l.send(None) diff --git a/tests/test_multilevel.py b/tests/test_multilevel.py new file mode 100644 index 0000000..a629ff9 --- /dev/null +++ b/tests/test_multilevel.py @@ -0,0 +1,58 @@ +from koil.composition import Composition +import asyncio +from pydantic import Field +from koil.composition.base import KoiledModel +from koil import Koil +from koil.helpers import unkoil +import pytest + + +class Kant(KoiledModel): + connected: bool = False + + async def __aenter__(self): + await asyncio.sleep(0.004) + + self.connected = True + return self + + async def __aexit__(self, *args, **kwargs): + self.connected = False + + +class Tan(KoiledModel): # + x: int = 3 + + async def arun(self): + await asyncio.sleep(0.02) + return self.x + + def run(self): + return unkoil(self.arun) + + async def __aenter__(self): + await asyncio.sleep(0.002) + self.x = 4 + return self + + async def __aexit__(self, *args, **kwargs): + pass + + +def test_multilevel(): + with Koil(): + with Tan() as t: + assert t.x == 4, "tan.x should be 4 because it was set in enter" + + with Kant() as k: + assert k.connected, "kant should be connected" + + +@pytest.mark.asyncio +async def test_nothing_multilevel(): + async with Koil(): + async with Tan() as t: + assert t.x == 4, "tan.x should be 4 because it was set in enter" + + async with Kant() as k: + assert k.connected, "kant should be connected" diff --git a/tests/test_processed.py b/tests/test_processed.py index 5754447..92e53d6 100644 --- a/tests/test_processed.py +++ b/tests/test_processed.py @@ -140,15 +140,3 @@ async def test_spawn_process_back_raise_calling_gen(): with pytest.raises(Exception, match="This is an iterate and raise exception"): async for i in iterate_processed(back_calling_raising_gen, 1, number=2): assert i == 3, "Process should run and yield 3" - - -async def test_context_var(): - async with Koil(): - t.set(1) - assert ( - await run_processed(context_vars, _silent_errrors=False) == 1 - ), "Process should run and return correct context" - t.set(5) - assert ( - await run_processed(context_vars, _silent_errrors=False) == 5 - ), "Process should run and return 1" From b14ddd1a424528b5bf997ad541492a0b22b3cc5c Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 2 Aug 2023 13:56:55 +0200 Subject: [PATCH 21/30] fix workflow --- .github/workflows/docker-master.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/docker-master.yaml b/.github/workflows/docker-master.yaml index 84c77dc..9b32625 100644 --- a/.github/workflows/docker-master.yaml +++ b/.github/workflows/docker-master.yaml @@ -43,11 +43,7 @@ jobs: - uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies - env: - DISPLAY: ":99.0" - run: | - sudo apt install libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 + - uses: tlambert03/setup-qt-libs@v1 - name: Run image uses: abatilo/actions-poetry@v2.0.0 with: From 8ea2c51a07e8dd8d43134a26eb850a54aa106e9a Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 2 Aug 2023 14:42:34 +0200 Subject: [PATCH 22/30] testing new --- koil/decorators.py | 18 +++++++--------- koil/koil.py | 51 +++++++++++++++++++++++----------------------- poetry.lock | 3 ++- pyproject.toml | 6 ++++-- 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/koil/decorators.py b/koil/decorators.py index c1ab043..98ac6ad 100644 --- a/koil/decorators.py +++ b/koil/decorators.py @@ -44,19 +44,15 @@ def ___get_koiled_loop(self): def koiled_enter(self, *args, **kwargs): potential_koiled_loop = current_loop.get() if potential_koiled_loop is not None: - assert ( - getattr(self, fieldname, None) is None - ), f"You cannot enter a koil loop inside another koil loop. Do no set explicitly a Koil in {cls}. Found koiled loop {potential_koiled_loop}" + return unkoil(self.__aenter__, *args, **kwargs) else: - if getattr(self, fieldname, None) is None: - setattr( - self, - fieldname, - koil_class(creating_instance=self, **koilparams), - ) + setattr( + self, + fieldname, + koil_class(name=f"{repr(self)}", **koilparams), + ) getattr(self, fieldname).__enter__() - - return unkoil(self.__aenter__, *args, **kwargs) + return unkoil(self.__aenter__, *args, **kwargs) def koiled_exit(self, *args, **kwargs): unkoil(self.__aexit__, *args, **kwargs) diff --git a/koil/koil.py b/koil/koil.py index ed464d6..e47259e 100644 --- a/koil/koil.py +++ b/koil/koil.py @@ -7,7 +7,7 @@ from typing import Any, Optional from koil.errors import ContextError -from koil.vars import * +from koil.vars import current_loop import time import logging @@ -121,20 +121,19 @@ def __enter__(self): except RuntimeError: pass - self._loop = current_loop.get() - assert ( - self._loop is None - ), f"You are already in a koiled context. You can't nest koiled contexts. Omit creating a new Koil here {self._loop.name}" - # We are now creating a koiled loop for this context - self._loop = get_threaded_loop( - getattr( - self, - "name", - f"KoiledLoop {'governed by' + self.creating_instance.__class__.__name__ if getattr(self, 'creating_instance', None) else ''}", - ), - uvify=getattr(self, "uvify", True), - ) - current_loop.set(self._loop) + self._loop = None + _loop = current_loop.get() + if _loop is None: + # We are now creating a koiled loop for this context + self._loop = get_threaded_loop( + getattr( + self, + "name", + f"KoiledLoop {'governed by' + self.__class__.__name__ if getattr(self, 'creating_instance', None) else ''}", + ), + uvify=getattr(self, "uvify", True), + ) + current_loop.set(self._loop) self.running = True return self @@ -144,25 +143,25 @@ async def __aloop_close(self): loop.stop() def __exit__(self, *args, **kwargs): - asyncio.run_coroutine_threadsafe(self.__aloop_close(), self._loop) + if self._loop: + self._loop.call_soon_threadsafe(self._loop.stop) - iterations = 0 + iterations = 0 - while self._loop.is_running(): - time.sleep(0.001) - iterations += 1 - if iterations == 100: - logger.warning( - "Shutting Down takes longer than expected. Probably we are having loose Threads? Keyboard interrupt?" - ) + while self._loop.is_running(): + time.sleep(0.001) + iterations += 1 + if iterations == 100: + logger.warning( + "Shutting Down takes longer than expected. Probably we are having loose Threads? Keyboard interrupt?" + ) - current_loop.set(None) + current_loop.set(None) self.running = False @dataclass class Koil(KoilMixin): - creating_instance: Optional[Any] = None "The instance that created this class through entering" uvify: bool = False diff --git a/poetry.lock b/poetry.lock index 45f08b2..43edcfc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -642,10 +642,11 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [extras] +process = ["cloudpickle"] qtpy = [] uvloop = ["uvloop"] [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "4220753ad74540b5a89569254baf0a22e2ebf8de3e78de268522d95d048c387f" +content-hash = "0b5ca12e264dfed9b4d01c4fd8eec5ee94815402d85630cd33d85087d00f0261" diff --git a/pyproject.toml b/pyproject.toml index b641b88..985c80e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "koil" -version = "0.2.14" +version = "0.3.3" readme = "README.md" description = "Async for a sync world" authors = ["jhnnsrs "] @@ -12,11 +12,12 @@ python = "^3.7" qtpy = { version = ">1", optional = true } uvloop = { version = "^0.16.0", optional = true } janus = "^1.0.0" -cloudpickle = "^2.2.1" +cloudpickle = { version = "^2.2.1", optional = true } [tool.poetry.extras] qtpy = ["qt"] uvloop = ["uvloop"] +process = ["cloudpickle"] [tool.poetry.group.dev.dependencies] @@ -29,6 +30,7 @@ pytest-asyncio = "^0.20.2" pydantic = "^1.10.2" autoflake = "^1.7.7" mypy = "^1.4.1" +cloudpickle = "^2.2.1" [build-system] requires = ["poetry-core>=1.0.0"] From 2a154b4a4ccc34e047d1dc076b3c178e987e1845 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 2 Aug 2023 15:51:54 +0200 Subject: [PATCH 23/30] up up --- koil/decorators.py | 4 ---- koil/koil.py | 5 ----- 2 files changed, 9 deletions(-) diff --git a/koil/decorators.py b/koil/decorators.py index 98ac6ad..622b4af 100644 --- a/koil/decorators.py +++ b/koil/decorators.py @@ -38,9 +38,6 @@ def real_cls_decorator(cls: Type[T]) -> Type[T]: cls.__aexit__ ), "__aexit__ must be a coroutine (awaitable)" - def ___get_koiled_loop(self): - return getattr(self, fieldname) - def koiled_enter(self, *args, **kwargs): potential_koiled_loop = current_loop.get() if potential_koiled_loop is not None: @@ -73,7 +70,6 @@ async def aenter(self): cls.aenter = aenter cls.exit = koiled_exit cls.enter = koiled_enter - cls.__get_koiled_loop = ___get_koiled_loop return cls diff --git a/koil/koil.py b/koil/koil.py index e47259e..4093fb3 100644 --- a/koil/koil.py +++ b/koil/koil.py @@ -137,11 +137,6 @@ def __enter__(self): self.running = True return self - async def __aloop_close(self): - loop = asyncio.get_event_loop() - logger.debug("Causing loop to stop") - loop.stop() - def __exit__(self, *args, **kwargs): if self._loop: self._loop.call_soon_threadsafe(self._loop.stop) From 624f2d24207fae79e9e0b00f9b31145dc2918141 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 2 Aug 2023 15:52:01 +0200 Subject: [PATCH 24/30] up up --- koil/qt.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/koil/qt.py b/koil/qt.py index 7357f18..40b6e5f 100644 --- a/koil/qt.py +++ b/koil/qt.py @@ -344,13 +344,3 @@ def __post_init__(self): class Config: arbitrary_types_allowed = True - - def create_task(self, coro, *args, **kwargs) -> QtFuture: - logger.warning( - """Creating a task within the qt loop is not recommended. As this might lead to deathlocks and other bad things. (Especially when calling qt back from the koil). Try to use a `create_runner` - and connect your signals to and then call the `run` method instead.""" - ) - return coro(*args, **kwargs, as_task=True).run() - - def create_runner(self, coro, *args, **kwargs) -> QtRunner: - return coro(*args, **kwargs, as_task=True) From 92653f67d66d8ae6ee3a5bd2277f99bda62d1ff2 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Thu, 17 Aug 2023 13:07:10 +0200 Subject: [PATCH 25/30] changed initialization strategy --- koil/composition/base.py | 20 +------------------- koil/decorators.py | 21 ++++++++++++++++----- koil/koil.py | 2 +- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/koil/composition/base.py b/koil/composition/base.py index 9934218..6a7070f 100644 --- a/koil/composition/base.py +++ b/koil/composition/base.py @@ -22,24 +22,6 @@ class PedanticKoil(BaseModel, KoilMixin): _token = None _loop = None - @root_validator() - @classmethod - def check_not_running_in_koil(cls, values): - if current_loop.get() is not None: - raise ValueError( - f"You are already running in a Koil Loop. You cannot run a Koil Loop inside another Koil Loop. {current_loop.get()}" - ) - try: - asyncio.get_running_loop() - if not values["sync_in_async"]: - raise ValueError( - "Please use async instead. Or set Koil to sync_in_async=True" - ) - except RuntimeError: - pass - - return values - def _repr_html_inline_(self): return f"
allow sync in async{self.sync_in_async}
uvified{self.uvify}
" @@ -50,7 +32,7 @@ class Config: @koilable(fieldname="koil", add_connectors=True, koil_class=PedanticKoil) class KoiledModel(BaseModel): - koil: Optional[PedanticKoil] + koil: PedanticKoil = Field(default_factory=PedanticKoil, exclude=True) def __enter__(self: T) -> T: ... diff --git a/koil/decorators.py b/koil/decorators.py index 622b4af..12e5724 100644 --- a/koil/decorators.py +++ b/koil/decorators.py @@ -3,6 +3,7 @@ import inspect from typing import Callable, Type, TypeVar from koil.vars import current_loop +from koil.errors import KoilError import logging T = TypeVar("T") @@ -14,6 +15,7 @@ def koilable( fieldname: str = "__koil", add_connectors: bool = False, + init_koil: bool = True, koil_class: Type[Koil] = Koil, **koilparams, ) -> Callable[[Type[T]], Type[T]]: @@ -23,6 +25,7 @@ def koilable( Args: fieldname (str, optional): The name of the field to store the koil instance. Defaults to "__koil". add_connectors (bool, optional): If True, it will add the connectors to the class. Defaults to False. + init_koil (bool, optional): If True, it will initialize the koil instance on the field if it is None. Defaults to True. koil_class (Type[Koil], optional): The class of the koil to use. Defaults to Koil. """ @@ -41,13 +44,21 @@ def real_cls_decorator(cls: Type[T]) -> Type[T]: def koiled_enter(self, *args, **kwargs): potential_koiled_loop = current_loop.get() if potential_koiled_loop is not None: + # We are in a koiled loop no need to koil again return unkoil(self.__aenter__, *args, **kwargs) else: - setattr( - self, - fieldname, - koil_class(name=f"{repr(self)}", **koilparams), - ) + if not hasattr(self, fieldname) or getattr(self, fieldname) is None: + if init_koil: + setattr( + self, + fieldname, + koil_class(name=f"{repr(self)}", **koilparams), + ) + else: + raise KoilError( + f"Does not have a koil instance on {fieldname} and init_koil is False" + ) + getattr(self, fieldname).__enter__() return unkoil(self.__aenter__, *args, **kwargs) diff --git a/koil/koil.py b/koil/koil.py index 4093fb3..7bc0210 100644 --- a/koil/koil.py +++ b/koil/koil.py @@ -76,7 +76,7 @@ async def gather(): def get_threaded_loop(name="KoilLoop", uvify=True): - """Creates a new event loop and run it in a new thread.""" + """Creates a new event loop and run it in a new threads""" with _selector_policy(uvify=uvify): newloop = asyncio.new_event_loop() From 3698428fee09754d8d1463797eef673514546e29 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Fri, 1 Sep 2023 18:31:26 +0200 Subject: [PATCH 26/30] just a push --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 985c80e..f56d7de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "koil" -version = "0.3.3" +version = "0.3.4" readme = "README.md" description = "Async for a sync world" authors = ["jhnnsrs "] From 1d6ed0c242998661a4feeb23951956fbcd5a50aa Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Mon, 18 Sep 2023 14:43:20 +0200 Subject: [PATCH 27/30] added helper funcs --- koil/composition/qt.py | 14 -------------- koil/qt.py | 28 ++++++++++++++++++++++++++-- pyproject.toml | 2 +- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/koil/composition/qt.py b/koil/composition/qt.py index ddddffb..b222109 100644 --- a/koil/composition/qt.py +++ b/koil/composition/qt.py @@ -11,20 +11,6 @@ class QtPedanticKoil(PedanticKoil, QtKoilMixin): - parent: Optional[QtWidgets.QWidget] = Field(exclude=True) - - _qobject: QtCore.QObject = None - - def create_task(self, coro, *args, **kwargs) -> QtFuture: - logger.warning( - """Creating a task within the qt loop is not recommended. As this might lead to deathlocks and other bad things. (Especially when calling qt back from the koil). Try to use a `create_runner` - and connect your signals to and then call the `run` method instead.""" - ) - return coro(*args, **kwargs, as_task=True).run() - - def create_runner(self, coro, *args, **kwargs) -> QtRunner: - return coro(*args, **kwargs, as_task=True) - class Config: underscore_attrs_are_private = True arbitrary_types_allowed = True diff --git a/koil/qt.py b/koil/qt.py index 40b6e5f..f372621 100644 --- a/koil/qt.py +++ b/koil/qt.py @@ -4,7 +4,7 @@ import logging import threading from dataclasses import dataclass -from typing import Callable, Generic, TypeVar +from typing import Any, Callable, Generic, TypeVar from qtpy import QtCore, QtWidgets from typing_extensions import ParamSpec @@ -43,9 +43,18 @@ def resolve(self, *args): if not args: args = (None,) ctx = contextvars.copy_context() + + if self.aiofuture.done(): + logger.warning(f"QtFuture {self} already done. Cannot resolve") + return + self.loop.call_soon_threadsafe(self.aiofuture.set_result, (ctx,) + args) def reject(self, exp: Exception): + if self.aiofuture.done(): + logger.warning(f"QtFuture {self} already done. Could not reject") + return + self.loop.call_soon_threadsafe(self.aiofuture.set_exception, exp) @@ -106,7 +115,7 @@ def __init__( super().__init__(*args, **kwargs) assert not inspect.iscoroutinefunction( coro - ), "This should not be a coroutine, but a normal qt slot with the first parameter being a qtfuture" + ), f"This should not be a coroutine, but a normal qt slot {'with the first parameter being a qtfuture' if autoresolve == False else ''}" self.coro = coro self.called.connect(self.on_called) self.autoresolve = autoresolve @@ -241,6 +250,9 @@ def run(self, *args: P.args, **kwargs: P.kwargs): ) return KoilFuture(future, cancel_event) + def __call__(self, *args: Any, **kwds: Any) -> Any: + return self.run(*args, **kwds) + class QtGeneratorRunner(KoilGeneratorRunner, QtCore.QObject): errored = QtCore.Signal(Exception) @@ -314,6 +326,18 @@ def async_generator_to_qt(func): return QtGeneratorRunner(func) +def async_to_qt(func): + return QtRunner(func) + + +def qt_to_async(func, autoresolve=False, use_context=True): + return QtCoro(func, autoresolve=autoresolve, use_context=use_context).acall + + +def qtgenerator_to_async(func): + return QtGenerator(func) + + class WrappedObject(QtCore.QObject): def __init__(self, *args, koil: Koil = None, **kwargs): super().__init__(*args, **kwargs) diff --git a/pyproject.toml b/pyproject.toml index f56d7de..a3f1344 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "koil" -version = "0.3.4" +version = "0.3.5" readme = "README.md" description = "Async for a sync world" authors = ["jhnnsrs "] From f08c3decdfb529b6a66d4b647f5e6ce2da14ddd3 Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 31 Jan 2024 12:00:16 +0100 Subject: [PATCH 28/30] update readme --- .github/workflows/docker-master.yaml | 27 ++- .github/workflows/quality.yaml | 83 ++++++++ README.md | 185 ++++++++++++++---- koil/composition/base.py | 10 +- koil/composition/qt.py | 6 +- koil/contrib/__init__.py | 0 koil/contrib/pytest.py | 0 koil/contrib/pytest_qt.py | 58 ++++++ koil/decorators.py | 31 ++- koil/helpers.py | 5 +- koil/koil.py | 11 +- koil/process.py | 3 +- koil/qt.py | 113 ++++++++++- koil/task.py | 7 +- koil/utils.py | 35 +++- poetry.lock | 187 ++++++------------ pyproject.toml | 7 +- tests/test_context_complex.py | 14 +- tests/test_multilevel.py | 2 - tests/test_processed.py | 9 +- tests/test_qtkoil.py | 18 +- tests/test_qtkoil_modern.py | 278 +++++++++++++++++++++++++++ tests/test_threaded.py | 2 - 23 files changed, 858 insertions(+), 233 deletions(-) create mode 100644 .github/workflows/quality.yaml create mode 100644 koil/contrib/__init__.py create mode 100644 koil/contrib/pytest.py create mode 100644 koil/contrib/pytest_qt.py create mode 100644 tests/test_qtkoil_modern.py diff --git a/.github/workflows/docker-master.yaml b/.github/workflows/docker-master.yaml index 9b32625..ea0f0ba 100644 --- a/.github/workflows/docker-master.yaml +++ b/.github/workflows/docker-master.yaml @@ -2,15 +2,17 @@ name: CI on: push: branches: [master] + paths: [rath/**, tests/**] pull_request: branches: [master] + paths: [rath/**, tests/**] jobs: mac_and_windows: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9, "3.10", "3.11"] - poetry-version: [1.4] + python-version: [3.8, 3.9, "3.10", "3.11"] + poetry-version: [1.7] os: [macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -18,6 +20,7 @@ jobs: - uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - uses: tlambert03/setup-qt-libs@v1 - name: Run image uses: abatilo/actions-poetry@v2.0.0 with: @@ -27,16 +30,16 @@ jobs: - name: Install Qt because Qt sucks with Poetry run: poetry run pip install pyqt5 - name: Run Tests - run: poetry run pytest --cov --cov-report=xml -s . - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 - linux: + run: poetry run pytest --cov --cov-report=xml -k "not process" + linux_integration: strategy: fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9] - poetry-version: [1.4] + python-version: [3.8, 3.9, "3.10", "3.11"] + poetry-version: [1.7] os: [ubuntu-latest] + env: + DISPLAY: ':99.0' runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 @@ -44,8 +47,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - uses: tlambert03/setup-qt-libs@v1 - - name: Run image - uses: abatilo/actions-poetry@v2.0.0 + - uses: abatilo/actions-poetry@v2.0.0 with: poetry-version: ${{ matrix.poetry-version }} - name: Run Poetry Install @@ -53,9 +55,6 @@ jobs: - name: Install Qt because Qt sucks with Poetry run: poetry run pip install pyqt5 - name: Run Tests - env: - QT_DEBUG_PLUGINS: 1 - DISPLAY: ":99.0" - run: xvfb-run `which poetry` run pytest --cov --cov-report=xml -s . + run: poetry run pytest --cov --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml new file mode 100644 index 0000000..231b0b9 --- /dev/null +++ b/.github/workflows/quality.yaml @@ -0,0 +1,83 @@ +name: Python Code Quality and Style Check +on: + push: + branches: [master] + paths: [koil/**, tests/**] + pull_request: + branches: [master] + paths: [koil/**, tests/**] +jobs: + ruff: + strategy: + fail-fast: false + matrix: + python-version: ["3.11"] + poetry-version: [1.7] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - uses: tlambert03/setup-qt-libs@v1 + - name: Run image + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: Run Poetry Install + run: poetry install --all-extras + - name: Install Qt because Qt sucks with Poetry + run: poetry run pip install pyqt5 pyqtwebengine + - name: Run Ruff + run: poetry run ruff . + mypy: + strategy: + fail-fast: false + matrix: + python-version: ["3.11"] + poetry-version: [1.7] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - uses: tlambert03/setup-qt-libs@v1 + - name: Run image + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: Run Poetry Install + run: poetry install --all-extras + - name: Install Qt because Qt sucks with Poetry + run: poetry run pip install pyqt5 pyqtwebengine + - name: Run Mypy + run: poetry run mypy . + black: + strategy: + fail-fast: false + matrix: + python-version: ["3.11"] + poetry-version: [1.4] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - uses: tlambert03/setup-qt-libs@v1 + - name: Run image + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: Run Poetry Install + run: poetry install --all-extras + - name: Install Qt because Qt sucks with Poetry + run: poetry run pip install pyqt5 pyqtwebengine + - name: Run black + run: poetry run black --check . + \ No newline at end of file diff --git a/README.md b/README.md index 7c24b31..7156483 100644 --- a/README.md +++ b/README.md @@ -7,24 +7,28 @@ [![PyPI status](https://img.shields.io/pypi/status/koil.svg)](https://pypi.python.org/pypi/koil/) [![PyPI download day](https://img.shields.io/pypi/dm/koil.svg)](https://pypi.python.org/pypi/koil/) -### DEVELOPMENT - -# Quick Start - -Let's discover **Koil in less than 5 minutes**. ### Inspiration koil is an abstraction layer for threaded asyncio to enable "sensible defaults" for programmers working with frameworks that are barely compatible with asyncio (originally developped to get around pyqt5) +### Installation + +```bash +pip install koil +``` + + ### Main Concept Async libraries are amazing, and its an ecosystem rapidly increasing, however in some contexts it still doesn't seem like the way to go and the burden of learning these concepts might be to high. However you developed a wonderful async api -that you want to share with the world. +that you want to share with the world. Of couse because you care about cleaning up your resources you made it a context. ```python + + class AmazingAsyncAPI: def __init__(self) -> None: pass @@ -33,6 +37,11 @@ class AmazingAsyncAPI: await asyncio.sleep(0.01) return "the-glory-of-async" + async def yielding_sleeper(self): + for i in range(0, 20): + await asyncio.sleep(0.01) + yield i + async def __aenter__(self): # amazing connection logic return self @@ -43,21 +52,48 @@ class AmazingAsyncAPI: ``` -However if somebody wants to use this api in sync environment they are in for a good one, as a call to asyncio.run() just wont do the trick. +However if somebody wants to use this api in sync environment they are in for a good one, as a call to asyncio.run() just won't do the trick. +And you will have to write a lot of boilerplate code to make it work. And once you start trying to use the yielding_sleeper +you will be in for a good one. + +```python + +async def the_annoying_wrapper(): + + async with AmazingAsyncAPI() as e: + print(await e.sleep()) # easy enough + + # How do I use the output of the yielding + # sleeper? Queues? Another thread? + + +asyncio.run(the_annoying_wrapper()) + +``` + +Well koil is here to help. Just mark your class with koilable and the functions that you want to be able to call from +a sync context with unkoilable. + ```python from koil import koilable, unkoilable -@koilable() +@koilable class AmazingAsyncAPI: def __init__(self) -> None: pass - @unkoilable() + @unkoilable async def sleep(self): await asyncio.sleep(0.01) return "the-glory-of-async" + @unkoilable + async def yielding_sleeper(self): + for i in range(0, 20): + await asyncio.sleep(0.01) + yield i + async def __aenter__(self): # amazing connection logic return self @@ -73,13 +109,27 @@ And now it works. Just use your Api with a normal context manager. ```python with AmazingAsyncAPI as e: print(e.sleep()) -``` + + for i in e.yielding_sleeper(): + print(i) + +# Context manager is closed and cleaned up +```> + + +## How does it work? Koil under the hood spawns a new event loop in another thread, calls functions that are marked with unkoilable threadsafe in that loop and returns the result, when exiting it shuts down the loop in the other thread. -If you have multiple context managers or tasks that you would just like to run in another thread, you can -also create a loop in another thread +### Other usages + +If you have multiple context managers or tasks that you would just like to run in another thread, we do *not* +spawn a new thread for each of them, but rather use the same thread for all of them. This is to avoid the overhead +of spawning a new thread for each context manager. On the asyncio side, all tasks will be in the same loop, so +you can use asyncio primitives to communicate between them. + +You can also just use Koil as a threaded event loop, and use the unkoil function to run functions in that loop. ```python @@ -96,49 +146,110 @@ with Koil(): # creates a threaded loop ``` -Moreover koil also is able to be used with generators +Importantly, Koil also provides primites to run sync functions (in another thread, and experimentally in another process) +and await them in the koil loop. This is useful if you have a sync function that you want to use in an async context. ```python -import asyncio -from koil import unkoil_gen +from koil.helpers import run_spawned, iterate_spawned +import time -async def task(arg): - for i in range(0,20) - await asyncio.sleep(1) - yield arg +def sync_function(arg): + return arg +def sync_generator(arg): + for i in range(0, arg): + time.sleep(1) + yield i -with Koil(): # creates a threaded loop +async def run_async(): + x = await run_spawned(sync_function, 1) + + async for i in iterate_spawned(sync_generator, 1): + print(i) - for x in unkoil_gen(task, 1): - print(x) + return x + +with Koil(): + x = unkoil(run_async) ``` -And finally koil is able to create task like objects, +THis allows you to use async primitives to communicate with sync functions. Note that this is not a good idea +if you have a lot of sync functions, as the overhead of spawning a new thread for each of them is quite high. +You can however pass a threadpool to the run_spawned function to avoid this overhead. -```python -async def task(arg): - await asyncio.sleep(2) - return arg -with Koil(): +## Task Support - x = unkoil(task, 1, as_task=True) +Sometimes you want to run a task in the background and just get the result when you need it. Can't Koil do that? +Well it could, but we belive this is a bad idea. You should have that in the async world. However you can +browser our code and use our deprecated functions to do that. We just don't think its a good idea. - # do other stuff +## PyQt Support - if x.done(): - print(x) +One of the main reasons for koil was to get around the fact that PyQt5 is not asyncio compatible, and all the### Installation +```bash +pip install koil ``` -## PyQt Support -... Documentation coming soon... +```python +from koil.qt import create_qt_koil, koilqt, unkoilqt + +class KoiledInterferingFutureWidget(QtWidgets.QWidget): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.koil = create_qt_koil(parent=self) + # important to set parent, helps with cleanup + # once the widget is destroyed. Loop starts automatically -### Installation + self.ado_me = koilqt(self.in_qt_task, autoresolve=True) + # We can make qt functions callable from the async loop + + self.loop_runner = unkoilqt(self.ado_stuff_in_loop) + self.loop_runner.returned.connect(self.task_finished) + + self.call_task_button = QtWidgets.QPushButton("Call Task") + self.greet_label = QtWidgets.QLabel("") + + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.call_task_button) + layout.addWidget(self.greet_label) + + self.setLayout(layout) + + self.call_task_button.clicked.connect(self.my_coro_task.run) + + def in_qt_task(self, future: QtFuture): + """This is a task that is run in the Qt Main Thread""" + # We can do stuff in the Qt Thread + # We can resolve the future at any time + # self.on_going_future = future + + future.resolve("called") + + def task_finished(self): + """This is called when the task is finished. Main thread""" + self.greet_label.setText("Hello!") + + async def ado_stuff_in_loop(self): + """This is a coroutine that is run in the threaded loop""" + x = await self.ado_me() + self.coroutine_was_run = True + + +def main(): + app = QtWidgets.QApplication(sys.argv) + widget = KoiledInterferingFutureWidget() + widget.show() + sys.exit(app.exec_()) + +if __name__ == "__main__": + main() -```bash -pip install koil ``` + + + + diff --git a/koil/composition/base.py b/koil/composition/base.py index 6a7070f..d960381 100644 --- a/koil/composition/base.py +++ b/koil/composition/base.py @@ -1,11 +1,7 @@ -import asyncio -from pydantic import BaseModel, Field, root_validator -from dataclasses import field +from pydantic import BaseModel, Field from koil.decorators import koilable -from typing import Optional, TypeVar -from koil.vars import * -from koil.errors import * -from koil.koil import * +from typing import Optional, TypeVar, Any +from koil.koil import KoilMixin T = TypeVar("T") diff --git a/koil/composition/qt.py b/koil/composition/qt.py index b222109..b5b76f5 100644 --- a/koil/composition/qt.py +++ b/koil/composition/qt.py @@ -1,8 +1,7 @@ -from pydantic import Field from koil.composition.base import PedanticKoil from typing import Optional -from koil.qt import QtFuture, QtKoilMixin, QtRunner +from koil.qt import QtKoilMixin from qtpy import QtWidgets, QtCore import logging @@ -11,6 +10,9 @@ class QtPedanticKoil(PedanticKoil, QtKoilMixin): + parent: Optional[QtWidgets.QWidget] = None + _qobject: Optional[QtCore.QObject] = None + class Config: underscore_attrs_are_private = True arbitrary_types_allowed = True diff --git a/koil/contrib/__init__.py b/koil/contrib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/koil/contrib/pytest.py b/koil/contrib/pytest.py new file mode 100644 index 0000000..e69de29 diff --git a/koil/contrib/pytest_qt.py b/koil/contrib/pytest_qt.py new file mode 100644 index 0000000..44b45ce --- /dev/null +++ b/koil/contrib/pytest_qt.py @@ -0,0 +1,58 @@ +from typing import Callable +from koil.qt import QtRunner +from pytestqt.qtbot import QtBot + + +def wait_for_qttask(qtbot: QtBot, task: QtRunner, cause: Callable[[QtBot], None]): + """A helper function that waits for a QtRunner task to finish. + + This function will utilize the pytest-qt qtbot fixture to wait for a QtRunner + task to finish. + It will return the value of the task, or raise an exception if the task errored. + + Parameters + ---------- + qtbot : QtBot + The qtbot fixture from pytest-qt + task : QtRunner + The task to wait for + cause : Callable[[QtBot], None] + A function that will cause the task to run + + Raises + ------ + Exception + The exception that the task raised + + Returns + ------- + Any + The value that the task returned + + + + """ + out = ("MAGIC_NOTHINGNESS_WORD",) + + def callback(*value): + nonlocal out + out = value + + task.returned.connect(callback) + task.errored.connect(callback) + + with qtbot.waitSignal(task.returned) as blocker: + blocker.connect(task.errored) + cause(qtbot) + + if len(out) == 0: + raise RuntimeError("Task did not return a value.") + + if isinstance(out[0], Exception): + raise out[0] + elif out[0] == "MAGIC_NOTHINGNESS_WORD": + raise RuntimeError("Task did not return a value.") + else: + if len(out) == 1: + return out[0] + return out diff --git a/koil/decorators.py b/koil/decorators.py index 12e5724..d7aac97 100644 --- a/koil/decorators.py +++ b/koil/decorators.py @@ -1,10 +1,11 @@ -from koil.helpers import unkoil +from koil.helpers import unkoil, unkoil_gen from koil.koil import Koil import inspect from typing import Callable, Type, TypeVar from koil.vars import current_loop from koil.errors import KoilError import logging +from .utils import check_is_asyncfunc, check_is_asyncgen T = TypeVar("T") @@ -88,7 +89,29 @@ async def aenter(self): def unkoilable(func): - def wrapper(*args, **kwargs): - return unkoil(func, *args, **kwargs) + """Decorator to make a function unkoilable - return wrapper + Args: + func (Callable): The function to run in the koil loop + + Raises: + TypeError: If the function is not a coroutine + + """ + + if not check_is_asyncgen(func) and not check_is_asyncfunc(func): + raise TypeError("Function must be a coroutine") + + if check_is_asyncgen(func): + + def wrapper(*args, **kwargs): + return unkoil_gen(func, *args, **kwargs) + + return wrapper + + else: + + def wrapper(*args, **kwargs): + return unkoil(func, *args, **kwargs) + + return wrapper diff --git a/koil/helpers.py b/koil/helpers.py index 9088405..518c573 100644 --- a/koil/helpers.py +++ b/koil/helpers.py @@ -6,7 +6,6 @@ KoilError, KoilStopIteration, ThreadCancelledError, - ProcessCancelledError, ) from koil.task import KoilFuture, KoilRunner from koil.utils import run_threaded_with_context @@ -26,7 +25,7 @@ from typing_extensions import ParamSpec -from typing import Coroutine, Any, Union, Optional, Awaitable, AsyncIterator, Iterator +from typing import Coroutine, Any, Union, Awaitable, AsyncIterator, Iterator P = ParamSpec("P") T = TypeVar("T") @@ -292,7 +291,7 @@ def wrapper( sync_yield_queue.put(res) args = sync_next_queue.get() sync_next_queue.task_done() - except StopIteration as e: + except StopIteration: raise KoilStopIteration("Thread stopped") except Exception as e: logging.info("Exception in generator", exc_info=True) diff --git a/koil/koil.py b/koil/koil.py index 7bc0210..f8ece02 100644 --- a/koil/koil.py +++ b/koil/koil.py @@ -4,7 +4,6 @@ import os import sys import threading -from typing import Any, Optional from koil.errors import ContextError from koil.vars import current_loop @@ -16,7 +15,7 @@ try: import uvloop -except: +except ImportError: uvloop = None @@ -93,9 +92,6 @@ class KoilMixin: def exit(self): return self.__exit__(None, None, None) - async def aexit(self): - return await self.__aexit__(None, None, None) - async def aenter(self): return await self.__aenter__() @@ -116,7 +112,10 @@ def __enter__(self): asyncio.get_running_loop() if not hasattr(self, "sync_in_async") or self.sync_in_async is False: raise ContextError( - "You are running in asyncio event loop already. Using koil makes no sense here, use asyncio instead. If this happens in a context manager, you probably forgot to use the `async with` syntax." + f"""You are running in asyncio event loop already. + Using koil makes no sense here, use asyncio instead. You can use koil in a sync context by setting `sync_in_async=True` currently it is + set to {getattr(self, "sync_in_async", None)}. + If this happens in a context manager, you probably forgot to use the `async with` syntax.""" ) except RuntimeError: pass diff --git a/koil/process.py b/koil/process.py index 0640a96..c7f5088 100644 --- a/koil/process.py +++ b/koil/process.py @@ -1,9 +1,8 @@ from .vars import output_queue_context, input_queue_context, in_process_context -import contextvars import multiprocessing from .errors import ProcessCancelledError, KoilError -from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor +from concurrent.futures import ThreadPoolExecutor import asyncio diff --git a/koil/qt.py b/koil/qt.py index f372621..b3a6fb0 100644 --- a/koil/qt.py +++ b/koil/qt.py @@ -4,11 +4,10 @@ import logging import threading from dataclasses import dataclass -from typing import Any, Callable, Generic, TypeVar +from typing import Any, Callable, Generic, TypeVar, Awaitable from qtpy import QtCore, QtWidgets from typing_extensions import ParamSpec -from functools import partial from koil.koil import Koil, KoilMixin from koil.task import KoilFuture, KoilGeneratorRunner, KoilRunner, KoilYieldFuture from koil.utils import ( @@ -17,6 +16,8 @@ ) from koil.vars import current_loop import uuid +from typing import Protocol +from .utils import check_is_asyncfunc, check_is_asyncgen, check_is_syncgen logger = logging.getLogger(__name__) @@ -115,7 +116,7 @@ def __init__( super().__init__(*args, **kwargs) assert not inspect.iscoroutinefunction( coro - ), f"This should not be a coroutine, but a normal qt slot {'with the first parameter being a qtfuture' if autoresolve == False else ''}" + ), f"This should not be a coroutine, but a normal qt slot {'with the first parameter being a qtfuture' if autoresolve is False else ''}" self.coro = coro self.called.connect(self.on_called) self.autoresolve = autoresolve @@ -338,6 +339,105 @@ def qtgenerator_to_async(func): return QtGenerator(func) +class UnkoiledQt(Protocol): + errored: QtCore.Signal + cancelled: QtCore.Signal() + yielded: QtCore.Signal + done: QtCore.Signal + returned: QtCore.Signal + + def run(self, *args, **kwargs) -> KoilFuture: + """Runs the function in the governing loop and returns a KoilFuture + + This is useful if you want to cancel the function from the outside. + The function will be run in the governing loop and the result will be + send to the main thread via a QtSignal. + + Args: + *args: The arguments for the function + **kwargs: The keyword arguments for the function + + Returns: + KoilFuture: The future that can be cancelled + + """ + ... + + +class KoilQt(Protocol): + errored: QtCore.Signal + cancelled: QtCore.Signal() + yielded: QtCore.Signal + done: QtCore.Signal + returned: QtCore.Signal + + def run(self, *args, **kwargs) -> KoilFuture: + """Runs the function in the governing loop and returns a KoilFuture + + This is useful if you want to cancel the function from the outside. + The function will be run in the governing loop and the result will be + send to the main thread via a QtSignal. + + Args: + *args: The arguments for the function + **kwargs: The keyword arguments for the function + + Returns: + KoilFuture: The future that can be cancelled + + """ + ... + + +def unkoilqt(func, *args, **kwargs) -> UnkoiledQt: + """Unkoils a function so that it can be run in the main thread + + Args: + func (Callable): The function to run in the main thread + + + + """ + + if not (check_is_asyncgen(func) or check_is_asyncfunc(func)): + raise TypeError(f"{func} is not an async function") + + if check_is_asyncgen(func): + return async_generator_to_qt(func, *args, **kwargs) + + else: + return async_to_qt(func, *args, **kwargs) + + +def koilqt(func, *args, autoresolve=None, **kwargs) -> Callable[..., Awaitable[Any]]: + """Converts a qt mainthread function to be run in the asyncio loop + + Args: + func (Callable): The function to run in the main thread (can also + be a generator) + + Returns: + Callable[..., Awaitable[Any]]: The function that can be run in the + asyncio loop + + """ + + if check_is_asyncgen(func) or check_is_asyncfunc(func): + raise TypeError( + f"{func} should NOT be a coroutine function. This is a decorator to convert a function to be callable form the asyncio loop" + ) + + if check_is_syncgen(func): + if autoresolve is not None or autoresolve is True: + raise TypeError("Cannot autoresolve a generator") + return qtgenerator_to_async(func, *args, **kwargs) + + else: + if autoresolve is None: + autoresolve = True + return qt_to_async(func, *args, autoresolve=autoresolve, **kwargs) + + class WrappedObject(QtCore.QObject): def __init__(self, *args, koil: Koil = None, **kwargs): super().__init__(*args, **kwargs) @@ -368,3 +468,10 @@ def __post_init__(self): class Config: arbitrary_types_allowed = True + + +def create_qt_koil(parent, auto_enter: bool = True) -> QtKoil: + koil = QtKoil(parent=parent) + if auto_enter: + koil.enter() + return koil diff --git a/koil/task.py b/koil/task.py index 1e499a7..5c81597 100644 --- a/koil/task.py +++ b/koil/task.py @@ -42,7 +42,7 @@ def cancel(self, wait=False): if wait: try: - done = self.future.result() + self.future.result() raise RuntimeError("Task was cancelled but returned a result") except CancelledError: return True @@ -144,7 +144,6 @@ def run(self, *args: P.args, **kwargs: P.kwargs): assert loop is not None, "No koiled loop found" assert loop.is_running(), "Loop is not running" assert not loop.is_closed(), "Loop is closed" - ait = self.iterator(*self.args, **self.kwargs).__aiter__() - res = [False, False] - cancel_event = current_cancel_event.get() + self.iterator(*self.args, **self.kwargs).__aiter__() + current_cancel_event.get() raise NotImplementedError("No design decision was taken") diff --git a/koil/utils.py b/koil/utils.py index 25b5116..bc2df6a 100644 --- a/koil/utils.py +++ b/koil/utils.py @@ -4,7 +4,40 @@ import threading from typing import Any, Callable from koil.errors import CancelledError -import asyncio + +import inspect + + +def check_is_asyncgen(func) -> bool: + """Checks if a function is an async generator""" + if inspect.isasyncgenfunction(func): + return True + + return False + + +def check_is_asyncfunc(func) -> bool: + """Checks if a function is an async function""" + if inspect.iscoroutinefunction(func): + return True + + return False + + +def check_is_syncgen(func) -> bool: + """Checks if a function is an async generator""" + if inspect.isgeneratorfunction(func): + return True + + return False + + +def check_is_syncfunc(func) -> bool: + """Checks if a function is an async function""" + if inspect.isfunction(func): + return True + + return False async def check_event(event: threading.Event): diff --git a/poetry.lock b/poetry.lock index 43edcfc..401508b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -44,7 +44,6 @@ mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} -typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] @@ -67,7 +66,6 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] name = "cloudpickle" @@ -184,27 +182,6 @@ files = [ [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "importlib-metadata" -version = "6.7.0" -description = "Read metadata from Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, - {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, -] - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -234,50 +211,50 @@ typing-extensions = ">=3.7.4.3" [[package]] name = "mypy" -version = "1.4.1" +version = "1.8.0" description = "Optional static typing for Python" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, - {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, - {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, - {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, - {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, - {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, - {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, - {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, - {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, - {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, - {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, - {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, - {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, - {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, - {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, - {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, - {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, - {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, - {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, - {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, + {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, + {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, + {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, + {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, + {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, + {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, + {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, + {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, + {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, + {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, + {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, + {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, + {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, + {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, + {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, + {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, + {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, ] [package.dependencies] mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] +mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] @@ -328,9 +305,6 @@ files = [ {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, ] -[package.dependencies] -typing-extensions = {version = ">=4.6.3", markers = "python_version < \"3.8\""} - [package.extras] docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] @@ -347,9 +321,6 @@ files = [ {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, ] -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] @@ -434,7 +405,6 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" @@ -457,7 +427,6 @@ files = [ [package.dependencies] pytest = ">=6.1.0" -typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] @@ -520,66 +489,42 @@ packaging = "*" test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] [[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" +name = "ruff" +version = "0.1.15" +description = "An extremely fast Python linter and code formatter, written in Rust." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447"}, + {file = "ruff-0.1.15-py3-none-win32.whl", hash = "sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f"}, + {file = "ruff-0.1.15-py3-none-win_amd64.whl", hash = "sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587"}, + {file = "ruff-0.1.15-py3-none-win_arm64.whl", hash = "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360"}, + {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, ] [[package]] -name = "typed-ast" -version = "1.5.5" -description = "a fork of Python 2 and 3 ast modules with type comment support" +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, - {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, - {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, - {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, - {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, - {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, - {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, - {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, - {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, - {file = "typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5"}, - {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c"}, - {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa"}, - {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f"}, - {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d"}, - {file = "typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5"}, - {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, - {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, - {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, - {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, - {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, - {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, - {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, - {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, - {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, - {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, - {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, - {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, - {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] @@ -625,22 +570,6 @@ dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flak docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"] -[[package]] -name = "zipp" -version = "3.15.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, - {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - [extras] process = ["cloudpickle"] qtpy = [] @@ -648,5 +577,5 @@ uvloop = ["uvloop"] [metadata] lock-version = "2.0" -python-versions = "^3.7" -content-hash = "0b5ca12e264dfed9b4d01c4fd8eec5ee94815402d85630cd33d85087d00f0261" +python-versions = "^3.8" +content-hash = "2fb22c03a1e3b394784ca9728c5589138ab1eb1fb15fbf84953c7aa95afd70e7" diff --git a/pyproject.toml b/pyproject.toml index a3f1344..2d30257 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ license = "CC BY-NC 3.0" packages = [{ include = "koil" }] [tool.poetry.dependencies] -python = "^3.7" +python = "^3.8" qtpy = { version = ">1", optional = true } uvloop = { version = "^0.16.0", optional = true } janus = "^1.0.0" @@ -29,8 +29,9 @@ QtPy = "^2.3.0" pytest-asyncio = "^0.20.2" pydantic = "^1.10.2" autoflake = "^1.7.7" -mypy = "^1.4.1" cloudpickle = "^2.2.1" +mypy = "^1.8.0" +ruff = "^0.1.15" [build-system] requires = ["poetry-core>=1.0.0"] @@ -39,7 +40,7 @@ build-backend = "poetry.core.masonry.api" [tool.pytest.ini_options] asyncio_mode = "auto" qt_api = "pyqt5" -markers = ["qt: marks tests that require a running qt application"] +markers = ["qt: marks tests that require a running qt application", "process: marks tests that require the process extra"] [[tool.pydoc-markdown.loaders]] diff --git a/tests/test_context_complex.py b/tests/test_context_complex.py index fbe8639..099e216 100644 --- a/tests/test_context_complex.py +++ b/tests/test_context_complex.py @@ -43,7 +43,7 @@ def sleep_and_yield(self, nana): for i in range(2): a = self.a("v") check_cancelled() - t = yield a + yield a @unkoilable async def a(self, a): @@ -56,7 +56,7 @@ async def t(self): async def g(self): async for i in iterate_spawned(self.sleep_and_yield, "haha", cancel_timeout=3): - x = yield i + "33" + yield i + "33" async def __aenter__(self): return self @@ -73,16 +73,16 @@ async def test_async(): x.cancel() try: x = await x - except asyncio.CancelledError as e: + except asyncio.CancelledError: pass def test_x_sync(): with X(1) as x: - l = unkoil_gen(x.g) - l.send(None) - l.send(None) + sender = unkoil_gen(x.g) + sender.send(None) + sender.send(None) try: - l.send(None) + sender.send(None) except StopIteration: pass diff --git a/tests/test_multilevel.py b/tests/test_multilevel.py index a629ff9..ecfcd33 100644 --- a/tests/test_multilevel.py +++ b/tests/test_multilevel.py @@ -1,6 +1,4 @@ -from koil.composition import Composition import asyncio -from pydantic import Field from koil.composition.base import KoiledModel from koil import Koil from koil.helpers import unkoil diff --git a/tests/test_processed.py b/tests/test_processed.py index 92e53d6..474937e 100644 --- a/tests/test_processed.py +++ b/tests/test_processed.py @@ -1,7 +1,6 @@ import asyncio import pytest -from koil.errors import ContextError from koil.helpers import unkoil, unkoil_gen, run_processed, iterate_processed from .context import AsyncContextManager @@ -67,6 +66,7 @@ def context_vars(): return t.get() +@pytest.mark.process async def test_spawn_process_func(): async with Koil(): assert ( @@ -74,6 +74,7 @@ async def test_spawn_process_func(): ), "Process should run and return 3" +@pytest.mark.process async def test_spawn_process_exception_func(): async with Koil(): with pytest.raises(Exception, match="This is a test exception"): @@ -82,6 +83,7 @@ async def test_spawn_process_exception_func(): ), "Process should run and return 3" +@pytest.mark.process async def test_spawn_process_back_calling_func(): async with Koil(): assert ( @@ -89,6 +91,7 @@ async def test_spawn_process_back_calling_func(): ), "Process should run and return 3" +@pytest.mark.process async def test_spawn_process_back_raise_calling_func(): async with Koil(): with pytest.raises(Exception, match="This is a sleep and raise exception"): @@ -116,12 +119,14 @@ def back_calling_raising_gen(arg: int, number: int): yield arg + number +@pytest.mark.process async def test_spawn_process_gen(): async with Koil(): async for i in iterate_processed(process_gen, 1, number=2): assert i == 3, "Process should run and yield 3" +@pytest.mark.process async def test_spawn_process_exception_gen(): async with Koil(): with pytest.raises(Exception, match="This is a test exception"): @@ -129,12 +134,14 @@ async def test_spawn_process_exception_gen(): assert i == 3, "Process should run and yield 3" +@pytest.mark.process async def test_spawn_process_back_calling_gen(): async with Koil(): async for i in iterate_processed(back_calling_gen, 1, number=2): assert i == 3, "Process should run and yield 3" +@pytest.mark.process async def test_spawn_process_back_raise_calling_gen(): async with Koil(): with pytest.raises(Exception, match="This is an iterate and raise exception"): diff --git a/tests/test_qtkoil.py b/tests/test_qtkoil.py index 6af732f..bcb7c60 100644 --- a/tests/test_qtkoil.py +++ b/tests/test_qtkoil.py @@ -3,6 +3,7 @@ from koil.qt import QtCoro, QtFuture, QtGenerator, QtGeneratorRunner, QtKoil, QtRunner import contextvars import pytest + x = contextvars.ContextVar("x") @@ -139,7 +140,7 @@ def task_finished(self): self.greet_label.setText("Hello!") async def call_coro(self): - x = await self.do_me.acall() + await self.do_me.acall() self.coroutine_was_run = True @@ -185,7 +186,7 @@ async def call_coro(self): self.coroutine_finished = True - x = await self.do_me.acall() + await self.do_me.acall() self.coroutine_was_run = True print("nana") @@ -206,6 +207,7 @@ def test_koil_qt_no_interference(qtbot): assert widget.greet_label.text() == "Hello!" + @pytest.mark.qt def test_koil_qt_call_task(qtbot): """Tests if we can call a task from a koil widget.""" @@ -213,9 +215,10 @@ def test_koil_qt_call_task(qtbot): qtbot.addWidget(widget) # click in the Greet button and make sure it updates the appropriate label - with qtbot.waitSignal(widget.sleep_and_resolve_task.returned) as b: + with qtbot.waitSignal(widget.sleep_and_resolve_task.returned): qtbot.mouseClick(widget.call_task_button, QtCore.Qt.LeftButton) + @pytest.mark.qt def test_call_gen(qtbot): """Tests if we can call a task from a koil widget.""" @@ -223,10 +226,11 @@ def test_call_gen(qtbot): qtbot.addWidget(widget) # click in the Greet button and make sure it updates the appropriate label - with qtbot.waitSignal(widget.sleep_and_yield_task.yielded, timeout=1000) as b: + with qtbot.waitSignal(widget.sleep_and_yield_task.yielded, timeout=1000): qtbot.mouseClick(widget.call_gen_button, QtCore.Qt.LeftButton) + @pytest.mark.qt def test_call_future(qtbot): """Tests if we can call a task from a koil widget.""" @@ -238,8 +242,9 @@ def test_call_future(qtbot): qtbot.mouseClick(widget.call_task_button, QtCore.Qt.LeftButton) - assert widget.task_was_run == True - assert widget.coroutine_was_run == True + assert widget.task_was_run is True + assert widget.coroutine_was_run is True + @pytest.mark.qt def test_call_raise(qtbot): @@ -254,6 +259,7 @@ def test_call_raise(qtbot): assert isinstance(b.args[0], Exception) + @pytest.mark.qt def test_context(qtbot): """Tests if we can call a task from a koil widget.""" diff --git a/tests/test_qtkoil_modern.py b/tests/test_qtkoil_modern.py new file mode 100644 index 0000000..87410cc --- /dev/null +++ b/tests/test_qtkoil_modern.py @@ -0,0 +1,278 @@ +import asyncio +from PyQt5 import QtWidgets, QtCore +from koil.qt import QtFuture, QtGenerator, QtKoil +import contextvars +import pytest +from koil.qt import unkoilqt, koilqt + +x = contextvars.ContextVar("x") + + +async def sleep_and_resolve(): + await asyncio.sleep(0.1) + return 1 + + +async def sleep_and_raise(): + await asyncio.sleep(0.1) + raise Exception("Task is done!") + + +async def sleep_and_use_context(): + await asyncio.sleep(0.1) + return x.get() + 1 + + +async def sleep_and_yield(times=5): + for i in range(times): + await asyncio.sleep(0.1) + yield i + + +class KoiledWidget(QtWidgets.QWidget): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.koil = QtKoil(parent=self) + self.koil.enter() + + self.button_greet = QtWidgets.QPushButton("Greet") + self.greet_label = QtWidgets.QLabel("") + + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.button_greet) + layout.addWidget(self.greet_label) + + self.setLayout(layout) + + self.button_greet.clicked.connect(self.greet) + + def greet(self): + self.greet_label.setText("Hello!") + + +class KoiledInterferingWidget(QtWidgets.QWidget): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.koil = QtKoil(parent=self) + self.koil.enter() + + self.call_task_button = QtWidgets.QPushButton("Call Task") + self.call_gen_button = QtWidgets.QPushButton("Call Generator") + self.call_raise_button = QtWidgets.QPushButton("Call Raise") + self.call_context_button = QtWidgets.QPushButton("Call Context") + + self.sleep_and_resolve_task = unkoilqt(sleep_and_resolve) + self.sleep_and_resolve_task.returned.connect(self.task_finished) + + self.sleep_and_use_context_task = unkoilqt(sleep_and_use_context) + self.sleep_and_use_context_task.returned.connect(self.task_finished) + + self.sleep_and_yield_task = unkoilqt(sleep_and_yield) + self.sleep_and_yield_task.yielded.connect(self.task_finished) + + self.sleep_and_raise_task = unkoilqt(sleep_and_raise) + self.sleep_and_resolve_task.returned.connect(self.task_finished) + + self.greet_label = QtWidgets.QLabel("") + self.value = None + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.call_task_button) + layout.addWidget(self.call_gen_button) + layout.addWidget(self.call_context_button) + layout.addWidget(self.greet_label) + + self.setLayout(layout) + + self.call_task_button.clicked.connect(self.call_task) + self.call_gen_button.clicked.connect(self.call_gen) + self.call_context_button.clicked.connect(self.call_context) + self.call_raise_button.clicked.connect(self.call_raise) + + def call_task(self): + self.sleep_and_resolve_task.run() + + def call_gen(self): + self.sleep_and_yield_task.run() + + def call_context(self): + self.sleep_and_use_context_task.run() + + def call_raise(self): + self.sleep_and_raise_task.run() + + def task_finished(self, int): + self.value = int + self.greet_label.setText("Hello!") + + +class KoiledInterferingFutureWidget(QtWidgets.QWidget): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.koil = QtKoil(parent=self) + self.koil.enter() + + self.do_me = koilqt(self.in_qt_task, autoresolve=False) + + self.my_coro_task = unkoilqt(self.call_coro) + self.my_coro_task.returned.connect(self.task_finished) + + self.task_was_run = False + self.coroutine_was_run = False + + self.call_task_button = QtWidgets.QPushButton("Call Task") + self.greet_label = QtWidgets.QLabel("") + + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.call_task_button) + layout.addWidget(self.greet_label) + + self.setLayout(layout) + + self.call_task_button.clicked.connect(self.call_task) + + def in_qt_task(self, future: QtFuture): + self.task_was_run = True + future.resolve("called") + + def call_task(self): + self.my_coro_task.run() + + def task_finished(self): + self.greet_label.setText("Hello!") + + async def call_coro(self): + await self.do_me() + self.coroutine_was_run = True + + +class KoiledGeneratorWidget(QtWidgets.QWidget): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.koil = QtKoil(parent=self) + self.koil.enter() + + self.my_coro_task = unkoilqt(self.call_coro) + self.my_coro_task.returned.connect(self.task_finished) + + self.task_was_run = False + self.coroutine_was_run = False + self.coroutine_finished = False + + self.call_task_button = QtWidgets.QPushButton("Call Task") + self.greet_label = QtWidgets.QLabel("") + + layout = QtWidgets.QVBoxLayout() + layout.addWidget(self.call_task_button) + layout.addWidget(self.greet_label) + + self.setLayout(layout) + + self.call_task_button.clicked.connect(self.call_task) + + def in_qt_task(self, future: QtFuture): + self.task_was_run = True + future.resolve("called") + + def call_task(self): + self.my_coro_task.run() + + def task_finished(self): + self.greet_label.setText("Hello!") + + async def call_coro(self): + self.qt_generator = QtGenerator() + + async for x in self.qt_generator: + self.coroutine_was_run = True + + self.coroutine_finished = True + + await self.do_me.acall() + self.coroutine_was_run = True + print("nana") + + +@pytest.mark.qt +def test_koil_qt_no_interference(qtbot): + """Tests if just adding koil interferes with normal + qtpy widgets. + + Args: + qtbot (_type_): _description_ + """ + widget = KoiledWidget() + qtbot.addWidget(widget) + + # click in the Greet button and make sure it updates the appropriate label + qtbot.mouseClick(widget.button_greet, QtCore.Qt.LeftButton) + + assert widget.greet_label.text() == "Hello!" + + +@pytest.mark.qt +def test_koil_qt_call_task(qtbot): + """Tests if we can call a task from a koil widget.""" + widget = KoiledInterferingWidget() + qtbot.addWidget(widget) + + # click in the Greet button and make sure it updates the appropriate label + with qtbot.waitSignal(widget.sleep_and_resolve_task.returned): + qtbot.mouseClick(widget.call_task_button, QtCore.Qt.LeftButton) + + +@pytest.mark.qt +def test_call_gen(qtbot): + """Tests if we can call a task from a koil widget.""" + widget = KoiledInterferingWidget() + qtbot.addWidget(widget) + + # click in the Greet button and make sure it updates the appropriate label + with qtbot.waitSignal(widget.sleep_and_yield_task.yielded, timeout=1000): + + qtbot.mouseClick(widget.call_gen_button, QtCore.Qt.LeftButton) + + +@pytest.mark.qt +def test_call_future(qtbot): + """Tests if we can call a task from a koil widget.""" + widget = KoiledInterferingFutureWidget() + qtbot.addWidget(widget) + + # click in the Greet button and make sure it updates the appropriate label + with qtbot.waitSignal(widget.my_coro_task.returned, timeout=1000): + + qtbot.mouseClick(widget.call_task_button, QtCore.Qt.LeftButton) + + assert widget.task_was_run is True + assert widget.coroutine_was_run is True + + +@pytest.mark.qt +def test_call_raise(qtbot): + """Tests if we can call a task from a koil widget.""" + widget = KoiledInterferingWidget() + qtbot.addWidget(widget) + + # click in the Greet button and make sure it updates the appropriate label + + with qtbot.waitSignal(widget.sleep_and_raise_task.errored, timeout=1000) as b: + qtbot.mouseClick(widget.call_raise_button, QtCore.Qt.LeftButton) + + assert isinstance(b.args[0], Exception) + + +@pytest.mark.qt +def test_context(qtbot): + """Tests if we can call a task from a koil widget.""" + widget = KoiledInterferingWidget() + qtbot.addWidget(widget) + + x.set(5) + # click in the Greet button and make sure it updates the appropriate label + + with qtbot.waitSignal( + widget.sleep_and_use_context_task.returned, timeout=1000 + ) as b: + qtbot.mouseClick(widget.call_context_button, QtCore.Qt.LeftButton) + + assert b.args[0] == 6 diff --git a/tests/test_threaded.py b/tests/test_threaded.py index 8004279..92893d2 100644 --- a/tests/test_threaded.py +++ b/tests/test_threaded.py @@ -74,5 +74,3 @@ def test_iterating(): assert next(x) == 1 assert next(x) == 2 assert next(x) == 3 - - From a9ac2817f17bf098113fa28c246517e3c33991ee Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Wed, 31 Jan 2024 12:00:45 +0100 Subject: [PATCH 29/30] update --- koil/qt.py | 56 +++++++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/koil/qt.py b/koil/qt.py index b3a6fb0..986e356 100644 --- a/koil/qt.py +++ b/koil/qt.py @@ -19,6 +19,8 @@ from typing import Protocol from .utils import check_is_asyncfunc, check_is_asyncgen, check_is_syncgen + + logger = logging.getLogger(__name__) @@ -347,19 +349,19 @@ class UnkoiledQt(Protocol): returned: QtCore.Signal def run(self, *args, **kwargs) -> KoilFuture: - """Runs the function in the governing loop and returns a KoilFuture - + """ Runs the function in the governing loop and returns a KoilFuture + This is useful if you want to cancel the function from the outside. The function will be run in the governing loop and the result will be send to the main thread via a QtSignal. - + Args: *args: The arguments for the function **kwargs: The keyword arguments for the function Returns: KoilFuture: The future that can be cancelled - + """ ... @@ -372,46 +374,47 @@ class KoilQt(Protocol): returned: QtCore.Signal def run(self, *args, **kwargs) -> KoilFuture: - """Runs the function in the governing loop and returns a KoilFuture - + """ Runs the function in the governing loop and returns a KoilFuture + This is useful if you want to cancel the function from the outside. The function will be run in the governing loop and the result will be send to the main thread via a QtSignal. - + Args: *args: The arguments for the function **kwargs: The keyword arguments for the function Returns: KoilFuture: The future that can be cancelled - + """ ... + def unkoilqt(func, *args, **kwargs) -> UnkoiledQt: - """Unkoils a function so that it can be run in the main thread + """ Unkoils a function so that it can be run in the main thread Args: func (Callable): The function to run in the main thread - - + + """ if not (check_is_asyncgen(func) or check_is_asyncfunc(func)): raise TypeError(f"{func} is not an async function") if check_is_asyncgen(func): - return async_generator_to_qt(func, *args, **kwargs) - + return async_generator_to_qt(func, *args, **kwargs) + else: return async_to_qt(func, *args, **kwargs) - + def koilqt(func, *args, autoresolve=None, **kwargs) -> Callable[..., Awaitable[Any]]: - """Converts a qt mainthread function to be run in the asyncio loop - + """ Converts a qt mainthread function to be run in the asyncio loop + Args: func (Callable): The function to run in the main thread (can also be a generator) @@ -419,25 +422,25 @@ def koilqt(func, *args, autoresolve=None, **kwargs) -> Callable[..., Awaitable[A Returns: Callable[..., Awaitable[Any]]: The function that can be run in the asyncio loop - + """ - + if check_is_asyncgen(func) or check_is_asyncfunc(func): - raise TypeError( - f"{func} should NOT be a coroutine function. This is a decorator to convert a function to be callable form the asyncio loop" - ) - + raise TypeError(f"{func} should NOT be a coroutine function. This is a decorator to convert a function to be callable form the asyncio loop") + if check_is_syncgen(func): if autoresolve is not None or autoresolve is True: raise TypeError("Cannot autoresolve a generator") return qtgenerator_to_async(func, *args, **kwargs) - + else: if autoresolve is None: autoresolve = True return qt_to_async(func, *args, autoresolve=autoresolve, **kwargs) + + class WrappedObject(QtCore.QObject): def __init__(self, *args, koil: Koil = None, **kwargs): super().__init__(*args, **kwargs) @@ -470,8 +473,9 @@ class Config: arbitrary_types_allowed = True -def create_qt_koil(parent, auto_enter: bool = True) -> QtKoil: - koil = QtKoil(parent=parent) + +def create_qt_koil(parent, auto_enter: bool=True) -> QtKoil: + koil = QtKoil(parent=parent) if auto_enter: koil.enter() - return koil + return koil \ No newline at end of file From 9cf5240067c56dadb792d86b94e2b932c54d724a Mon Sep 17 00:00:00 2001 From: jhnnsrs Date: Sun, 9 Jun 2024 20:21:14 +0200 Subject: [PATCH 30/30] up deps --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2d30257..038d28e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "koil" -version = "0.3.5" +version = "0.3.6" readme = "README.md" description = "Async for a sync world" authors = ["jhnnsrs "]